Hello,C++(3)

类和对象

1、 基本概念

a)封装

  概念:1、把属性和方法封装成类

     2、对属性和方法进行访问控制

  类成员的访问控制:C++中可以给成员变量和成员函数定义访问级别

      Public修饰成员变量和成员函数可以在类的内部和类的外部被访问:完全公开

      Private修饰成员变量和成员函数只能在类的内部被访问:基类访问

      Protected只能用在继承里面:仅限于整个家族的访问

  struct和class关键字区别

     用struct定义类时,所有成员的默认属性为public

     用class定义类时,所有成员的默认属性为private

b)继承(待整理)

c)多态(待整理)

2、 对象的构造和析构

  创建一个对象时,常常需要作某些初始化的工作,例如对数据成员赋初值。注意,类的数据成员是不能在声明类时初始化的。

  为了解决这个问题,C++编译器提供了构造函数(constructor)来处理对象的初始化。构造函数是一种特殊的成员函数,与其他成员函数不同,不需要用户来调用它,而是在建立对象时自动执行。

a)构造函数

  i.构造函数的定义

  1)C++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数叫做构造函数;

  2)构造函数在定义时可以有参数;

  3)没有任何返回类型的声明。

  ii. 构造函数的调用

  自动调用:一般情况下C++编译器在创建对象时会自动调用构造函数

  手动调用:在一些情况下则需要手工调用构造函数

  注意:当类中提供了有参构造函数或无参构造函数,c++编译器都不会提供默认无参构造函数。也就是说,当你定义了构造函数,你必须使用它。

  iii.构造函数的分类使用

   1)  无参数构造函数

    调用方法: Test t1, t2;

   2)  有参构造函数

    Test5 t1(10);    //c++编译器默认调用有参构造函数 括号法

              Test5 t2 = (20, 10);   //c++编译器默认调用有参构造函数 等号法

              Test5 t3 = Test5(30);   //程序员手工调用构造函数 产生了一个对象 构造函数法赋值法

   3)拷贝构造函数

    使用某个对象的实例来初始化这个对象的一个新的实例,用于类对象之间的复制

    如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。

    拷贝构造函数的最常见形式如下:

      AA(const AA &obj)  //obj 是一个对象引用,该对象是用于初始化另一个对象的。

实例 :

#include <iostream>
using namespace std;
 
class A
{
private:
    int a;
public:
    A(int i){a=i;}    //内联的构造函数
    A(A    &aa);
    int geta(){return a;}
};
 
A::A(A &aa)        //拷贝构造函数
{
    a=aa.a;
    cout<<"拷贝构造函数执行!"<<endl;
}
 
int get_a(A aa)        //③参数是对象,是值传递,会调用拷贝构造函数
{
    return aa.geta();
}
 
int get_a_1(A &aa)    //如果参数是引用类型,本身就是引用传递,所以不会调用拷贝构造函数
{
    return aa.geta();
}
 
A get_A()        //④返回值是对象类型,会调用拷贝构造函数。会调用拷贝构造函数,因为函数体内生成的对象aa是临时的,离开这个函数就消失了。所有会调用拷贝构造函数复制一份。
{
    A aa(1);
    return aa;
}
 
A& get_A_1()    //会调用拷贝构造函数,因为函数体内生成的对象aa是临时的,离开这个函数就消失了。所有会调用拷贝构造函数复制一份。
{
    A aa(1);
    return aa;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    A a1(1);
    A b1(a1);            //①用a1初始化b1,调用拷贝构造函数
    A c1=a1;            //②用a1初始化c1,调用拷贝构造函数
 
    int i=get_a(a1);        //③函数形参是类的对象,调用拷贝构造函数
    int j=get_a_1(a1);        //函数形参类型是引用,不调用拷贝构造函数
 
    A d1=get_A();        //调用拷贝构造函数
    A e1=get_A_1();        //调用拷贝构造函数
 
    return 0;
}

 

调用拷贝构造函数的四种时机:
1、A b1(a1); //①用a1初始化b1,调用拷贝构造函数
2、A c1=a1; //②用a1初始化c1,调用拷贝构造函数

