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

2024-02-18 13:52

本文主要是介绍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++面向对象程序设计-北京大学-郭炜【课程笔记(三)】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/721359

相关文章

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符

C++中detach的作用、使用场景及注意事项

《C++中detach的作用、使用场景及注意事项》关于C++中的detach,它主要涉及多线程编程中的线程管理,理解detach的作用、使用场景以及注意事项,对于写出高效、安全的多线程程序至关重要,下... 目录一、什么是join()?它的作用是什么?类比一下:二、join()的作用总结三、join()怎么

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的