c++的构造函数

目录

构造函数

1.构造函数: 

2.构造函数的特点: 

默认构造函数 -- 没有参数的构造函数

1. 合成(自动)的默认构造函数(一般不常用) 

1) 介绍,以及为什么不使用

 2)可以使用合成默认构造函数的情况

2. 自定义的默认构造函数 

 1)介绍

 有参构造函数 -- 带参数的构造函数

 1. 自定义有参构造函数

 1) 简述

 2)  使用有参构造函数实例化对象

3)有参构造函数定义时需要注意:  

1. 使用的参数名字和类内的属性名字不同

2. 使用的构造函数参数的名字和类内的属性名字一样 

3. 解决办法:  使用this指针 :指向当前对象(可以理解为调用函数的这个对象)

拷贝构造函数 

1. 合成的拷贝构造函数(有很大风险) -- 浅拷贝

1)简述 -- 浅拷贝

 2)合成拷贝构造函数具有很大的风险(浅拷贝的风险)

2. 自定义的拷贝构造函数 -- 深拷贝

 1)简述 -- 深拷贝

2)使用深拷贝来解决浅拷贝的问题 

3)浅拷贝和深拷贝 

4)拷贝构造函数的调用时机 

返回过程: 

5) 拷贝构造函数注意事项

赋值构造函数

合成的赋值构造函数

1)简述

2) 代码

3)合成赋值构造函数也存在问题 -- 也是浅拷贝 

自定义的赋值构造函数 

1)含义 

 2)代码

3)赋值构造函数的写法 

总结: 

构造函数

构造函数是c++类中的概念,用来给对象的属性进行初始化和一些其它的操作(相当于创造一个东西时,赋予它一些特性)。 

1.构造函数: 

其实从字面意思来理解,就是创造一个东西的函数,对于类来说这个东西就是对象。 

所以,c++语法规定,在我们实例化一个类对象的时候,都会默认调用构造函数。

2.构造函数的特点: 

1) 创建对象时自己调用 

2)    没有返回值,函数名和类名一样

3)  可以利用函数的重载,写多个构造函数。 

 

3. 注意:

构造函数必须设置为公有,因为我们实例化对象在类外,所以构造函数必须能够被外界使用。  

默认构造函数 -- 没有参数的构造函数

1. 合成(自动)的默认构造函数(一般不常用) 

1) 介绍,以及为什么不使用

我们上面说了,c++的语法规则:实例化一个对象就会自动调用构造函数。 

但是如果我们没有在类中写构造函数呢?  那么编译器会默认给我们生成一个合成的默认构造函数。这个构造函数其实内部什么也没有,就是一个空函数。   那没什么要有它呢?-- 也许为了语法统一。

但是,使用合成的默认构造函数的风险很高。   看代码:

class Human {
public:int getAge();void visit();private:int age;
};int Human::getAge() {return this->age;
}void Human::visit() {cout << getAge() << endl;
}int main(void) {Human man; // 创建man对象man.visit(); // 使用visit函数来打印age的值system("pause");return 0;
}

 上面代码,我们创建了一个对象,并且我们没有在类中实现构造函数,这是编译器会自己创建一个构造函数(空实现)。

我们通过visit()打印通过对外接口getAge()获得的age的值。-- 看上面的结果,是一个很大的负值。这中情况很常见 -- 没有赋值直接使用。

为什么会是上面的结果呢?  

因为,对象man中的属性age,没有赋值,在实例化的时候,调用的也是编译器自己创建的默认构造函数,也是空实现,不会对age进行赋值,所以age在使用的时候是没有赋值的。所以会出现问题。

这就是为什么不使用它的原因了。 

 2)可以使用合成默认构造函数的情况

有一种情况,我们是可以使用默认构造函数的。

class Human {
public:int getAge();void visit();private:int age = 18;  // 仅c++11之后可以
};

