C++面向对象程序设计-北京大学-郭炜【课程笔记(三)】

news2025/3/11 11:10:55

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 = 4
	return 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 constructor
    d4 = 6;      // 3、调用类型转换构造函数,构建为6的临时构造函数,输出:id = 6 constructor,临时构造函数调用完就会直接销毁,引发析构函数调用,输出:id = destructed
    std::cout << "main" << std::endl;  // 输出:main
    {
        Demo d5(5);   // 4、局部对象,输出:id = 5 constructor
    }  // 5、局部变量销毁,引发析构函数调用。输出:id = destructed
    Func();  // 6、如下
    /*
    6、输出:id = 2 constructor
    7、输出:id = 3 constructor
    8、输出:Func
    9、静态的局部变量,整个程序结束,静态变量才会消完,所以不会先引发 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 = destructed
    13、引发static Demo d2(2);的析构函数调用,输出:id = destructed
    14、引发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;
}

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

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

相关文章

LiveGBS流媒体平台GB/T28181功能-redis订阅国标设备状态redis订阅通道状态subscribe device操作及示例

支持Redis订阅国标设备状态及国标通道状态上线离线 1、设备状态监听的烦恼2、device订阅2.1、设备上线消息2.2、设备离线消息2.2、通道上线消息2.2、通道离线消息 3、订阅示例3.1、连接REDIS3.2、订阅device示例3.3、设备上线示例3.3.1、注册上线后 3.4、设备离线示例3.4.1、注…

力扣OJ题——旋转数组

题目&#xff1a;189.旋转数组 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数 思路一&#xff1a; 1.每次挪动旋转1位&#xff08;用tmp将最后一位存起来&#xff0c;其余所有数据向后移&#xff0c;然后将tmp放在第一个位…

什么是VDA 4902?

VDA 4902为条码运输标签&#xff0c;主要为企业制作条码运输标签提供参考和标准。 运输标签唯一标识内部物料流以及货物供应商、货运代理和收货人之间运输中的运输方式和载货人。因此&#xff0c;所有供应商必须确保所有运输工具和载货架都带有符合 VDA 标准下 4902 第 4 版&a…

恢复被.target勒索病毒加密的数据文件:拒绝向.target勒索病毒支付赎金

引言&#xff1a; 在当今数字时代&#xff0c;勒索病毒已成为网络安全领域的一大威胁&#xff0c;而.target勒索病毒是其中引起广泛关注的一种变种。本文将深入探讨.target勒索病毒的特点以及被其加密的数据文件恢复方法。数据的重要性不容小觑&#xff0c;您可添加我们的技术…

用Python和OpenCV搭建自己的一维码和QRCode扫描仪(步骤 + 源码)

导 读 本文主要介绍使用Python和OpenCV搭建自己的一维码和QRCode扫描仪&#xff08;步骤 源码&#xff09;。 项目简介 本文我们将创建一个程序来扫描图像中的二维码和条形码。对于这个程序&#xff0c;我们需要三个包&#xff0c;分别是OpenCV、NumPy和pyzbar。大多数 Pyth…

linux搭建测试环境详细过程

前言 本文记录下&#xff0c;测试人员如何搭建测试环境&#xff0c;以供后面自己方便找&#xff0c;大家可以借鉴下搭建测试环境需要安装的有&#xff1a;nginx&#xff0c;redis&#xff0c;mysql&#xff0c;java&#xff0c;docker&#xff0c;保证这几个基本就可以用了&…

P1439 背包九讲(1):简单的0-1背包

P1439 背包九讲1&#xff1a;简单的0-1背包 一、原题呈现1、题目描述2、输入描述3、输出描述4、样例输入5、样例输出 二、思路分析这是一个最基础的01背包问题。 三、整体代码 一、原题呈现 1、题目描述 有一个箱子容量为 V&#xff08;正整数&#xff0c;0&#xff1c;&…

BUGKU-WEB source

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; 解题思路 看源码&#xff0c;看F12网络请求没有东西只能老老实实按照提示用Linux去扫描目录 相关工具 kali虚拟机安装gobuster 或者dirsearch 解题步骤 先查看源码&#xff1a; flag{Zmxhz19ub3RfaGvyzS…

Code Composer Studio (CCS) - Breakpoint (断点)

Code Composer Studio [CCS] - Breakpoint [断点] 1. BreakpointReferences 1. Breakpoint 选中断点右键 -> Breakpoint Properties… Skip Count&#xff1a;跳过断点总数&#xff0c;在断点执行之前设置总数 Current Count&#xff1a;当前跳过断电累计值 References […

Cron表达式选择器

Cron表达式选择器 功能描述 Cron表达式选择器是用于定时任务调度的一种常见工具&#xff0c;通常用于指定任务的执行时间。Cron表达式由一系列时间单位和对应的时间值组成&#xff0c;用于指定任务的执行时间。下面是一个Cron表达式的示例 0 0 12 * * ?这个表达式的含义是每…

适用于 Windows 的 12 个最佳 PDF 编辑器

PDF文档的普遍存在按理说&#xff0c;PDF文档的可读性和可移植性受到专业文档的青睐。 然而&#xff0c;PDF格式的可食用性是一大缺陷。幸运的是&#xff0c;各种 PDF 编辑工具和软件使 PDF 的编辑变得更加容易&#xff0c;这篇文章旨在帮助我们的读者找到其中最好的工具和软件…

IPv4编址方式

IPv4编址方式 本文的知识都可以到B站up湖科大教书匠的视频里去看具体讲解。 分类地址 IPv4地址被分为网络号和主机号&#xff0c;可分为A类地址、B类地址、C类地址、D类地址、E类地址。其中&#xff0c;只有A类、B类、C类地址的可用于给网络中的主机编址。 A类地址的网络号…

详解 leetcode_078. 合并K个升序链表.小顶堆实现

/*** 构造单链表节点*/ class ListNode{int value;//节点值ListNode next;//指向后继节点的引用public ListNode(){}public ListNode(int value){this.valuevalue;}public ListNode(int value,ListNode next){this.valuevalue;this.nextnext;} }package com.ag; import java.ut…

上位机图像处理和嵌入式模块部署(cmake的使用)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 过去我们编写windows程序的时候&#xff0c;习惯上都是直接使用visual studio创建工程开发。而开发linux程序的时候&#xff0c;则是编写好c、cpp代…

计算机设计大赛 深度学习YOLO图像视频足球和人体检测 - python opencv

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络4 Yolov5算法5 数据集6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习YOLO图像视频足球和人体检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非…

Unity中关于ScrollRect组件完整解决方案(ScrollRect中元素自动排版+ScrollRect中元素自动定位到Viewport可见范围内)

这个教程可以实现点击我这个视频中所示的效果 一、元素自动排版功能 1、首先要往我们的unity项目中导入两个脚本文件&#xff0c;脚本文件名称分别是UIScrollEventListener和CZScrollRect&#xff0c;这两个脚本文件代码如下所示。 1-1、介绍UIScrollEventListener脚本写法。…

树和二叉树的基本知识

一、树的概念及结构 1.树的概念 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的 。 有一个 特殊…

上位机图像处理和嵌入式模块部署(Halcon借鉴与客户学习)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 对于很多学院派的同学来说&#xff0c;他们对市场的感觉一般是比较弱的。如果写一个软件的话&#xff0c;或者说开发一个项目的话&#xff0c;他们…

SQL-Labs靶场“11-15”关通关教程

君衍. 一、十一关 基于POST单引号字符型注入1、源码分析2、联合查询注入3、报错注入 二、十二关 基于POST双引号字符型注入1、源码分析2、联合查询注入3、报错注入 三、十三关 基于POST单引号报错注入变形1、源码分析2、报错注入 四、十四关 基于POST双引号报错注入1、源码分析…

2024阿里云云服务器ECS价格表出炉

2024年最新阿里云服务器租用费用优惠价格表&#xff0c;轻量2核2G3M带宽轻量服务器一年61元&#xff0c;折合5元1个月&#xff0c;新老用户同享99元一年服务器&#xff0c;2核4G5M服务器ECS优惠价199元一年&#xff0c;2核4G4M轻量服务器165元一年&#xff0c;2核4G服务器30元3…