3、int i=get_a(a1); //③函数形参是类的对象,调用拷贝构造函数 
4、A get_A() //④函数的返回类型是对象
注意:
int get_a_1(A &aa)    //如果参数是引用类型,本身就是引用传递,所以不会调用拷贝构造函数

  小结:

  必须定义拷贝构造函数的情况:

  只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义拷贝构造函数也可以拷贝;有的类有一个数据成员是指针,或者是有成员表示在构造函数中分配的其他资源,这两种情况下都必须定义拷贝构造函数。

  什么情况使用拷贝构造函数:

  类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:

  (1)一个对象以值传递的方式传入函数体

  (2)一个对象以值传递的方式从函数返回

  (3)一个对象需要通过另外一个对象进行初始化。

b)析构函数

  语法:~ClassName()

  i.析构函数的定义

  1)C++中的类可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数

  2)析构函数没有参数也没有任何返回类型的声明

  3)析构函数在对象销毁时自动被调用

  ii.析构函数的调用

  C++编译器自动调用

  构造函数和析构函数的调用顺序:

    1)当类中有成员变量是其它类的对象时,首先调用成员变量的构造函数,调用顺序与声明顺序相同;之后调用自身类的构造函数

    2)析构函数的调用顺序与对应的构造函数调用顺序相反

  iii.深拷贝和浅拷贝

  浅拷贝和深拷贝的比较:

  浅拷贝是指在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了。

  当调用一次构造函数,调用两次析构函数,而两个对象的指针成员所指内存相同,这会导致什么问题呢?指针被分配一次内存,但是程序结束时该内存却被释放了两次,会导致崩溃。

  对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。

  (以上为浅拷贝和深拷贝的意义,详细见https://blog.csdn.net/caoshangpa/article/details/79226270)

  iv.初始化列表

  1、必须使用初始化列表的几种情况:

    1) 类成员为const类型

    2) 类成员为引用类型

实例:

#include <iostream>
using namespace std;

class A
{
    public:
        A(int &v) : i(v), p(v), j(v) {}
        void print_val() { cout << "hello:" << i << "  " << j << endl;}
    private:
        const int i;
        int p;
        int &j;
};

int main(int argc ,char **argv)
{
    int pp = 45;
    A b(pp);
    b.print_val();
}

  因为const对象或引用只能初始化但是不能赋值。构造函数的函数体内只能做赋值而不是初始化,

  因此初始化const对象或引用的唯一机会是构造函数函数体之前的初始化列表中。

    3) 类成员为没有默认构造函数的类类型

#include <iostream>
using namespace std;

class Base
{
    public:
        Base(int a) : val(a) {}
    private:
        int val;
};

class A
{
    public:
        A(int v) : p(v), b(v) {}
        void print_val() { cout << "hello:" << p << endl;}
    private:
        int p;
        Base b;
};

int main(int argc ,char **argv)
{
    int pp = 45;
    A b(pp);
    b.print_val();
}

原因同样是创建对象时,要初始类成员的每一个成员(如果没有在初始化列表里面,编译器会自动使用它的默认的构造函数进行初始化,

但是它没有默认构造函数,所以会编译报错,所以没有默认构造函数的成员变量需要使用初始化列表进行初始化)

    4) 如果类存在继承关系,派生类必须在其初始化列表中调用基类的构造函数

#include <iostream>
using namespace std;

class Base
{
    public:
        Base(int a) : val(a) {}
    private:
        int val;
};

class A : public Base
{
    public:
        A(int v) : p(v), Base(v) {}
        void print_val() { cout << "hello:" << p << endl;}
    private:
        int p;
};

int main(int argc ,char **argv)
{
    int pp = 45;
    A b(pp);
    b.print_val();
}

  2、语法规则

Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)

{

    // some other assignment operation

}

  3、注意

  初始化:被初始化的对象正在创建;

  赋值:被赋值的对象已经存在;

  成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关。

  v.对象的动态建立和释放

3、静态成员变量成员函数

4、this指针

5、全局函数和成员函数的对比

6、友元

a)       友元函数

b)       友元类

7、运算符重载

a)       实现方法

b)       限制

c)       友元函数实现操作符重载  

猜你喜欢

转载自www.cnblogs.com/juanjuanduang/p/10837306.html