比如代码中,我们在类内定义的age变量,定义的时候已经初始化了。这时,即使再输出age也不会出现上面的情况(构造函数不赋值的情况下)。

  正常输出。 

所以,当我们类内的数据在定义的时候全都已经初始化了(必须全部初始化,因为有不初始化的就存在风险),就可以使用合成默认构造函数了。 

注意: 只有c++11之后才支持在类内定义数据时,进行初始化。

2. 自定义的默认构造函数 

 1)介绍

上面说到,我们一般都不会使用合成默认构造函数,我们一般都会自己定义一个构造函数。 

在自定义的构造函数中我们就可以对相应的数据进行初始化了。 

class Human {
public:int getAge();void visit();Human();private:int age;
};int Human::getAge() {return this->age;
}Human::Human() {age = 18;
}void Human::visit() {cout << getAge() << endl;
}int main(void) {Human man; // 创建man对象man.visit(); // 使用visit函数来打印age的值system("pause");return 0;
}

上面代码,就定义了一个默认构造函数,Human(),我们在实现构造函数中,将age属性进行了初始化。 

在自定义的构造函数中我们可以根据自己的需要进行设置,将相应的属性进行初始化。

注意:  当我们自己定义了构造函数的时候,编译器就不会给我们提供了。

 有参构造函数 -- 带参数的构造函数

 1. 自定义有参构造函数

 1) 简述

前面提到构造函数可以有多个,带参数的构造函数是我们自己写的。 前面写的默认构造函数没有参数的。

有参构造函数:就是带参数的构造函数,所以它的书写规则和无参构造很类似,就是带了个参数。 

class Human {
public:Human(int age, string name);int getAge();string getName();void visit();
private:int age;string name;
};int Human::getAge() {return this->age;
}string Human::getName() {return name;
}void Human::visit(){cout << getAge() << endl;cout << getName() << endl;
}Human::Human(int age, string name) {this->age = age; // 如果名字相同使用this指针,或者可以将参数的名字设置成不一样的this->name = name;
}int main(void) {Human man1(18, "男神");man1.visit();system("pause");return 0;
}

代码中:定义了一个有参构造函数:Human(int age,string name) ;

 2)  使用有参构造函数实例化对象

代码中有参构造有两个参数,用来接收用户用来初始化属性的值。 

所以在使用有参构造函数创建对象的时候,需要传入参数。Human man1(18,"男神"); 就是调用有参构造函数进行创建对象。 

3)有参构造函数定义时需要注意:  

1. 使用的参数名字和类内的属性名字不同
class Human {
public:// 有参构造函数Human(int age1, string name1);
private:int age;string name;
};Human::Human(int age1, string name1) {age = age1;name = name1;
}

当有参构造函数的参数名字和类内属性名,名字不一样的时候,可以直接进行赋值。 

2. 使用的构造函数参数的名字和类内的属性名字一样 
class Human {
public:// 有参构造函数Human(int age, string name);
private:int age;string name;
};Human::Human(int age, string name) {age = age;  // 此时这两个age都表示的是参数,所以不正确。name类似name = name;
}

当参数的名字和属性名字相同的时候,就不能直接赋值了。 

age = age; // 此时这两个age都表示的是形参里面的age,并不是类内属性的age,所以不对。 

3. 解决办法:  使用this指针 :指向当前对象(可以理解为调用函数的这个对象)
Human::Human(int age, string name) {this->age = age; this->name = name;
}

使用this之后,就可以了。 因为this指向当前对象,所以this->age就是指当前对象的age属性。

拷贝构造函数 

拷贝构造函数:在一个对象初始化的时候,给它赋值另外一个对象,这是就会默认调用拷贝构造函数。

Human man2 = man1; 

1. 合成的拷贝构造函数(有很大风险) -- 浅拷贝

1)简述 -- 浅拷贝

