当前位置: 首页 > news >正文

Day02_C++编程

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 使用 << 输入任意类型的数据,并且可以连续使用 << 运算符,连续输出数据
简单的解释一下 cout

cout << 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结构体类型的 变量 ac++中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() 一定会调用全局的func

4    关于命名空间中只声明函数,定义在外部的语法形式

当一个函数定义在命名空间外部的时候注意:该函数哪一部分属于命名空间的,就需要在该部分前面加上 命名空间::

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;

        

http://www.dtcms.com/a/295840.html

相关文章:

  • 基于U-Net的结冰检测系统实现
  • C11补充
  • SGLang + 分布式推理部署DeepSeek671B满血版
  • 数据结构-5(二叉树)
  • pytorch-geometric包(torch_scatter、torch_sparse、torch_cluster)
  • 服务器带宽具体是指什么意思?
  • PyTorch中神经网络的模型构建
  • 钉钉DingTalk完整版下载离线安装包2025
  • 【小董谈前端】【样式】 CSS与样式库:从实现工具到设计思维的跨越
  • ThinkPHP8集成RabbitMQ的完整案例实现
  • C# 方法执行超时策略
  • [Python] -进阶理解5- Python 模块与包的导入机制解析
  • uniapp中mp-html使用方法
  • 特定日志输出aop实现
  • day62-可观测性建设-全链路监控zabbix+grafana
  • Redis的事务和Lua之间的区别
  • day13 flash
  • 「iOS」黑魔法——方法交换
  • 告别束缚:这款“隐形心电监测仪”让心脏健康管理更自由
  • JavaSE:开发环境的搭建(Eclipse)
  • 企业级数据分析创新实战:基于表格交互与智能分析的双引擎架构
  • 从0到1学习c++ 命名空间
  • 《 java 随想录》| 数组
  • MySQL的命令行客户端
  • 探索双链表:C语言中的链式结构魔法
  • 光谱仪杂散光性能分析
  • 大疆无人机炸机后视频损坏的完美修复案例解析
  • uni-file-picker vue3二次封装(本地上传 + v-model)
  • Mysql命令show processlist
  • Linux基础服务(autofs和Samba)