C++面向对象程序设计-北京大学-郭炜【课程笔记(三)】
C++面向对象程序设计-北京大学-郭炜【课程笔记(三)】
- 1、构造函数(constructor)
- 1.1、基本概念
- 2、赋值构造函数
- 2.1、基本概念
- 2.1、复制构造函数起作用的三种情况
- 2.2、常引用参数的使用
- 3、类型转换构造函数
- 3.1、什么事类型转换构造函数
- 4、析构函数
- 4.1、什么是析构函数
- 4.2、析构函数和数组
- 4.3、析构函数和运算符 delete
- 5、构造函数析构函数调用时机
开始课程:P7 2_2. 构造函数
课程链接:程序设计与算法(三)C++面向对象程序设计 北京大学 郭炜
课程PPT:github提供的对应课程PPT
1、构造函数(constructor)
1.1、基本概念
1、成员函数的一种
- 名字与类名相同,可以有参数,不能有返回值(void 也不行)
- 作用是对对象进行初始化,如给成员变量赋初值
- 如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数
- 默认构造函数无参数,不做任何操作
- 如果定义了构造韩素,则编译器不生成默认的无参数的构造函数
对象生成时,构造函数自动调用。对象一旦生成,就再也不能在其上执行构造函数
- 一个类可以有多个构造函数
2、为什么需要构造函数
- 构造函数执行必要的初始化工作,有了构造函数,就不必再写初始化函数,也不用担心忘记调用初始化函数。
- 有时对象没被初始化就使用,会导致程序出错。
例1:
// 类中没有写构造函数
class Complex{private:double real, imag;public:void Set(double r, double i);
}; // 编译器自动生成默认构造函数Complex c1; // 默认构造函数被调用
Complex * pc = new Complex; // 默认构造函数被调用
例2:
class Complex{private:double real, imag;pubilc:Complex(double r, double i = 0); // 构造函数
}
Complex::Complex(double r, double i){real = r; imag = i;
}Complex c1; //error,缺少构造函数的参数
Complex * pc = new Complex; // error,没有参数
Complex c1(2); // OK
Complex c1(2,4), c2(3,5);
Complex * pc = new Complex(3,4);
例3:可以有多个构造函数,参数个数或类型不同
class Complex{private:double real, imag;pubilc:// 函数重载Complex(double r, double i = 0); // 构造函数Complex(double r, double i);Complex(double r);Complex(Complex c1, Complex c2);
}
Complex::Complex(double r){real = r; imag = 0;
}
Complex::Complex(double r, double i){real = r; imag = i;
}
Complex::Complex(Complex c1, Complex c2){real = c1.real + c2.real;imag = c1.imag + c2.imag;
}// 构造函数初始化
Complex c1(3), c2(1,0), c3(c1,c2);
// c1 = {3, 0}, c2 = {1, 0}, c3 = {4, 0};
例4-1:构造函数在数组中的使用
#include<iostream>class CSample
{int x;public:CSample(){std::cout << "Constructor 1 Called" << std::endl;}CSample(int n){x = n;std::cout << "x = " << x << std::endl;std::cout << "Constructor 2 Called" << std::endl;std::cout << "====================" << std::endl;}
};int main()
{CSample array1[2]; // 无参数构造函数会被调用两次std::cout << "step1" << std::endl;CSample array2[2] = {4, 5};std::cout << "step2" << std::endl;CSample array3[2] = {3}; // array3[0]:用的是有参构造函数初始化;array3[1]:用的是无参构造函数初始化;std::cout << "step3" << std::endl;CSample * array4 = new CSample[2];delete []array4;return 0;
}
// OUT
Constructor 1 Called
Constructor 1 Called
step1
x = 4
Constructor 2 Called
x = 5
Constructor 2 Called
step2
x = 3
Constructor 2 Called
Constructor 1 Called
step3
Constructor 1 Called
Constructor 1 Called
zhangbushi@zhangbushideair beida_lesson % g++ 04.cpp -o 04
zhangbushi@zhangbushideair beida_lesson % ./04
Constructor 1 Called
Constructor 1 Called
step1
x = 4
Constructor 2 Called
====================
x = 5
Constructor 2 Called
====================
step2
x = 3
Constructor 2 Called
====================
Constructor 1 Called
step3
Constructor 1 Called
Constructor 1 Called
例4-2:构造函数在数组中的使用
class Test
{public:Test(int n) {} //(1)Test(int n, int m) {} //(2)Test() {} //(3)
};Test array1[3] = {1, Test(1,2)};
// 三个元素分别(1),(2),(3)初始化Test array2[3] = {Test(2,3), Test(1,2), 1};
// 三个元素分别用(2),(2),(1)初始化Test * pArray[3] = {new Test(4), new Test(1,2)}; // new的返回值是指针类型
//两个元素分别用(1),(2)初始化
2、赋值构造函数
2.1、基本概念
只有一个参数,即对同类对象的引用。
形如X::X( X& )
或X::X(const X &)
, 二者选一,后者能以常量对象作为参数
如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。
注意事项:无参构造函数不一定存在,但赋值构造函数一定存在;
例1:
class Complex
{private:double real, imag;
};
Complex c1; //调用缺省无参构造函数
Complex c2(c1);//调用缺省的复制构造函数,将 c2 初始化成和c1一样
如果定义的自己的复制构造函数,则默认的复制构造函数不存在。
class Complex {public :double real,imag;Complex(){ }Complex( const Complex & c ) {real = c.real;imag = c.imag;cout << “Copy Constructor called”;}
};
Complex c1;
Complex c2(c1);//调用自己定义的复制构造函数,输出 Copy Constructor called
不允许有形如 X::X( X )的构造函数。(必须要加上引用)
class CSample {CSample( CSample c ) {} //错,不允许这样的构造函数
};
2.1、复制构造函数起作用的三种情况
- 1、当用一个对象去初始化同类的另一个对象时。
Complex c2(c1);
Complex c2 = c1; //初始化语句,非赋值语句
- 2、如果某函数有一个参数是类 A 的对象,那么该函数被调用时,类A的复制构造函数将被调用。
class A
{public:A() { };A( A & a) { cout << "Copy constructor called" <<endl;}
};void Func(A a1){ }
int main(){A a2; // 通过无参构造函数初始化Func(a2); // 调用复制构造函数(复制构造函数,形参是实参的拷贝,不一定)return 0;
}
// 程序输出结果为: Copy constructor called
- 3、如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用:
# include <iostream>
class A
{public:int v;A(int n) { v = n; };A( const A & a) { v = a.v;std::cout << "Copy constructor called" << std::endl;}
};A Func()
{ A b(4); // 调用A(int n) { v = n; }; v = 4return b;
}
int main()
{ std::cout << Func().v << std::endl; return 0;
}// 输出结果:
Copy constructor called
4
- 4、注意:对象之间复制并不导致复制构造函数被调用
#include<iostream>class CMyclass
{public:int n;CMyclass() {};CMyclass( CMyclass & c) { n = 2 * c.n ;}
};int main()
{CMyclass c1, c2;c1.n = 5; c2 = c1; // 对象间赋值CMyclass c3(c1); // 调用复制构造函数std::cout << "c2.n = " << c2.n << ",";std::cout << "c3.n = " << c3.n << std::endl;return 0;
}
// 输出
c2.n = 5,c3.n = 10
2.2、常引用参数的使用
void fun(CMyclass obj_). {cout << “fun” << endl; }
- 这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大。
- 所以考虑使用CMyclass & 引用类型作为参数
- 如果希望确保实参的值在函数中不应该被改变,那么可以加上const关键字
3、类型转换构造函数
3.1、什么事类型转换构造函数
- 定义转换构造函数的目的是实现类型的自动转换。
- 只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。
- 当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。
实例:
#include<iostream>class Complex
{public:double real, imag;Complex( int i ) // (1){std::cout << "IntConstructor called" << std::endl;real = i; imag = 0;}Complex(double r, double i) {real =r; imag = i;} //(2)
};int main ()
{Complex c1(7, 8);Complex c2 = 12;c1 = 9; // 解释如下/*c1 = 9; 解释如下1、首先9会被自动转化成一个临时Complex对象,即:Complex Linshi = 9;2、c1 = linshi;*/std::cout << c1.real << "," << c1.imag << std::endl;return 0;
}
4、析构函数
4.1、什么是析构函数
实例:
class String{private :char * p;public:String () {p = new char[10]; //动态分配的内存空间,需要释放,在析构函数中释放。}
~ String ();
};
String ::~ String() {
delete [] p;
}
4.2、析构函数和数组
对象数组生命结束时,对象数组的每个元素的析构函数都会被调用。
#include<iostream>class Ctest
{public:~Ctest() {std::cout << "destructor called" << std::endl;}
};int main ()
{Ctest array[2];std::cout << "End Main" << std::endl;return 0;
}
// OUT
End Main
destructor called
destructor called
4.3、析构函数和运算符 delete
delete 运算导致析构函数调用
若new一个对象数组,那么用delete释放时应该写 []。否则只delete一个对象(调用一次析构函数)
Ctest * pTest;
pTest = new Ctest; //构造函数调用
delete pTest; //析构函数调用
------------------------------------------------------------------
pTest = new Ctest[3]; //构造函数调用3次
delete [] pTest; //析构函数调用3次
析构函数在对象作为函数返回值返回后被调用
/*
日期:2024.02.17
作者:源仔
*/#include<iostream>class CMyclass
{public:~CMyclass() {std::cout << "destructor" << std::endl;}
};CMyclass obj; // 全局对象
CMyclass fun(CMyclass sobj)
{return sobj;/*1、参数对象消亡也会导致析构函数被调用。2、函数调用返回时,生成临时对象返回*/
}int main()
{obj = fun(obj); // 函数调用的返回值(临时对象)被return 0; // 用过后,该临时对象析构函数被调用
}// OUT
destructor //指的是CMyclass fun(CMyclass sobj)中的CMyclass sobj形参使用结束,调用析构函数
destructor //指的是fun(obj)临时变量使用结束,调用析构函数
destructor //指的是CMyclass obj;全局对象消完,调用析构函数
5、构造函数析构函数调用时机
#include<iostream>
class Demo
{int id;public:Demo(int i){id = i;std::cout << "id = " << id << " constructor " << std::endl;}~Demo(){std::cout << "id = " << " destructed " << std::endl;}
};Demo d1(1); // 1、全局对象,在main函数之前就初始化了,就会引发构造函数,输出:id = 1 constructor
void Func()
{static Demo d2(2); // 静态的局部变量,整个程序结束,静态变量才会消完Demo d3(3);std::cout << "func" << std::endl;
}int main()
{Demo d4(4); // 2、输出:id = 4 constructord4 = 6; // 3、调用类型转换构造函数,构建为6的临时构造函数,输出:id = 6 constructor,临时构造函数调用完就会直接销毁,引发析构函数调用,输出:id = destructedstd::cout << "main" << std::endl; // 输出:main{Demo d5(5); // 4、局部对象,输出:id = 5 constructor} // 5、局部变量销毁,引发析构函数调用。输出:id = destructedFunc(); // 6、如下/*6、输出:id = 2 constructor7、输出:id = 3 constructor8、输出:Func9、静态的局部变量,整个程序结束,静态变量才会消完,所以不会先引发 static Demo d2(2)的析构函数10、先引发Demo d3(3);的析构函数,输出:id = destructed*/std::cout << "main ends" << std::endl; // 11、输出:main ends/*12、引发d4 = 6;中d4的析构函数调用(注意:之前引发的析构函数是 6 创建临时构造函数引发的析构函数调用),输出:id = destructed13、引发static Demo d2(2);的析构函数调用,输出:id = destructed14、引发Demo d4(4);的析构函数调用,输出:id = destructed*/return 0;
}/*
id = 1 constructor
id = 4 constructor
id = 6 constructor
id = destructed
main
id = 5 constructor
id = destructed
id = 2 constructor
id = 3 constructor
func
id = destructed
main ends
id = destructed
id = destructed
id = destructed
*/
实例5:
假设A是一个类的名字,下面的程序片段会类A的调用析构函数几次?
答案:
调用3次。
解释:
new创建的动态变量,必须要释放,才能引发析构函数的调用。
int main()
{A * p = new A[2];A * p2 = new A;A a;delete [] p;
}
相关文章:

C++面向对象程序设计-北京大学-郭炜【课程笔记(三)】
C面向对象程序设计-北京大学-郭炜【课程笔记(三)】 1、构造函数(constructor)1.1、基本概念 2、赋值构造函数2.1、基本概念2.1、复制构造函数起作用的三种情况2.2、常引用参数的使用 3、类型转换构造函数3.1、什么事类型转换构造函…...

Linux:搭建docker私有仓库(registry)
当我们内部需要存储镜像时候,官方提供了registry搭建好直接用,废话少说直接操作 1.下载安装docker 在 Linux 上安装 Docker Desktop |Docker 文档https://docs.docker.com/desktop/install/linux-install/安装 Docker 引擎 |Docker 文档https://docs.do…...

用HTML、CSS和JS打造绚丽的雪花飘落效果
目录 一、程序代码 二、代码原理 三、运行效果 一、程序代码 <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetGBK"><style>* {margin: 0;padding: 0;}#box {width: 100vw;heig…...

订餐|网上订餐系统|基于springboot的网上订餐系统设计与实现(源码+数据库+文档)
网上订餐系统目录 目录 基于springboot的网上订餐系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户功能模块的实现 (1)用户注册界面 (2)用户登录界面 (3)菜品详情界面 (…...

从零开始学howtoheap:解题西湖论剑Storm_note
how2heap是由shellphish团队制作的堆利用教程,介绍了多种堆利用技术,后续系列实验我们就通过这个教程来学习。环境可参见从零开始配置pwn环境:从零开始配置pwn环境:从零开始配置pwn环境:优化pwn虚拟机配置支持libc等指…...

Rust 基本环境安装
rust 基本介绍请看上一篇文章:rust 介绍 rustup 介绍 rustup 是 Rust 语言的安装器和版本管理工具。通过 rustup,可以轻松地安装 Rust 编译器(rustc)、标准库和文档。它也允许你切换不同的 Rust 版本或目标平台,以及…...

【电源】POE系统供电原理(二)
转载本博客文章,请注明出处 上一篇文章中,有提到POE系统工作原理及动态检测机制,下面我们继续介绍受电端PD技术及原理。POE供电系统包含PSE、PD及互联接口部分组成,如下图所示。 图1 POE供电系统 PSE控制器的主要作用ÿ…...

GPU独显下ubuntu屏幕亮度不能调节解决方法
GPU独显下屏幕亮度不能调节(假设你已经安装了合适的nvidia显卡驱动),我试过修改 /etc/default/grub 的 GRUB_CMDLINE_LINUX_DEFAULT"quiet splash acpi_backlightvendor" ,没用。修改和xorg.conf相关的文件,…...

Linux篇:网络基础1
一、网络基础:网络本质就是在获取和传输数据,而系统的本质是在加工和处理数据。 1、应用问题: ①如何处理发来的数据?—https/http/ftp/smtp ②长距离传输的数据丢失的问题?——TCP协议 ③如何定位的主机的问题&#…...

RK3568笔记十七:LVGL v8.2移植
若该文为原创文章,转载请注明原文出处。 本文介绍嵌入式轻量化图形库LVGL 8.2移植到Linux开发板ATK-RK3568上的步骤。 主要是参考大佬博客: LVGL v8.2移植到IMX6ULL开发板_lvgl移植到linux-CSDN博客 一、环境 1、平台:rk3568 2、开发板:…...

C#系列-C#访问MongoDB+redis+kafka(7)
目录 一、 C#中访问MongoDB. 二、 C#访问redis. 三、 C#访问kafka. C#中访问MongoDB 在C#中访问MongoDB,你通常会使用MongoDB官方提供的MongoDB C#/.NET Driver。这个驱动提供了丰富的API来执行CRUD(创建、读取、更新、删除&#x…...

(12)Hive调优——count distinct去重优化
离线数仓开发过程中经常会对数据去重后聚合统计,count distinct使得map端无法预聚合,容易引发reduce端长尾,以下是count distinct去重调优的几种方式。 解决方案一:group by 替代 原sql 如下: #7日、14日的app点击的…...

记录 | 验证pytorch-cuda是否安装成功
检测程序如下: import torchprint(torch.__version__) print(torch.cuda.is_available()) 或者用终端 Shell,运行情况如下...

LeetCode 239.滑动窗口的最大值 Hot100 单调栈
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1: 输入:nums [1,3,-1,-3,5,3,6,7], k 3 输…...

463. Island Perimeter(岛屿的周长)
问题描述 给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] 1 表示陆地, grid[i][j] 0 表示水域。 网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有…...

如何解决缓存和数据库的数据不一致问题
数据不一致问题是操作数据库和操作缓存值的过程中,其中一个操作失败的情况。实际上,即使这两个操作第一次执行时都没有失败,当有大量并发请求时,应用还是有可能读到不一致的数据。 如何更新缓存 更新缓存的步骤就两步࿰…...

linux系统下vscode portable版本的python环境搭建003:venv
这里写自定义目录标题 python安装方案一. 使用源码安装(有[构建工具](https://blog.csdn.net/ResumeProject/article/details/136095629)的情况下)方案二.使用系统包管理器 虚拟环境安装TESTCG 本文目的:希望在获得一个新的系统之后ÿ…...

使用TinyXML-2解析XML文件
一、XML介绍 当我们想要在不同的程序、系统或平台之间共享信息时,就需要一种统一的方式来组织和表示数据。XML(EXtensible Markup Language,即可扩展标记语言)是一种用于描述数据的标记语言,它让数据以一种结构化的方…...

Linux:docker在线仓库(docker hub 阿里云)基础操作
把镜像放到公网仓库,这样可以方便大家一起使用,当需要时直接在网上拉取镜像,并且你可以随时管理自己的镜像——删除添加或者修改。 1.docker hub仓库 2.阿里云加速 3.阿里云仓库 由于docker hub是国外的网站,国内的对数据的把控…...
C语言程序设计(第四版)—习题7程序设计题
目录 1.选择法排序。 2.求一批整数中出现最多的数字。 3.判断上三角矩阵。 4.求矩阵各行元素之和。 5.求鞍点。 6.统计大写辅音字母。 7.字符串替换。 8.字符串转换成十进制整数。 1.选择法排序。 输入一个正整数n(1<n≤10)…...

ZCC6982-同步升压充双节锂电池充电芯片
特性 ■高达 2A 的可调充电电流(受实际散热和输入功率限制) ■支持 8.4V、8.6V、8.7V、8.8V 的充满电压(限QFN) ■高达 28V 的输入耐压保护 ■高达 28V 的电池端耐压保护 ■宽输入工作电压范围:3.0V~6.5V ■峰值…...

定时器(基本定时器、通用定时器、高级定时器)
目录 一、基本定时器 二、通用定时器 三、高级定时器 一、基本定时器 1、作用:计时和计数。 二、通用定时器 1、除了有基本定时器的计时和计数功能外,主要有输入捕获和输出比较的功能,硬件主要由六大部分组成: ① 时钟源 ② 控…...

009集——磁盘详解——电脑数据如何存储在磁盘
很多人也知道数据能够保存是由于设备中有一个叫做「硬盘」的组件存在,但也有很多人不知道硬盘是怎样储存这些数据的。这里给大家讲讲其中的原理。 首先我们要明白的是,计算机中只有0和1,那么我们存入硬盘的数据,实际上也就是一堆0…...

鸿蒙开发-HarmonyOS UI架构
初步布局Index 当我们新建一个工程之后,首先会进入Index页。我们先简单的做一个文章列表的显示 class Article {title?: stringdesc?: stringlink?: string }Entry Component struct Index {State articles: Article[] []build() {Row() {Scroll() {Column() …...

Flutter 动画(显式动画、隐式动画、Hero动画、页面转场动画、交错动画)
前言 当前案例 Flutter SDK版本:3.13.2 显式动画 Tween({this.begin,this.end}) 两个构造参数,分别是 开始值 和 结束值,根据这两个值,提供了控制动画的方法,以下是常用的; controller.forward() : 向前…...

用HTML5 Canvas创造视觉盛宴——动态彩色线条效果
目录 一、程序代码 二、代码原理 三、运行效果 一、程序代码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!-- 声明文档类型为XHTML 1.0 Transitional -…...

云原生介绍与容器的基本概念
云原生介绍 1、云原生的定义 云原生为用户指定了一条低心智负担的、敏捷的、能够以可扩展、可复制的方式最大化地利用云的能力、发挥云的价值的最佳路径。 2、云原生思想两个理论 第一个理论基础是:不可变基础设施。 第二个理论基础是:云应用编排理…...

Flash存储
目录 一、MCU读写擦除Flash步骤 1、写flash步骤: 2、读flash步骤: 3、擦除flash步骤: 4、要注意的地方: 一、MCU读写擦除Flash步骤 1、写flash步骤: (1)解锁 2、读flash步骤: 3、擦除flash步骤&#x…...

Day 44 | 动态规划 完全背包、518. 零钱兑换 II 、 377. 组合总和 Ⅳ
完全背包 题目 文章讲解 视频讲解 完全背包和0-1背包的区别在于:物品是否可以重复使用 思路:对于完全背包问题,内层循环的遍历方式应该是从weight[i]开始一直遍历到V,而不是从V到weight[i]。这样可以确保每种物品可以被选择多次…...

使用PaddleNLP UIE模型提取上市公司PDF公告关键信息
项目地址:使用PaddleNLP UIE模型抽取PDF版上市公司公告 - 飞桨AI Studio星河社区 (baidu.com) 背景介绍 本项目将演示如何通过PDFPlumber库和PaddleNLP UIE模型,抽取公告中的相关信息。本次任务的PDF内容是破产清算的相关公告,目标是获取受理…...