合成的拷贝构造函数就是:当自己没有实现拷贝构造函数的时候,编译器会自动生成一个合成拷贝构造函数。

 拷贝构造函数会将man1中属性的值拷贝到man2的属性中。

 2)合成拷贝构造函数具有很大的风险(浅拷贝的风险)

        浅拷贝:就是只将属性的值,拷贝到另外一个对象。

         这样看,浅拷贝好像没有什么问题。如果使用普通的变量,没有什么问题,但是如果属性中指针,那么就会出现问题。

举个例子: 

int age;
string name;
char *a;man1 :  age=12,name="帅哥",*a = 'a'Human man2 = man1;

将man1中的值拷贝到man2中, 我们没有定义拷贝构造函数,所以编译器自动生成,进行浅拷贝:  将man1 中 age 的值复制给 man2中 age,name也同理,

重点来看指针的拷贝:浅拷贝只是将指针变量a中存放的地址拷贝到man2的a中,也就是说进行浅拷贝之后,man1中的a和man2中的a指向同一片内存,同一块数据。 

可能会疑问问什么不能指向同一个呢? -- 很简单,man1 和 man2两个对象毫不相关,如果这个指针用来表示的是资产,那你说,两个人的资产怎么能放到一起呢。

所以这样用一定会出问题,会有很大的风险。 

2. 自定义的拷贝构造函数 -- 深拷贝

 1)简述 -- 深拷贝

前面说到,系统自动提供的拷贝构造函数只能进行浅拷贝,会造成很大的风险。 

那怎么办呢?--  那就自己实现一个呗,然后使用深拷贝来解决这个问题 

2)使用深拷贝来解决浅拷贝的问题 

 浅拷贝的问题是: 对指针进行拷贝时,不会开辟新空间,直接将新对象的指针指向别的对象的内存,这就会造成很大的风险。

问:那深拷贝如何解决这个问题呢? 

我们可以在自己实现的拷贝构造函数中,给新对象的指针开辟一片空间,来存放其他对象指针中的值 -- 这样就不会共用同一片空间了。

class Human {
public:Human();Human(const Human&); // 函数声明可以不写参数名称
private:int age;string name;char* f1;
};Human::Human() {age = 20;name = "帅哥";f1 = new char[10];strcpy_s(f1,10,"好好学习");  // 使用字符串拷贝给字符串进行赋值
}Human::Human(const Human& man) {age = man.age;name = man.name;f1 = new char[10];strcpy_s(f1, 10, man.f1);  // 将man中f1指向内存的值拷贝到此对象的f1中
}int main(void) {Human man1;Human man2 = man1;   // 在对象初始化时赋值给另外一个对象,则会默认调用拷贝构造函数system("pause");return 0;
}

Human(const Human&); 就是我们自定义的拷贝构造函数声明。

在函数实现的过程中, 我们又为新对象的f1指针创建了一片内存,用来存放其它对象的值。

3)浅拷贝和深拷贝 

浅拷贝:系统自己生成的拷贝构造函数,只是将变量中的值浅浅的复制过去,不管你是不是有风险, 比如:对于指针变量,只是将指针变量存放的地址复制过去,使它指向了同一片空间。

深拷贝: 为了解决浅拷贝带来的风险,我们需要自己实现拷贝构造函数,为新对象的指针开辟空间,将别的对象指向的内容复制过去,而不是简单的将指针中存储的地址复制过去。

4)拷贝构造函数的调用时机 

1. 当函数参数不是引用(值传递),参数类型是我们定义的类的时候 

void test(Human man) {   // 传参数的过程就是 Human man = man1 这不就是拷贝构造// 测试语句
}int main(void) {Human man1;test(man1);system("pause");return 0;
}

2. 返回值为我们定义的类 

Human test(Human& man) {// 测试语句return man;
}int main(void) {Human man1;test(man1);system("pause");return 0;
}
返回过程: 

其实就是创建一片临时空间来存放man的值,然后返回给主调函数。  这个过程其实和Human man1 = man;是类似的,只是此处我们不知道变量名称。

