类和对象
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、注意
初始化:被初始化的对象正在创建;
赋值:被赋值的对象已经存在;
成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关。