Day02_C++编程

发布于:2025-07-25 ⋅ 阅读:(19) ⋅ 点赞:(0)

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

        


网站公告

今日签到

点亮在社区的每一天
去签到