3. 初始化对象时,直接使用另外一个对象初始化,使用=或者()都可以。都会自动调用拷贝构造函数。 

int main(void) {Human man1;Human man2 = man1;   // 在对象初始化时赋值给另外一个对象,则会默认调用拷贝构造函数Human man3(man1);system("pause");return 0;
}

4. 使用对象数组初始化的时候 

int main(void) {Human man1;Human man2 = man1;  Human man3(man1);Human man4[3] = { man1,man2,man3};system("pause");return 0;
}

其实就是定义了一个数组,数组中的每个元素都是Human类型,所以其实就相当于: 

Human man4[0] = man1;        Human man4[1] = man2;       Human man4[2] = man3;  自动调用拷贝构造函数。 

5) 拷贝构造函数注意事项

      1. 拷贝构造函数的参数类型必须是: const Human& man 这种 

      2.  拷贝构造函数的参数只能有一个,因为初始化的时候,只能一个对象作为右值。 

      3. 由于2.,所有拷贝构造函数只能有一个,因为它无法进行函数重载,因为它只能有一个参数,并且参数的类型和数量都是固定的。 (有参构造可以重载,因为参数个数个类型不受限制)

赋值构造函数

合成的赋值构造函数

1)简述

赋值构造函数和其他的构造函数不同(具体看实现)。  合成赋值构造函数也是系统自己提供

2) 代码
int main(void) {Human man1,man2;man2 = man1;  // 在不初始化时,进行赋值,调用赋值构造函数system("pause");return 0;
}

当我们使用=将一个对象赋值给另外一个对象的时候,系统会自动调用赋值构造函数。(注意:不是初始化的时候,初始化时调用的是拷贝构造函数) 

3)合成赋值构造函数也存在问题 -- 也是浅拷贝 

还是浅拷贝,当属性中有指针时,虽然两个对象的指针都指向各自的内存,但是合成赋值构造函数只是进行简单的赋值,对指针进行赋值时,只是将指针变量的值赋值给新对象的指针,这样又会导致两个对象的指针直系那个同一片内存。(浅拷贝)  

自定义的赋值构造函数 

1)含义 

和上面一样,既然浅拷贝有问题,那么我们就自定义一个赋值构造函数来实现深拷贝。 

 2)代码
class Human {
public:Human();Human& operator=(const Human& man);  // 赋值构造函数 -- =运算符重载
private:int age;string name;char* f1;
};Human::Human() {age = 20;name = "帅哥";f1 = new char[10];strcpy_s(f1,10,"好好学习");  // 使用字符串拷贝给字符串进行赋值
}Human& Human::operator=(const Human& man) {age = man.age;name = man.name;// 深拷贝strcpy_s(f1, 10, man.f1);return *this; // 返回此对象
}int main(void) {Human man1,man2;man2 = man1;  // 在不初始化时,进行赋值,调用赋值构造函数system("pause");return 0;
}

问:为什么此处的深拷贝不需要开辟新空间? 

此处的深拷贝,已经不需要开辟空间了,因为我们在实例化对象的时候 ,已经在默认构造函数中,对每个对象的f1指针都开辟了单独的空间。所以,不需要再开辟空间了。

我们只需要将man1中的f1内存中的值拷贝到man2的f1指向的内存即可。(而不是,将f1中的地址赋值过去)。 

3)赋值构造函数的写法 

     1. 首先赋值构造函数就是一个=的重载函数 

     2. 参数传入的是作为=右值的对象,而调用函数的是=左值的对象。(this指针指向当前对象 ) 

     3.  返回Human&, 是用来返回调用函数的对象的。this指向调用函数的当前对象,*this就是此对象。

     4. 3.的原因:   为了能够实现连续赋值的情况  --  man  = man1  = man2;  

         man1 = ma2; 调用赋值构造函数,返回man1, 再进行 man = man1。 

总结: 

