C++--day06
目录:
1. C的提高 1-131P 时间七天
2. C++的基础 132-286P 时间八天
3. C++的提高 287-378P 时间五天
4. C/C++的数据结构 379-482P 时间五天
5. C/C++的设计模式基础 483-540P 时间三天
视频资料:https://www.bilibili.com/video/av27904891?from=search&seid=10891514449061956870
P179 copy构造函数调用时机4-函数返回值是匿名对象
#include<iostream> using namespace std; class Location { public: Location( int xx = 0 , int yy = 0 ) { X = xx ; Y = yy ; cout << "Constructor Object.\n"<<endl ; } Location( const Location & p ) //拷贝构造函数 完成对象的初始化 { X = p.X ; Y = p.Y ; cout << "Copy_constructor called." << endl ; } ~Location() { cout << X << "," << Y << " Object destroyed." << endl ; } int GetX () { return X ; } int GetY () { return Y ; } private : int X , Y ; } ;
//g函数返回一个元素
//结论:函数的返回值是一个元素(复杂类型),返回的是一个新的匿名对象 所以会调用匿名对象类的拷贝对象函数
Location g() { Location A(1,2); return A; } // void objplay1() { g(); } void main() { objplay1(); system("pause"); }
输出结果
P180 copy构造函数调用时机3-函数返回值是匿名对象去和留的剖析
#include<iostream> using namespace std; class Location { public: Location( int xx = 0 , int yy = 0 ) { X = xx ; Y = yy ; cout << "Constructor Object.\n"<<endl ; } Location( const Location & p ) //拷贝构造函数 完成对象的初始化 { X = p.X ; Y = p.Y ; cout << "Copy_constructor called." << endl ; } ~Location() { cout << X << "," << Y << " Object destroyed." << endl ; } int GetX () { return X ; } int GetY () { return Y ; } private : int X , Y ; } ; //g函数返回一个元素 //结论1:函数的返回值是一个元素(复杂类型),返回的是一个新的匿名对象(所以会调用匿名对象类的拷贝对象函数) //结论2:有关匿名对象的去和留 // 如果用匿名对象初始化另外一个同类型的对象,匿名匿名对象,转成有名对象 // 如果用匿名对象赋值给另外一个同类型的对象,匿名对象被析构 //这么写代码,编译器返回一个新对象(没有名字的匿名对象) Location g() { Location A(1,2); return A; } // void objplay1() { g(); } void objplay2() { //用匿名对象初始化m,此时C++编译器直接把匿名对象转成m;从匿名转成有名字了 Location m=g(); printf("匿名对象被扶正,不会被析构\n"); cout<<m.GetX()<<endl; } void objplay3() { //用匿名对象赋值给m2,匿名对象会析构 Location m2(1,2); m2=g(); printf("因为用匿名对象=给m2,匿名对象被析构\n"); cout<<m2.GetX()<<endl; } void main() { objplay3(); system("pause"); }
P181 构造和析构的重点整理
P183 构造和析构的总结
1、 构造函数的基础
2、 构造函数的分类
有参构造函数
无参构造函数
赋值构造函数
默认构造函数
3、拷贝函数的调用时机
4、C++编译器提供的构造函数 PK 对象显示初始化方案
5、拷贝构造函数
6、构造函数调用规则研究
若定义构造函数,则必须调用
若不定义构造函数 使用C++编译提供的构造函数
7、深拷贝和浅拷贝
8、构造函数初始化列表
对象互组
9、构造和析构的调用顺序研究
先调用成员变量的构造函数,再调用自己的构造函数
#include <iostream> using namespace std; void objplaymain71(); class Test { public: Test() { a = 0; b = 0; cout << "无参数构造函数 自动被调用" <<endl; } Test(int _a) //有参数构造函数 { a = _a; b = 0; } Test(const Test& obj) //copy构造函数 作用: 用一个对象初始化另外一个对象 { a = obj.a + 100; b = obj.b + 100; } void printT() { cout << "a:" << a << "b: "<<b<< endl; } ~Test() { cout<<"我是析构函数 对象生命周期结束时,会被c++编译器自动调用" <<endl; } protected: private: int a; int b; }; // 第3种调用时机 void printTest(Test t) { ; } // 1 和 2 void objplaymain72() { // Test t1(1); //ok Test t2(t1); Test t3 = t1; //会调用copy构造函数 printTest(t3); } //copy构造函数的第4种调用时机 //返回一个元素 匿名对象 Test getTestObj() { Test t(1); return t; } void TestNoNameObj() { Test myt1 =getTestObj(); //用匿名对象初始化 另外一个对象 Test myt2(1); myt2 = getTestObj(); //用匿名对象 给 另外一个对象 赋值 匿名对象被析构 } int main() { //objplaymain(); objplaymain72(); TestNoNameObj(); cout<<"hello..."<<endl; system("pause"); return 0; } void objplaymain71() { Test t1; //ok //Test t2() ; //调用无参数构造函数的 错误方法 //t2.printT(); // Test t3(1); //c++编译器自动的调用构造函数 Test t4 = 4; //c++编译器自动的调用构造函数 Test t5 = Test(5); //程序员手工的调用构造函数 // Test t6 = t1; return ; }
P184 构造函数的调用规则研究
默认构造函数
二个特殊的构造函数
1)默认无参构造函数
当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
2)默认拷贝构造函数
当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制
构造函数调用规则研究
1)当类中没有定义任何一个构造函数时,c++编译器会提供默认无参构造函数和默认拷贝构造函数
2)当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数
3) 当类中定义了任意的非拷贝构造函数(即:当类中提供了有参构造函数或无参构造函数),c++编译器不会提供默认无参构造函数
4 )默认拷贝构造函数成员变量简单赋值
总结:只要你写了构造函数,那么你必须用。
#include <iostream> using namespace std; class Test { public: // Test(const Test& obj) //copy构造函数 作用: 用一个对象初始化另外一个对象 // { // a = obj.a + 100; // b = obj.b + 100; // } // Test(int _a, int _b) // { // ; // } Test() { cout<<"hi"<<endl; } void printT() { cout << "a:" << a << "b: "<<b<< endl; } private: int a; int b; }; //当类中定义了拷贝构造函数时,c++编译器不会提供无参数构造函数 //当类中定义了有参数构造函数是,c++编译器不会提供无参数构造函数 //在定义类时, 只要你写了构造函数,则必须要用 int main() { Test t1; //调用无参构造函数 cout<<"hello..."<<endl; system("pause"); return 0; }
P187 深浅拷贝问题
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class Name { public: Name (const char *myp) { m_len=strlen(myp); m_p=(char *)malloc(m_len+1);// strcpy(m_p,myp); } //解决方案:手工的编写拷贝构造函数 使用深copy Name(const Name& obj1) { m_len=obj1.m_len; m_p=(char *)malloc(m_len+1); strcpy(m_p,obj1.m_p); } ~Name() { if (m_p!=NULL) { free(m_p); m_p=NULL; m_len=0; } } private: char *m_p; int m_len; }; void objpalymain() { Name obj1("abcde"); Name obj2=obj1;//默认的拷贝构造函数 C++编译器提供的 } void main() { objpalymain(); system("pause"); }
P188 深拷贝和浅拷贝-默认的等号操作符也是浅拷贝
#define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class Name { public: Name (const char *myp) { m_len=strlen(myp); m_p=(char *)malloc(m_len+1);// strcpy(m_p,myp); } //解决方案:手工的编写拷贝构造函数 使用深copy Name(const Name& obj1) { m_len=obj1.m_len; m_p=(char *)malloc(m_len+1); strcpy(m_p,obj1.m_p); } ~Name() { if (m_p!=NULL) { free(m_p); m_p=NULL; m_len=0; } } private: char *m_p; int m_len; }; void objpalymain() { Name obj1("abcde"); //Name obj2=obj1;//默认的拷贝构造函数 C++编译器提供的 Name obj3("obj3"); obj3 = obj1;//等号操作 } void main() { objpalymain(); system("pause"); }
P189 构造函数的初始化列表
1、对象初始化列表
1)对象初始化列表出现原因
1.必须这样做:
如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数,
如果没有初始化列表,那么他将无法完成第一步,就会报错。
2.类成员中若有const修饰,必须在对象初始化的时候,给const int m 赋值
当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,
因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。
2)C++中提供初始化列表对成员变量进行初始化
语法规则
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}
3)注意概念
初始化:被初始化的对象正在创建
赋值:被赋值的对象已经存在
4)注意:
成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关
初始化列表先于构造函数的函数体执行
#include<iostream>
using namespace std; class A { public: A(int _a) { a=_a; cout<<"构造函数"<<"a:"<<a<<endl; } ~A() { cout<<"A的析构函数"<<"a:"<<a<<endl; } protected: private: int a; }; //1 构造函数的初始化列表 解决: 在B类中 组合了一个 A类对象 (A类设计了构造函数) // 根据构造函数的调用规则 设计A的构造函数, 必须要用;没有机会初始化A // 新的语法 Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3) class B { public: B(int _b1,int _b2):a1(1),a2(2),c(0) { b1=_b1; b2=_b2; } B(int _b1,int _b2,int m,int n):a1(m),a2(n),c(0) { b1=_b1; b2=_b2; cout<<"B的构造函数"<<endl; } ~B() { cout<<"B的析构函数"<<endl; } protected: private: int b1; int b2; A a1; A a2;
const int c;
}; //2 先执行 被组合对象的构造函数 // 如果组合对象有多个,按照定义顺序, 而不是按照初始化列表的顺序 // 析构函数 : 和构造函数的调用顺序相反 //3 被组合对象的构造顺序 与定义顺序有关系 ,与初始化列表的顺序没有关系 //4 初始化列表 用来 给const 属性赋值 void obj10play() { //A a1(10); //1. 参数传递 B objB(1,2,3,4); //2. 调用顺序 } void main() { obj10play(); system("pause"); }
输出结果
P190 强化训练1--构造和析构调用顺序
//对象做函数参数 //1 研究拷贝构造 //2 研究构造函数,析构函数的调用顺序 //总结 构造和析构的调用顺序 #include "iostream" using namespace std; class ABCD { public: ABCD(int a, int b, int c) { this->a = a; this->b = b; this->c = c; printf("ABCD() construct, a:%d,b:%d,c:%d \n", this->a, this->b, this->c); } ~ABCD() { printf("~ABCD() construct,a:%d,b:%d,c:%d \n", this->a, this->b, this->c); } int getA() { return this->a; } private: int a; int b; int c; }; class MyE { public: MyE():abcd1(1,2,3),abcd2(4,5,6),m(100) { cout<<"MyD()"<<endl; } ~MyE() { cout<<"~MyD()"<<endl; } MyE(const MyE & obj):abcd1(7,8,9),abcd2(10,11,12),m(100) { printf("MyD(const MyD & obj)\n"); } protected: //private: public: ABCD abcd1; //c++编译器不知道如何构造abc1 ABCD abcd2; const int m; }; int doThing(MyE mye1)//是一个元素,用实参初始化形参,调用形参拷贝构造函数 { printf("doThing() mye1.abc1.a:%d \n", mye1.abcd1.getA()); return 0; } int run2() { MyE myE; doThing(myE);//实参调用doThing函数 return 0; } // int run3() { printf("run3 start..\n"); //ABCD(400, 500, 600); //临时对象的生命周期 ABCD abcd = ABCD(100, 200, 300); //若直接调用构造函数呢? //想调用构造函数对abc对象进行再复制,可以吗? //在构造函数里面调用另外一个构造函数,会有什么结果? printf("run3 end\n"); return 0; } int main() { run2(); //run3(); system("pause"); return 0; }
输出结果
P191 强化训练2--匿名对象声明周期
//研究构造函数,析构函数的调用顺序 //总结 构造和析构的调用顺序 #include "iostream" using namespace std; class ABCD { public: ABCD(int a, int b, int c) { this->a = a; this->b = b; this->c = c; printf("ABCD() construct, a:%d,b:%d,c:%d \n", this->a, this->b, this->c); } ~ABCD() { printf("~ABCD() construct,a:%d,b:%d,c:%d \n", this->a, this->b, this->c); } int getA() { return this->a; } private: int a; int b; int c; }; int run3() { printf("run3 start..\n"); //ABCD(400, 500, 600); //临时对象的生命周期 ABCD abcd = ABCD(100, 200, 300); //若直接调用构造函数呢? //想调用构造函数对abc对象进行再复制,可以吗? //在构造函数里面调用另外一个构造函数,会有什么结果? printf("run3 end\n"); return 0; } int main() { run3(); system("pause"); return 0; }
第一种 ABCD(400, 500, 600)输出结果 -- 先调用构造函数然后紧接着调用析构函数:因为是匿名对象,没有人接管它
第二种 ABCD abcd = ABCD(100, 200, 300);输出结果 -- 匿名对象转正abcd,运行完毕之后会调用析构函数
P192 强化训练3--构造中调用构造(产生匿名对象)
#include "iostream" using namespace std; //构造中调用构造是危险的行为 class MyTest { public: MyTest(int a, int b, int c) { this->a = a; this->b = b; this->c = c; } MyTest(int a, int b) { this->a = a; this->b = b; MyTest(a, b, 100); //产生新的匿名对象 } ~MyTest() { printf("MyTest~:%d, %d, %d\n", a, b, c); } protected: private: int a; int b; int c; public: int getC() const { return c; } void setC(int val) { c = val; } }; int main() { MyTest t1(1, 2); printf("c:%d\n", t1.getC()); //请问c的值是? system("pause"); return 0; }
输出结果:
由上节可知,匿名对象的生命周期先调用构造函数然后紧接着调用析构函数:因为是匿名对象,没有人接管它
匿名对象和t1没有关系,调用的是匿名对象的析构函数
P193 new和delete的基本语法
1)在软件开发过程中,常常需要动态地分配和撤销内存空间,例如对动态链表中结点的插入与删除。在C语言中是利用库函数malloc和free来分配和撤销内存空间的。
C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数。
注意: new和delete是运算符,不是函数,因此执行效率高。
2)虽然为了与C语言兼容,C++仍保留malloc和free函数,但建议用户不用malloc和free函数,而用new和delete运算符。
new运算符的例子:
new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针) new int(100); //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址 new char[10]; //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址 new int[5][4]; //开辟一个存放二维整型数组(大小为5*4)的空间,返回首元素的地址 float *p=new float (3.14159); //开辟一个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址赋给指针变量p
3)new和delete运算符使用的一般格式为:
用new分配数组空间时不能指定初值。如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值判断分配空间是否成功。
4) 应用举例
////分配基础类型 void main() { // int *p = (int *)malloc(sizeof(int)); *p = 10; free(p); int *p2 = new int; //分配基础类型 *p2 = 20; free(p2); // int *p3 = new int(30); printf("*p3:%d \n", *p3);//30delete p3; cout<<"hello..."<<endl; system("pause"); return ; }
//分配数组变量 void main() { //c语言分配数组 int *p = (int *)malloc(sizeof(int) * 10); //int array[10]; p[0] = 1; free(p); //c++分配数组 int *pArray = new int[10] ; pArray[1] = 2; delete [] pArray; //数组不要把[] 忘记 char *pArray2 = new char[25] ; //char buf[25] delete [] pArray2; cout<<"hello..."<<endl; system("pause"); return ; }
class Test { public: Test(int _a) { a = _a; cout<<"构造函数执行" <<endl; } ~Test() { cout<<"析构函数执行" <<endl; } protected: private: int a; }; //分配对象new delete //相同 和 不同的地方 new能执行类型构造函数 delete操作符 能执行类的析构函数 void main() { //c Test *pT1 = (Test *)malloc(sizeof(Test)); free(pT1); //c++ Test *pT2 = new Test(10); delete pT2; cout<<"hello..."<<endl; system("pause"); }
P194 new和delete深入分析
混用测试、异同比较
结论: malloc不会调用类的构造函数
Free不会调用类的析构函数
#include <iostream> using namespace std; // 1 // malloc free c语言的函数 // new delete 操作符 c++的语法 //2 new 基础类型变量 分配数组变量 分配类对象 //3 ////分配基础类型--混搭没有问题 void main01() { // int *p = (int *)malloc(sizeof(int)); *p = 10; //free(p); delete p; int *p2 = new int; //分配基础类型 *p2 = 20; free(p2); // int *p3 = new int(30); printf("*p3:%d \n", *p3); //delete p3; free(p3); cout<<"hello..."<<endl; system("pause"); return ; } //分配数组变量 void main02() { //c语言分配数组 int *p = (int *)malloc(sizeof(int) * 10); //int array[10]; p[0] = 1; //free(p); delete[] p; //c++分配数组 int *pArray = new int[10] ; pArray[1] = 2; //delete [] pArray; //数组不要把[] 忘记 free(pArray); char *pArray2 = new char[25] ; //char buf[25] delete [] pArray2; cout<<"hello..."<<endl; system("pause"); return ; } class Test { public: Test(int _a) { a = _a; cout<<"构造函数执行" <<endl; } ~Test() { cout<<"析构函数执行" <<endl; } protected: private: int a; }; //分配对象new delete //相同 和 不同的地方 new能执行类型构造函数 delete操作符 能执行类的析构函数 // malloc free 函数 C 只会分配内存大小 不会调用类的构造析构函数 // new delete 操作符号 c++的关键字 //结论 void main() { //c Test *pT1 = (Test *)malloc(sizeof(Test)); //free(pT1); delete pT1; //c++ Test *pT2 = new Test(10); //delete pT2; free(pT2); cout<<"hello..."<<endl; system("pause"); }
P195 静态成员变量和静态成员函数
1)定义静态成员变量
- 关键字 static 可以用于说明一个类的成员,
静态成员提供了一个同类对象的共享机制
- 把一个类的成员说明为 static 时,这个类无论有多少个对象被创建,这些对象共享这个 static 成员
- 静态成员局部于类,它不是对象成员
#include<iostream> using namespace std; class BB { public: void printC() { cout<<"c:"<<c<<endl; } void AddC() { c=c+1; //成员函数访问静态数据成员 } protected: private: int a; int b; static int c;//声明与定义静态数据成员 }; int BB::c=10;//声明与定义静态数据成员 void main() { BB b1,b2,b3; b1.printC();//10 b2.AddC();//11 b3.printC();//11 system("pause"); }
2)静态成员函数
1)概念
- 静态成员函数数冠以关键字static
- 静态成员函数提供不依赖于类数据结构的共同操作,它没有this指针
- 在类外调用静态成员函数用 “类名 :: ”作限定词,或通过对象调用
2)案例
#include<iostream> using namespace std; class BB { public: void printC() { cout<<"c:"<<c<<endl; } void AddC() { c=c+1; //成员函数访问静态数据成员 } static void getC() //静态成员函数 { cout<<"c:"<<c<<endl; //请在静态成员函数中,能调用 普通成员属性 或者 普通成员函数吗? cout<<"a:"<<a<<endl; //error C2597: 对非静态成员“BB::a”的非法引用 } private: int a; int b; static int c;//声明与定义静态数据成员 }; int BB::c=10;//声明与定义静态数据成员 void main() { BB b1,b2,b3; b1.printC();//10 b2.AddC();//11 b3.printC();//11 //静态成员函数调用的方法 b3.getC();//用对象 BB::getC(); system("pause");//类:: }
static中取a的值,不确定是哪个对象的a,对非静态成员“BB::a”的非法引用
static中取c的值c,c是个静态成员,静态成员是属于整个类的
在静态成员函数中只能使用静态成员变量,不能使用普通成员函数
** C++编译器是如何支持面向对象的机制的
P196 C++面向对象模型初探
基础知识
C++中的class从面向对象理论出发,将变量(属性)和函数(方法)集中定义在一起,用于描述现实世界中的类。从计算机的角度,程序依然由数据段和代码段构成。
C++编译器如何完成面向对象理论到计算机程序的转化?
换句话:C++编译器是如何管理类、对象、类和对象之间的关系
具体的说:具体对象调用类中的方法,那c++编译器是如何区分,是那个具体的类,调用这个方法那?
#include "iostream" using namespace std; class C1 { public: int i; //4 int j; //4 int k; //4 protected: private: }; //12 class C2 { public: int i; int j; int k; static int m; //4 public: int getK() const { return k; } //4 void setK(int val) { k = val; } //4 protected: private: }; //24 16 12(铁钉的不对) struct S1 { int i; int j; int k; }; //12 struct S2 { int i; int j; int k; static int m; }; //16 int main() { printf("c1:%d \n", sizeof(C1));//12 printf("c2:%d \n", sizeof(C2));//12 printf("s1:%d \n", sizeof(S1));//12 printf("s2:%d \n", sizeof(S2));//12 system("pause"); }
1)C++类对象中的成员变量和成员函数是分开存储的
成员变量:
普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式
静态成员变量:存储于全局数据区中
成员函数:存储于代码段中。
问题出来了:很多对象共用一块代码?代码是如何区分具体对象的那?
换句话说:int getK() const { return k; },代码是如何区分,具体obj1、obj2、obj3对象的k值?
1、C++类对象中的成员变量和成员函数是分开存储的。C语言中的内存四区模型仍然有效!
2、C++中类的普通成员函数都隐式包含一个指向当前对象的this指针。
3、静态成员函数、成员变量属于类
静态成员函数与普通成员函数的区别
静态成员函数不包含指向具体对象的指针
普通成员函数包含一个指向具体对象的指针
P197 this指针
#include <iostream> using namespace std; class Test { public: Test(int a, int b) //---> Test(Test *this, int a, int b) { this->a = a;//this就是t1取地址,谁调用它它就是谁 this->b = b; } void printT() { cout<<"a: " <<a <<endl; cout<< "b: " << this->b <<endl; } protected: private: int a; int b; }; void main() { Test t1(1, 2); t1.printT();// ===> printT(&t1) cout<<"hello..."<<endl; system("pause"); return ; }