01.思维导图
02.C++基础知识复习
1:c语言面向过程
怎么样添加cpp自动补全:
第一种方法:
#include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector> #include <memory> using namespace std; class Stu{ private: public: }; int main(int argc, const char** argv) { return 0; }
第二种方法:
vi ~/.vim/snippets/c.snippets
查看是否有丢包:
过程相当于一个函数:完成这个过程,需要哪些数据参与参与这个过程的这些数据,没有主题、比如:加法这个过程,我们会写这样的函数
int add(int a,int b){ return a + b; } int a = 1,b = 2; int res = add(a,b);
2:c++是面向对象的编程语言
什么是面向对象:其核心含义就是,以对象为导向的编程方式或者这么理解: 面相对象 = 围绕着某个特定的变量的编程方式 + 面相过程还是以加法函数为例
int a = 1,b = 2; int res = a.add(b) a 变量(在c++里面成为对象)本身,拥有很多很多方法,其中一个就叫做 add 的方法(函数) 在这个风格的代码里面,a是主题,add是a的方法,b是外部参与的过程的数据
c语言的字符串 char* str = "hello" char* ptr = "world" 赋值函数:strcpy(str,ptr) 比较函数:strcmp(str,ptr) 拼接函数:strcat(str,ptr) 在c++风格里面就会变成 str.copy(ptr); str.compare(ptr); str.append(ptr);
到底什么是面向对象:(具体含义)
01.在面向过程的时候,我们会将一个函数,写在全局区域,所有参与这个过程的数据,作为函数的参数入参。
02.在面向对象的时候,我们依旧是按照面向过程的方式,去写功能函数,只不过这些函数不再写在全局区域,而是为一个对象去写函数
03.将函数写成一个对象的方法 对比 将函数写成全局函数,有什么好处?
这个问题,暂时大家不要去深究,未来学了很多c++语法之后,大家自然就会发现将一个函数写成对象的方法之后,会有很多好处
3 c++中一些初步与c语言不同的地方
3.1 头文件
c++支持c语言风格的头文件 (XXX.h)
但是c++有自己风格的头文件 (XXXX)
早期c++为了兼容c语言风格和c++风格,将一部分c语言风格的头文件做了处理:去掉.h 前面加上 c
例如:stdio.h ==> cstdi,string.h ==> cstring,time.h ==> ctime
但凡在头文件里面看到c开头的头文件,都是c语言的头文件
3.2 标准输入输出
标准输出
cout << 123 cout << 123 << 'a' << "abc" << 3.14 << endl;
cout 使用 << 输入任意类型的数据,并且可以连续使用 << 运算符,连续输出数据
简单的解释一下 coutcout << 123 其中,cout 是一个变量,这个变量被声明在 std 里面 << 这个是cout的一个方法,该方法做的事情就是:输出 << 后面的内容 所以 cout << 123 总体解释:cout 对象调用 << 方法
标准输入
int a = 0; double b = 0; char c = 0; cin >> a; cin >> b; cin >> c; cin >> a >> b >> c; if(cin >> a){ cout << "cin吸收成功" } cin 吸收数据的表达式,可以直接放在if,for,while的()中进行成功判断
3.3 bool数据类型
c++中多出来了一种数据类型,叫做bool
这个数据类型只有2个值:true 或者 false
3.4 字符串类型
c++ 中的字符串类型为 string
string类型的操作要比c语言简单很多string str = "hello"; string ptr = "world"; string a = str; a = ptr; a = str + ptr a += ptr a == str a == (str + ptr)
3.5 struct 的区别
3.5.1 名称
struct Data{ }; c语言中: struct Data a;// 创建了一个Data结构体类型的 变量 a c++中 Data a; // 创建了一个 Data 类的 对象 a
3.5.2 存放数据不同
c语言,结构体中只能存放普通数据类型 struct Data{ int a; double b; char c; ... } c++类中,能存放 普通数据类型 + 静态变量 + 函数 struct Data{ int a; double b; char c; ... static int d; void func(){ } }
3.6 auto 关键字
c语言中 auto 关键字:将一个变量声明称自动变量,表明该变量生命周期自动管理:自动申请,自动释放
很少用,因为从很早起的c语言编译器开始,栈空间数据默认自动变量
所以c++中直接把auto关键字的作用给改掉了c++中auto关键字:成为了一种数据类型
自动推导变量的类型auto a = 3; auto b = 3.1 auto c = 'x'; auto d = "abc" 编译器会根据 = 右侧的真实数据类型,自动推导每个变量的类型 所以:a就是int,b是double,c是char,d是char* auto 也有缺点,但是不明显 auto 自动推导的类型也是有规范的: 整形只会自动推导成int,不会推导成long 或者short 浮点型只会推导成double,不会推导成float 字符串类型只会推导成 const char*,不会推导成 string
3.7 语法上隐藏的操作
c语言里面:c语言的所有操作,就是很直白的: = 就是赋值,+ 就是加法
在c++里面,因为语法的原因:导致c++里面很多时候 = 不仅仅是赋值4 关于:using namespace std;
这个叫做命名空间
命名空间的作用是:
将每个开发人员自己写的所有代码:包括函数声明定义,全局变量,类的自定义全都括在自己的命名空间里面,以避免代码合并的时候,出现重名问题。如何写一个命名空间
namespace 命名空间名{ 里面写函数声明、函数声明及定义、全局变量、类的自定义 }
命名空间中的函数如何调用
1:using namespace 命名空间名
在一个作用域中,写上该语句,造成的效果是:该作用域中的所有代码,会额外的去目标命名空间中寻找函数调用。
上述代码编译报错:因为using namespace zs 写在 main函数里面所以 main函数会先查询全局区域的函数寻找func,再额外的去 zs作用域中寻找func结果找到2个,编译报错
2 using 命名空间名::函数名、全局变量、类名
例如:using zs::func在写有这条语句的作用域中,指定,仅有名字叫做 func玩意,一定会去zs作用域中选取
3 命名空间名::函数调用、全变量名、类名
例如: zs::func()表示,仅在当前行,func函数一定去zs作用域中查询
在一通乱用命名空间的前提下,使用第三种方式是最稳妥的方式
zs::func() 一定会调用 zs命名空间中的func
ls::func() 一定会调用 ls 命名空间中的func
::func() 一定会调用全局的func4 关于命名空间中只声明函数,定义在外部的语法形式
当一个函数定义在命名空间外部的时候注意:该函数哪一部分属于命名空间的,就需要在该部分前面加上 命名空间::
func2 只有函数名属于命名空间的,所以定义在外部的时候,形式如下
void zs::func2(){ 定义 }
由于 返回值类型Data 是zs 命名空间中的数据类型,所以定在外部的时候,Data前面也是要加作用域运算符的
zs::Data zs::func2(){ 定义 }
由于函数的形参,以及函数定义体,和函数的函数名属于同一个作用域
所以只要函数名加上了作用于运算符之后,函数的形参部分和函数体部分,默认都是该作用域里面数据zs::Data zs::func(Data ot){ Data b; return b } 由于 形参 和 定义体默认都是 zs作用域的 所以形参和定义体里面的 Data数据类型,不需要在前面加上 zs::
关于命名空间重名的问题
命名空间重名,编译器会合并2个重名的命名空间,不会有语法问题
除非:2个同名的命名空间,出现了重名的 函数、全局变量名、类名#include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector> #include <memory> using namespace std; namespace zs{ void func(){ cout << "zs的func" << endl; } }; namespace ls{ void func(){ cout << "ls的func" << endl; } }; void func(){ cout << "全局func" << endl; } int main(int argc,const char** argv){ using namespace ls; using zs::func; using ls::func; zs::func(); ls::func(); ::func(); zs::func2(); return 0; }
2 面向对象之封装
1 什么是封装
01.让一个类(结构体) 的 对象(变量) 能做很多很多事情,拥有很多很多方法
02.换种说法:让一个类的对象:复杂化、功能强大化
03.拥有越多的函数,功能越强大04.封装本身就是围绕着类来进行的:就是在类里面写各种各样的函数
2 c++的类
不仅仅有 struct,还有class
struct 和 class 的区别在哪里在c++的类里面,拥有3种访问权限:
public:公开访问
即允许在类的外部,访问类里面的成员变量(也会叫做成员属性)
也允许在类的内部,访问类的成员属性
struct Stu{ private: int id; public: string name; void func(){ string name; name = "zs"; 此时如果是zs.func() 调用的函数,name就是 在 Stu作用域内部访问,称为内部访问 } } int main(){ Stu zs; zs.name = "zs"; 此时访问zs.name的时候,并不在 Stu作用域里面,所以本次访问的name属于外部访问 }
protected:受保护访问
只允许:内部访问
private:私有访问
只允许:内部访问
特别注意:目前来说:protected 和 private 没有区别
未来学了新的知识点之后,protected和private才有区别,一般来说我们现在也用不到 protected内部访问和外部访问
01.外部访问:既不在类的作用域中访问成员属性,并且在访问的时候,前面也没有加上 作用域:: 运算符,都属于外部访问
02.内部访问:和外部访问相反,只要在类的作用域里面,或者访问的时候,前面有 作用域::运算符,都属于内部访问
struct 和 class 的区别:
01.class 默认所有成员私有
02.struct 默认所有成员公开
03.class 不允许直接使用 {} 形式进行初始化 (如果想要使用{} 初始化,需要额外操作)private 和 public 具体使用
根据 c++ 标准委员会的要求(人为规定):
没有特殊需求的时候,所有的成员属性(变量),都放在private里面,所有成员方法(函数) 都放在public里面3 构造函数
在 c++ 中,无论是创建 class 对象,还是创建 struct对象,都会用到的一个函数
只要创建类的对象,就会自动调用的一个函数。Stu zs;// 无论Stu是class 还是 struct // 这条语句都会自动调用 Stu 类里面的一个叫做构造函数的玩意 // 所以,如果我们想要初始化 zs 里面的属性的,我们可以在Stu构造函数里面进行初始化
3.1 构造函数的特点
1:构造函数没有返回值类型
注意,不是说返回值类型是 void,而就是结结实实的没有返回值类型
2:构造函数的名字,必须和当前类的类名一模一样
3:构造函数允许出现多个,只要保证形参类型不一样
01.形参类型不一样:参数数量不一样,或者数量一样的情况下,参数的数据类型的排列组合不一样。
02.当有多个版本的构造函数的时候,到底调用的是哪个版本的构造函数,取决于调用构造函数的时候,传递了什么排列组合的实参。
4:我们已知创建一个类的对象的时候,一定会调用该类的构造函数。
01.那么,如果我们没写任何构造函数,是个什么情况?
02.当我们没有写任何构造函数的时候,编译器会为我们自动补全一个无参的什么都不干的构造函数。
03.但是,当我们一旦写了任意版本的构造函数之后,编译器将不再自动补全无参的什么都不做的构造函数。
3.2 工作函数的4种调用形式
以 class Stu{ string name; int id }; 为例:
1: Stu zs / Stu zs(参数)
总之就是:(参数) 不管有几个,直接跟在对象名后面
即: Stu zs(...)2: Stu zs = Stu() / Stu zs = Stu(参数)
3: Stu zs = 单个参数
注意,这种形式是绝对不支持调用多个参数版本的构造函数
4: Stu zs = {参数1,参数2,...,参数n}
隐式调用
以上4种构造函数调用形式,其中第3、4种,被称为 "隐式调用"
什么叫做隐式调用:让我们看不出来,这里调用了一个类的构造函数,例如:void func(Stu zs){ } 调用 func(10) 看不出来这里其实传了一个 Stu对象,使用第三种构造函数创建的对象 所以称为隐式调用
在特殊需求下,我们可能会要求所有函数入参,都必须是显示的,不能是隐式的我们就可以手动禁止该函数被隐式调用。
怎么禁止:在函数的声明前方,加上关键字 explicit
3.3 关于初始化 和 赋值
上述代码,运行的时候
3参构造函数,由37行 = 右侧的Score(m,c,e) 调用= 左侧的 score 是一个已经存在的对象,所以 score 无参构造函数,构建的就是 = 左侧的score问题就是:私有成员 score 到底在哪里构建的?
3.4 列表初始化
c++ 中的构造函数,分为2部分
01.内核部分 和 用户部分
02.用户部分:即{} 里面的内容,负责执行,用户自己写的自定义的代码
03.内核部分:申请该对象所需要的所有栈空间,这部分内容在哪呢?
04.在 ()与{}之间,我们称为 "列表初始化" 区域
05."列表初始化"区域:专门为当前对象申请栈空间,包括成员变量,成员对象的申请创建这部分区域,用户是可以人为干涉的
: 变量名1(初始值),变量名2(初始值),...,对象1(构造函数参数),对象2(构造函数参数),......
在列表初始化里面,()前面的一定是成员属性,()里面的就近原则
红框内的math一定是14行的成员属性math
蓝框内的 _math 就近原则,可能是成员属性,可能是参数,就看哪个离得近
4 对外接口
01.简称接口 : 专门用来访问私有成员的公开函数,就是对外接口
这种形式的好处在于:开发者对于私有私有成员拥有绝对控制权
开发者如果希望使用者能够访问某个私有成员,那么直接提供该私有成员的接口即可
如果开发者不希望某个私有成员被随便访问,该私有成员不写任何接口就好了对于访问私有成员这个事情,访问也分2种形式
读访问
写访问
02.所以,对于一个私有成员而言,需要2个对外接口
一个用于访问私有成员的值:读访问
一个用于修改私有成员的值:写访问
03.通常,我们将这2个接口称为 :set、get 接口(方法)4.1 关于 c++ const 关键字
char* a; const char* b; a = b;
在 c语言中, 一个 普通指针,允许指向 const 指针
即 a = b 这个操作 ,在c语言里面,最多给个警告但是在c++ 里面,绝对不允许一个普通指针,指向一个 const 指针即 在c++ 里面 a =b 直接报错
最终结论:
在写c++ get 接口的时候
如果发现当前类里面,存在一个指针,需要get的时候
务必将这个get接口的返回值类型,写成 const 指针类型,保证get出去的指针,不会被外部随意修改。
#include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector> #include <memory> using namespace std; class Stu{ private: string name; int id; public: Stu(){} Stu(string name,int id):name(name),id(id){} string getName(){return name;} void setName(string _name){ name = _name; } }; int main(int argc,const char** argv){ Stu zs("张三",1); zs.setName("zhangsan"); cout << zs.getName() << endl; return 0; }
#include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector> #include <memory> using namespace std; class Stu{ private: char name[16]; int id; public: Stu(){} Stu(char* _name,int id):id(id){ strcpy(name,_name); } const char* getName(){return name;} void setName(char* _name){ strcpy(name,_name); } }; int main(int argc,const char** argv){ Stu zs("张三",1); const char* name = zs.getName(); strcpy(name,"李四"); cout << zs.getName() << endl; return 0; }
封装一个mystring类, class mystring{ private: //char buf[256]; char* buf;// 指向一个堆空间,该堆空间用来保存外部传入的字符串 int len; // 记录 buf里面保存的字符串有多少 public: }; 要求能够实现以下功能 int main(){ mystring str = "hello"; // 这段代码要求一定为mystring类写一个可隐式调用的单参构造函数 mystring ptr = "world"; // 在写单参构造函数的时候,私有成员len使用列表初始化的形式初始化 str.copy(ptr); str.copy("你好"); str.append(ptr) str.append("你好") str.compare(ptr) str.compare("你好") str.show(); // 最后再写一个 show 函数,用来在终端输出 str 里面保存的字符串 cout << str.at(0) << endl;// 再写一个 at 函数,用来输出 str中第0个字符 }
5 c++ 中的this
01.this 是一个指针
02.this会一直出现在:所有对象调用成员函数的时候,成员函数中永远会存在一个this
03.this为什么会永远的出现在成员函数里面呢?
04.当一个对象调用成员函数的时候:编译器会默认的,在函数调用的最左边,多传入一个实参
该实参就是:调用成员函数的对象地址
既然编译器改造了函数调用,使其多了一个参数
自然编译器还要同步的改造该函数的声明部分,在参数列表的最左边多一个形参,用来接受调用该函数对象地址
而该用来接受对象地址的形参的名字,就叫做 this当函数内部,访问成员数据的时候,本质上都是通过this指针进行访问的,只不过平时this指针可以省略
注意:this指针的类型
this指针是一个 指针常量
如何为this指针加上const关键字,成为 const XXX* const this
原本this指针类型为 : XXX* const this
想要变成 const XXX* const this
只需要在函数声明的() 后面,直接加上const 即可
void Stu::show()const{ } 这里唯一的const 就是用来加在 this 指针前面的
#include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector> #include <memory> using namespace std; class Score{ int eng2; int math2; int chi2; public: Score(){ cout<<"score无参构造"<<endl; } Score(int m,int c,int e){ math2=m; chi2=c; eng2=e; } // 添加访问函数 int getMath() const { return math2; } int getChi() const { return chi2; } int getEng() const { return eng2; } }; class Stu{ char ch; string name; int id; private: public: int math; int chi; int eng; Score score; Stu() {} // 移除未实现的构造函数声明 // Stu(int _math,int chi,int eng); Stu(int _math, int _chi, int _eng, int m, int c, int e) // 修正参数使用,初始化 score 成员 : math(_math), chi(_chi), eng(_eng), score(m, c, e) { } }; int main(int argc, const char** argv) { Stu zs(100, 110, 120, 90, 85, 95); cout<<zs.math<<endl; cout<<zs.chi<<endl; cout<<zs.eng<<endl; // 打印 Score 成员的信息 cout << "Score 的数学成绩: " << zs.score.getMath() << endl; cout << "Score 的语文成绩: " << zs.score.getChi() << endl; cout << "Score 的英语成绩: " << zs.score.getEng() << endl; return 0; }
6析构函数
当一个对象,生命周期结束的时候,自动调用的函数也就是说:析构函数和构造函数功能相反构造函数在创建对象的时候自动调用,析构函数在对象销毁的时候自动调用
6.1析构函数特点
1:和构造函数一样,析构函数没有返回值类型
2:和构造函数一样,析构函数的函数名也必须和类名一模一样
但是为了区分该函数是析构函数而不是构造函数,并且为了表示析构函数和构造函数是相反的功能所有,要求在析构函数的名字前面加上 ~
3:当我们没有写析构函数的时候,编译器会自动补全一个什么都不干的析构函数
既然编译器会补全一个什么都不干的析构函数,我们为什么还要手写析构函数呢说明,我们希望在该对象销毁的时候,干点什么
干点什么呢?:
比如说:该类对象当中,存在一个指向堆空间的指针,该类对象在销毁的时候,是不会自动释放这个堆空间指针的,但是该类对象在销毁的时候,是会自动调用析构函数的
结合这2点:我们可以在析构函数里面,去释放堆空间指针
总结
当类里面存在一个不会自动销毁(释放)的数据的时候,这个时候就要求用户手写析构函数,并且在析构函数里面手动销毁这些数据4: 析构函数,作为一个类的成员函数,允许直接手动调用
一般来说,析构函数不会去手动调用,自动调用就行了
但是,存在一些特殊情况:比如因为信号的原因,导致程序异常中断,这个时候是自动调用对象的析构函数
如果这个时候,我们希望程序异常中断时候,仍旧能够释放所有资源
那么,我们应该捕获对应的信号,在信号处理的时候,手动调用析构函数
#include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector> #include <memory> using namespace std; class Stu{ private: int* p; FILE* fp; public: Stu(){ cout<<"Stu 无参构造函数"<<endl; p=(int*)calloc(1,4); fp=fopen("./text.txt","w"); } ~Stu(){ cout<<"Stu析构函数"<<endl; fclose(fp); } }; int main(int argc, const char** argv) { Stu zs; return 0; }
7 引用
这是c++中出现的一种新的数据类型其功能和地位类似于 c 语言中的指针
引用的写法如下:
int a = 10; int& pa = a; 所以引用的语法就是: 数据类型& 引用变量名 = 被引用的数据
引用有什么用
一旦引用成功,引用变量,就是被引用的变量本身,或者说是别名也就是说:
int a = 10; int& pa = a; pa 即是 a,a 即是 pa
引用的特点
1:一个常量,必须使用常量引用去绑定,不能使用普通引用绑定
一个变量,既可以使用普通引用,也可以使用常量引用进行绑定
2:引用仅仅在创建并初始化的时候,才能绑定对象
之后所有的 = 行为,都是为被绑定的数据赋值,而不是更换绑定
3:因为引用创建完成之后,就不能更换绑定了
所以,引用必须初始化,注意:如果类里面存在一个引用,该类的构造函数必须手写,并且在列表初始化区域,初始化该引用的绑定对象。
4:和指针类似
一个普通引用不能绑定常量引用
特别注意:
引用和指针的区别
1:一个普通指针,既可以指向常量,也可以指向变量
一个常量指针,同样的,既可以指向常量,也可以指向变量
一个普通引用,只能绑定变量,不能绑定常量
一个常量引用,既可以绑定变量,又可以绑定常量
2:所有引用都不允许更改换绑定
但是指针不同:除了指针常量以外,所有指针都可以更换指向
3:引用必须初始化
指针的初始化不是必须的
4:引用是变量的别名,变量本身
指针,是一个独立的变量,该变量里面存放了目标的地址导致了
4.1 &引用 == &变量
&指针 != &变量
4.2 sizeof(引用) == sizeo(变量) == 变量本身大小
sizeof(指针) == 4 或者 8
4.3 想要改变变量本身的值,直接改变引用的值就可以了
但是指针不行,必须先对指针取值(也叫 解引用) 才能改变变量的值
数组和函数的引用
语法上和数组指针、函数指针非常类似
int func(int a,char b,double c){ cout << "func" << endl; } void func3(int(& arr)[5]){ } int(& q)(int,char,double) = func; q(1,'x',3.14); int arr[5] = {1,2,3,4,5}; int(& p4)[5] = arr;
数组引用很少用:因为设计函数的时候,如果形参是一个数组的引用,要求明确该数组的容量是多少,这就导致该形参只能接受相同容量,相同类型的数组,别的容量的相同类型或者相同容量别的类型的数组一概传不进来。
8 拷贝构造函数
01.构造函数,属于多个构造函数版本其中的一个版本
02.构造函数的作用是:
提供一个外部数据(不提供也行), 来构建一个新的对象,并且以外部数据为依据初始化新的对象。
03.拷贝构造函数的作用是:
提供一个已经存在的对象,来构建一个新的对象,并且以这个已经存在的对象为依据,初始化新的对象。
拷贝构造函数的特点
1:由于是构造函数的缘故,构造函数有什么特点,拷贝构造函数都有
2:无论是否手写了构造函数,只要没有手写拷贝构造函数,编译器都会自动补全一个拷贝构造函数
3:拷贝构造函数,由于其特殊型,在特殊的情况下,会被编译器优化掉
编译器的优化规则:很复杂
优化手段:很复杂
但是在实际编程过程当中,我们不需要去考虑编译器是否会优化拷贝构造函数规则:我们只要尽可能的保证:少发生拷贝构造函数即可
01.传参的时候,一定给我传引用
02.如果函数内部不会涉及形参的改变,直接传const &
03.如果函数内部会涉及形参的改变,就只能传引用
04.返回值的时候:返回值类型能够引用就引用,不能引用就返回普通对象
05.一般来说,返回值是一个局部对象的时候,返回值类型不允许是 &
06.其他时候,返回值类型都可是 &拷贝构造函数的调用时机:
1:使用一个已经存在的对象去构建并初始化一个新的对象的时候
2:当函数的参数是一个对象的时候
3:当函数的返回值是一个对象的时候关于编译器默认补全的拷贝构造函数
编译器自动补全的拷贝构造函数,是拥有拷贝功能的怎么拷贝的呢
class Stu{ private: public: int math; int chinese; int eng; Stu(){} Stu(int m,int c,int e):math(m),chinese(c),eng(e){} // 编译器默认补全的拷贝构造函数,源代码如下 Stu(const Stu& r){ memcpy(this,&r,sizeof(r)); // 相当于 // math = r.math // chinese = r.chinese; // eng = r.eng; // XXX = r.XXX } }; int main(int argc,const char** argv){ Stu zs(100,120,90); Stu ls = zs; // 拷贝构造 return 0; }
编译器默认补全的拷贝构造,会将对象内所有属性,一个一个的进行 = 赋值
所以问题来了:既然编译器补全的拷贝构造,拥有完整的拷贝功能,我们为什么还要手写拷贝构造函数呢?因为浅拷贝的原因
01.因为:编译器默认补全的拷贝构造函数,会将所有属性进行 = 赋值
02.当这些属性里面,存在指针的时候,就不太对劲了
03.指针的 = 赋值,会造成让2个指针属性,指向同一段地址
04.这就导致,任意一个对象修改地址上的数据的时候,另外一个对象,也会收到影响
05.这样子的拷贝,我们称为 "浅拷贝"
06.如果想要避免这种事情的发生,我们就需要自己手写拷贝构造函数
07.写成所谓的 "深拷贝形式"
什么是深拷贝?
在发生拷贝构造的时候
指针属性,并不会直接进行 = 赋值,而是
1:让新对象的指针指向独立的堆空间
2:拷贝地址上的数据
9 c++ 中的 static 关键字
先复习一下c语言中的static关键字的作用
01.延长生命周期:静态局部变量
02.限制作用域:静态全局变量static 关键字,无论在c语言还是在c++中,其功能只有1个
将一个变量,声明在静态存储区中,并且标记其作用域 而静态存储区中的变量,就要遵循静态存储区的规则,静态存储区有一个非常重要的规则: 当静态存储区相同作用域中,声明同名变量的时候,并不会报错,也不会重复声明,而是直接引用上次声明的那个变量
c++ 中的static用在哪里
1:类里面的成员属性
类里面的同一个静态成员属性,在该类的所有对象之间共享
注意:
01.静态成员属性,并不会在构造函数中被声明以及初始化
02.所以,我们要想办法手动声明并初始化静态成员属性
03.声明方式为:在main上面 数据类型 类名::静态成员属性名 = 初始值class Bank{ private: static double rate;// 全局::Bank:: public: }; // 全局::Bank:: 注意这里是声明并初始化,不是访问,所以无关 private 还是 public 的问题 double Bank::rate = 0;