综合上面的看,构造函数其实就是,当我们对对象进行相应的操作时,系统会自动去调用的函数。为了构造当前对象,去调用函数。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/610481.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

xss-labs(6-9)

level6:欢迎来到level6 老规矩还是先看看输入框的闭合情况 尝试事件函数绕过 test" onclick="alert(欢迎来钓鱼) 既然事件函数被转义了,那就使用我们第二关用过的绕过方法插入标签看看 test"><script>alert(欢迎来钓鱼)</script>// <

新书速览|循序渐进Vue.js 3.x前端开发实战

Vue.js初学者和前端开发人员使用&#xff0c;网课、培训机构与大中专院校的教学用书 作者简介 张益珲 美国亚利桑那州立大学计算机工程技术硕士&#xff0c;架构师&#xff0c;从业近10年&#xff0c;多年大前端开发经验&#xff0c;曾就职于知名上市公司&#xff0c;主导开发…

算法训练营Day42(背包问题)

基础 非竞赛只需要搞懂0-1背包和完全背包 0-1背包基础 0-1背包是完全背包和多重背包的基础 n个物品&#xff0c;每个物品一个&#xff0c;每个物品有自己的重量和价值&#xff0c;&#xff0c;一个背包能装m物品&#xff0c;问最多装多少物品。 暴力解法&#xff0c;n个物品…

SpringMVC 的入门

SpringMVC 的入门 1环境搭建 1.1.创建工程 1.2.添加web支持 右键项目选择Add framework support... 2.添加web支持 ​ 3.效果 注意&#xff1a; 不要先添加打包方式将web目录要拖拽到main目录下&#xff0c;并改名为webapp 1.3.pom.xml <?xml version"1.0&q…

鱼哥赠书活动第⑥期:《内网渗透实战攻略》看完这本书教你玩转内网渗透测试成为实战高手!!!!

鱼哥赠书活动第⑥期&#xff1a;《内网渗透实战攻略》 如何阅读本书&#xff1a;本书章节介绍&#xff1a;本书大致目录&#xff1a;适合阅读对象&#xff1a;赠书抽奖规则:往期赠书福利&#xff1a; 当今&#xff0c;网络系统面临着越来越严峻的安全挑战。在众多的安全挑战中&…

7双指针问题-接雨水2

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表…

控制注塑机PQ比例阀放大器

控制不带电反馈的单或双比例电磁铁的博世力士乐&#xff08;Bosch Rexroth&#xff09;、伊顿威格士&#xff08;EATON Vickers&#xff09;、油研&#xff08;YUKEN&#xff09;、贺德克&#xff08;HYDAC&#xff09;、大金&#xff08;DAIKIN&#xff09;、不二越&#xff0…

网安入门13-文件上传(htaccess,其他绕过)

空格绕过&#xff0c;点号绕过 Pass-07 直接上传肯定是失败的 把文件名1.php改成1.php.或1.php_(下划线为空格)&#xff0c;这种命名方式在windows系统里是不被允许的&#xff0c;所以需要在burp之类里进行修改&#xff0c;然后绕过验证后&#xff0c;会被windows系统自动去掉…

三维猴打印PCB外壳预留板壳间距

3D文件下单那里有一个“3D模型设计规范”&#xff0c;里面详细讲了设计时要考虑打印的参数细节。如果有其他的设计规范不了解的也可以进去查看&#xff0c;里面写的很详细。 这里是打印PCB外壳预留板壳间距相关说明&#xff1a; 设计模型为装配体&#xff0c;请务必满足装配最…

什么是springmvc(介绍)

什么是springmvc 1. 什么是springmvc2.项目中加入springmvc支持2.1 导入依赖2.2 springMVC配置文件2.3 web.xml配置2.4 中文编码处理 3. 编写一个简单的controller4. 视图层配置4.1 视图解析器配置4.2 静态资源配置4.2 编写页面4.3 页面跳转方式 5. SpringMVC处理请求的流程6. …

