目录
this指针,友元,重载运算符等后续单独出。
1.类与对象基本概念
-
类的封装性:将具有共性的数据和方法封装在一起,加以权限区分,限制用户的访问。
-
类的权限:
private(私有),protected(保护),public(共有)
类的内部(class内)没有权限限制,权限限制在类的外部实现。
默认为私有的。 private成员:只能被本类成员函数或友元访问。 public成员:在任何地方都可以访问。 protected成员:在private的基础上,还可以被派生类访问。
加了作用域后也相当于在类内。
2.构造函数
-
定义:
-
构造器 constructor
对类创建的对象进行初始化 特点: 1.名称与类名一致;
2.没有返回类型;
3.经常被重载;
4.每次创建对象都会自动调用构造函数。
-
默认构造器:default constructor 可以进行无参调用的构造函数
特点: 1.一个类没有定义构造函数时,c++编译器会自动生成默认构造函数; 2.类中只要有定义了构造函数,c++编译器就不会生成默认构造函数; 3.所有参数都是默认参数的普通构造函数,也可以充当默认构造函数。
-
调用构造函数各种方式:
class Data { public: int mA; public: //无参构造函数 Data() { mA=0; cout<<"无参构造函数"<<endl; } //有参构造函数 Data(int a) { mA=a; cout<<"有参构造函数 mA="<<mA<<endl; } }; int main() { //隐式调用无参构造函数(推荐) Data ob1; //显示调用无参构造函数 Data ob2 = Data(); //隐式调用有参构造函数(推荐) Data ob3(10); //显示调用有参构造函数 Data ob4 = Data(10); //匿名对象(无参) 当前语句技术 立即释放 Data(); Data(20); //构造函数隐式转换(类中只有一个数据成员) Data ob5 = 100; //相当于Data ob5(100) }
-
-
注:自己写时,有参无参的构造函数都要实现。
3.析构函数
-
定义
析构器 destructor
当对象不再使用时(出了所在作用域),对该对象进行清理工作 特点:(与构造器相反) 1.名称为“~类名”;
2.没有返回类型;
3.没有参数;
4.每次析构对象都会自动调用析构函数。
5.析构函数理论上可写可不写,但有指针的时候必须写,不然指针开辟的空间无法删除。
4.构造和析构函数调用顺序
#include<iostream> using namespace std; //创建一个类 class Data { private: int a; public: Data(); Data(int b); ~Data(); }; //默认构造函数 Data::Data() { a = 999; cout << "调用默认构造函数:" << a << endl; } //构造函数 Data::Data(int b) { a = b; cout << "调用构造函数:" << a << endl; } Data::~Data() { cout << "调用析构函数,a="<<a << endl; } int main() { Data D0; Data D1(10); Data D2(20); { Data D3(30); } Data D4(40); return 0; }
调用顺序: 1.D0默认构造函数。 2.D1构造函数。 3.D2构造函数。 4.D3构造函数。 5.此时遇到},自动对{}里的内容调用析构函数。 6.D4构造函数。 7.遇到},从下往上依次对D4,D2,D1,D0调用析构函数。
总结:1.不加参时自动调用默认构造函数;2.遇到}自动调用析构函数;3.析构顺序为从下往上(最先构造的最后被析构).
5.拷贝构造函数
-
定义:
拷贝构造函数 copy constructor 用一个已经存在的对象创建一个新的对象
特点:
1.如果没有实现拷贝构造函数,编译器自动生成一个拷贝构造函数(行为是bit-wise copy) 2.自定义拷贝构造函数:当编译器提供的拷贝构造函数无法满足需求。(如年龄大两岁)
-
什么时候调用?
新对象被旧对象初始化时。
-
拷贝构造 和 无参构造 有参构造的关系
如果用户定义了 拷贝构造或者有参构造 都会屏蔽无参构造。
如果用户定义了 无参构造或者有参构造 不会屏蔽拷贝构造。
-
拷贝构造几种调用形式
1、旧对象给新对象初始化 调用拷贝构造。
2、给对象取别名 不会调用拷贝构造。
3、普通对象作为函数参数,调用函数时 会发生拷贝构造。
4、函数返回值普通对象。
Visual Studio会发生拷贝构造
#include<iostream> using namespace std; //创建一个类 class Data { public: int a; public: Data(); Data(int b); ~Data(); Data(const Data& D); }; //默认构造函数 Data::Data() { a = 999; cout << "调用默认构造函数:" << a << endl; } //构造函数 Data::Data(int b) { a = b; cout << "调用构造函数:" << a << endl; } //析构函数 Data::~Data() { cout << "调用析构函数,a="<<a << endl; } //拷贝构造函数 Data::Data(const Data& D)//D是旧对象的数据,函数内调用的是新对象数据 { //用旧对象的数值给新对象赋值 a = D.a; cout << "拷贝构造函数" << endl; } int main() { Data D1(10); //调用构造函数 Data D2=D1; //调用拷贝构造函数 Data& DD = D1; //取别名不会调用拷贝构造函数 cout << "D1:" << D1.a << endl; cout << "D2:" << D2.a << endl; return 0; }
运行结果:
-
6.浅拷贝和深拷贝
-
什么是浅拷贝和深拷贝?
-
浅拷贝:只是数值一样。如果成员是指针,则原函数和拷贝构造函数公用一个地址。
-
深拷贝:为拷贝函数的指针成员开辟新地址。
-
-
分别在什么时候用浅拷贝和深拷贝?
-
默认的拷贝构造 都是浅拷贝。
-
如果类中没有指针成员, 不用实现拷贝构造和析构函数。
-
如果类中有指针成员,且指向堆区空间, 必须实现析构函数释放指针成员指向的堆区空间,必须实现拷贝构造完成深拷贝动作(否则指针区域会被析构多次)。
-
7.初始化列表
-
对象成员
定义:类中的成员也可以是对象,叫做对象成员。
当一个类中有对象成员,无参调用时,不需要初始化列表。
#include<iostream> using namespace std; class A { public: int mA; public: A() { mA = 0; cout << "A类无参构造" << endl; } A(int a) { mA = a; cout << "A的有参构造" << endl; } ~A() { cout << "A的析构函数" << endl; } }; class B { public: int mB; A ob;//对象成员 public: B() { cout << "B类的无参构造" << endl; } ~B() { cout << "B的析构函数" << endl; } }; int main(int argc, char* argv[]) { B ob1; return 0; }
调用顺序:
-
-
调用有参构造函数时需要初始化列表
#include<iostream> using namespace std; class A { public: int mA; public: A() { mA = 0; cout << "A类无参构造" << endl; } A(int a) { mA = a; cout << "A的有参构造" << endl; } ~A() { cout << "A的析构函数" << endl; } }; class B { public: int mB; A ob;//对象成员 public: B() { cout << "B类的无参构造" << endl; } //ob(a)隐式调用有参构造 // B(int a, int b):ob(a) { mB = b; cout << "B类的有参构造" << endl; } ~B() { cout << "B的析构函数" << endl; } }; int main(int argc, char* argv[]) { B ob1(10, 20); cout << "mA =" << ob1.ob.mA << ", mB =" << ob1.mB << endl; return 0; }
调用顺序:
8.explicit防止构造函数隐式转换
explicit:声明为explicit的构造函数不能在隐式转换中使用
//无explicit #include<iostream> using namespace std; class MyString { public: MyString(int n) { cout << "MyString int" << endl; } MyString(const char* s) { cout << "MyString char" << endl; } }; int main() { MyString str1 = 1;//这样赋值容易产生歧义,是数值赋值?还是类初始化? MyString str2(1);//正常调用 return 0; }
#include<iostream> using namespace std; class MyString { public: explicit MyString(int n) { cout << "MyString int" << endl; } MyString(const char* s) { cout << "MyString char" << endl; } }; int main() { //如果有explicit,则无法这样赋值 //MyString str1 = 1; MyString str2(1);//正常调用 return 0; }
9.对象数组
本质是数组,数组的每个元素是对象。
#include<iostream> using namespace std; class A { public: int ma; A() { ma = 0; cout << "A():" << ma << endl; } A(int a) { ma = a; cout << "A(int a):" << ma << endl; } ~A() { cout << "~A:" << ma << endl; } }; int main() { //对象数组每个元素都会调用构造函数和析构函数 //对象数组不初始化,每个元素调用无参构造 A arr1[5]; //对象元素初始化,必须调用显示有参构造,逐个初始化 A arr2[3]={A(10),A(20),A(30)};//必须用{} cout <<"sizeof(arr2):"<< sizeof(arr2) << " " <<"sizeof(arr2[0]):" << sizeof(arr2[0]) << endl; int n = sizeof(arr2) / sizeof(arr2[0]); for (int i = 0; i < n; i++) { cout << arr2[i].ma << " "; } cout<< endl; return 0; }
10.动态对象
10.1 动态对象创建
好处:可根据需要分配空间。
具体操作:不用malloc,而用new创建动态对象,用delete释放动态对象。
malloc的问题: 1.必须直到对象长度。 2.需要强制转换返回值。 3.可能内存分配失败。 4.构造函数中,不能自动调用初始化函数。
new流程: 自动分配内存+初始化。
deletel: 自动调用析构函数+释放内存。
#include<iostream> using namespace std; class Person { public: char* name; int age; public: //无参构造 Person() { cout << "Person()" << endl; name = new char[sizeof("#")+1]; strcpy(name, "#"); age = 0; } //有参构造 Person(const char* na, int ag) { cout << "Person(char,int)" << endl; name = new char[strlen(na) + 1]; strcpy(name, na); age = ag; } void print() { cout << "name:" << name << " " << "age:" << age << endl; } ~Person() { cout << "~Person" << endl; if (name != NULL) { delete[]name; name = NULL; } } }; int main() { Person *p1 = new Person; Person *p2 = new Person("YY~", 18); p1->print(); p2->print(); delete p1; delete p2; return 0; }
10.2 动态对象数组
对象数组中每个对象都要调用构造函数。
除了栈上可以聚合初始化,其他情况必须提供一个默认的构造函数。
#include<iostream> using namespace std; class Person { public: char* name; int age; public: Person() { cout << "Person()" << endl; name = NULL; age = 0; } Person(const char* na, int ag) { cout << "Person(char,int)" << endl; name = new char[strlen(na) + 1]; strcpy(name, na); age = ag; } ~Person() { cout << "~Person()" << endl; if (name != NULL) { delete[]name; } } }; //栈聚合 void stack() { //栈聚合初始化 Person person[] = { Person("Y",19),Person("ZZYY",99) }; for (int i = 0; i < 2; i++) { cout << person[i].name << " " << person[i].age << endl; } //创建堆上对象数组必须提供构造函数 Person* all = new Person[5]; delete[]all; } int main() { stack(); return 0; }
11.静态成员
不管类有多少对象,静态成员只有一个拷贝(副本),且这个拷贝 被类中所有对象共享。
11.1 静态成员变量
static修饰的静态成员 属于类,而不是具体的对象。
所有对象共享同一个静态成员。
-
注意!!!!
static修饰的成员 定义类的时候 必须分配空间。
static修饰的静态成员数据 必须类中定义 并且 类外初始化。
#include<iostream> using namespace std; class Data { public: int a;//普通成员 //类中定义! static int b;//静态数据 }; //类外初始化! int Data::b = 99; void test() { //静态成员两种访问方式: //1.静态成员 可以通过类名称直接访问(属于类) cout <<" Data::b :"<< Data::b << endl; //2.静态成员 也可通过对象访问(共享) Data ob1; cout << "ob1.b :"<<ob1.b << endl; //所有对象共享一个静态成员 ob1.b = 222; Data ob2; ob2.b = 333; cout <<"ob1.b = 222; AND ob2.b = 333; b=" <<Data::b << endl; } int main() { test(); return 0; }
11.2 静态成员函数
静态成员函数 属于类 而不是对象(所有对象 共享)
-
定义:
class Data{ static void func(){ } }
-
注!!!!
静态成员函数可直接通过类名访问。
静态函数内 只能操作 静态对象。
class Data{ private: int data; static int a; public: static int getA() { data=10;//error 非静态对象,不能在静态数组里操作 return a; } } int Data::a=99;//静态成员类外定义 void test(){ cout<<Data::getA()<<endl;//静态成员函数可直接通过类名访问。 }