纯血鸿蒙「扩圈」100天,酝酿已久的突围

坦白讲&#xff0c;去年参加华为开发者大会看到HarmonyOS NEXT&#xff08;仅运行鸿蒙原生应用&#xff0c;所以也称作「纯血鸿蒙」&#xff09;的时候&#xff0c;小雷也没料想到鸿蒙原生应用生态的发展速度会如此之快。 9月25日&#xff0c;华为正式对外宣布启动HarmonyOS NE…

【C语言】指针——从底层原理到应用

C语言指针-从底层原理到花式技巧&#xff0c;用图文和代码帮你讲解透彻 目录 一、前言二、变量与指针的本质 1. 内存地址2. 32位与64位系统3. 变量4. 指针变量5. 操作指针变量 5.1 指针变量自身的值5.2 获取指针变量所指向的数据5.3 以什么样的数据类型来使用/解释指针变量所指…

Python学习笔记-使用Anaconda+VSCode配置开发环境

文章目录 概述一、安装Anaconda1.1 下载软件1.2 安装anaconda1.3 配置环境 二、配置虚拟环境2.1 使用conda创建一个新的虚拟环境2.1.1 使用search指令查看支持的python的版本&#xff1a;2.1.2 使用create创建指定版本的虚拟环境&#xff1a;2.1.3 使用env list查看虚拟环境列表…

BigDecimal使用记录

在公司经费这块用到了BigDecimal类&#xff0c;特此整理记录一下。 一、BigDecimal简介&#xff1a; float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算&#xff0c;这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而&a…

vulhub中的Apache HTTPD 多后缀解析漏洞详解

Apache HTTPD 多后缀解析漏洞 1.查看python版本 这里python版本很重要&#xff0c;因为版本过低可能会导致后面的结果运行不成功 这里我就遇到了因为版本过低而执行不了docker-compose up -d的情况 查看python版本 cd /usr/bin ls -al python* 当版本过低时安装高版本的 …

_Incapsula_Resource与Rc4混淆分析

一、获得混淆js 这么一个地址 https://www.interasia.cc/_Incapsula_Resource?SWJIYLWA5074a744e2e3d891814e9a2dace20bd4,719d34d31c8e3a6e6fffd425f7e032f3 浏览器打开这个地址 复制这个js&#xff0c;到浏览器调试 先格式化查看&#xff0c;也就是一个eval函数执行b函数 …

base64 图片进行编码、解码;api调用

1、base64 图片进行编码、解码 编码 import base64# 假设您有一个图像文件&#xff0c;例如 image.jpg with open(r"C:\Users\l****1686722996428308480-1 (1).jpg", rb) as image_file:# 读取图像文件的二进制数据image_data image_file.read()# 将二进制数据编码…

C语言基础语法跟练

题源&#xff1a;牛客网 1、输出"Hello Nowcoder!"。开始你的编程之旅吧。 #include <stdio.h>int main() {printf("Hello Nowcoder!");return 0; } 2、KiKi学会了printf在屏幕输出信息&#xff0c;他想输出一架小飞机。请帮他编写程序输出这架小…

超实用的 Python 库之lxml使用详解

概要 XML&#xff08;可扩展标记语言&#xff09;和HTML&#xff08;超文本标记语言&#xff09;是广泛用于数据交换和网页构建的标记语言。在Python中&#xff0c;有许多库可以用来解析和处理XML和HTML文档&#xff0c;其中最强大和常用的之一是lxml。lxml是一个高性能、功能…

回归预测 | Matlab实现DE-BP差分算法优化BP神经网络多变量回归预测

回归预测 | Matlab实现DE-BP差分算法优化BP神经网络多变量回归预测 目录 回归预测 | Matlab实现DE-BP差分算法优化BP神经网络多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现DE-BP差分算法优化BP神经网络多变量回归预测&#xff08;完整源码和…