网站建设赚钱项目,文明网站建设管理培训心得,做海报的网站知乎,东城手机网站制作注#xff1a;本博客图片来源于学习笔记: 学习笔记https://gitee.com/box-he-he/learning-notes
完整思维导图请前往该博主码云下载。 目录 注#xff1a;本博客图片来源于学习笔记: 学习笔记https://gitee.com/box-he-he/learning-notes
完整思维导图请前往该博主码云下载… 注本博客图片来源于学习笔记: 学习笔记https://gitee.com/box-he-he/learning-notes
完整思维导图请前往该博主码云下载。 目录 注本博客图片来源于学习笔记: 学习笔记https://gitee.com/box-he-he/learning-notes
完整思维导图请前往该博主码云下载。
一、构造函数
构造函数的形式及其使用
初始化列表
explicit关键字
二、析构函数
1 .概念
2. 特性
析构函数的调用顺序
三、拷贝构造函数
特性
四、运算符重载部分
1、概念
2、赋值运算符重载
3、前置和后置的实现 一、构造函数
构造函数是一种特殊的成员函数在创建对象时自动调用用于初始化对象的成员变量和执行其他必要的操作。它的名称与类名相同没有返回类型并且可以被重载。
构造函数有以下几个重要的特点
构造函数的名称与类名相同包括大小写和命名空间。构造函数没有返回类型。构造函数可以被重载即可以定义多个具有不同参数列表的构造函数。构造函数可以有默认参数从而允许在创建对象时省略某些参数。构造函数可以有任意数量的初始化列表用于按照指定顺序初始化对象的成员变量。构造函数可以访问类的所有成员变量和成员函数。构造函数在创建对象时自动调用无需手动调用。对象的整个生命周期只调用一次。
构造函数主要用于以下几个方面功能
初始化成员变量构造函数通过参数或初始化列表来初始化对象的成员变量确保对象在创建时具有特定的初始值。分配内存或资源如果对象需要动态分配内存或管理其他资源如文件句柄构造函数可以在创建对象时进行相应的操作从而确保对象可以正确地使用这些资源。执行必要的操作有时候创建对象可能需要执行一些必要的操作比如打开文件等构造函数可以在创建对象时执行这些操作。
需要注意以下几点
一个类可以有多个构造函数它们具有不同的参数列表。这被称为构造函数的重载。如果没有显式地定义构造函数编译器会自动生成一个默认构造函数但它只会对自定义类型变量进行默认初始化对内置类型int, double, float等无法初始化。编写构造函数时应该根据类的需求来初始化成员变量并且可以根据需要执行其他必要的操作。如果自定义了构造函数编译器将不再生成默认构造函数。如果需要同时拥有默认构造函数和自定义构造函数可以提供默认参数或者显式定义一个无参数的默认构造函数。
构造函数的形式及其使用
默认构造函数包括1、无参构造函数2、全缺省的构造函数3、编译器默认生成的构造函数
当用户自己定义构造函数时 必须存在一个默认构造函数。
构造函数的使用 class Date{public:// 1.无参构造函数默认构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year year;_month month;_day day;}// 3.全缺省的构造函数默认构造函数Date(int year 2024, int month 2, int day 6){_year year;_month month;_day day;}// 4.编译器默认生成的构造函数默认构造函数private:int _year;int _month;int _day;};void TestDate(){Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数// 注意如果通过无参构造函数创建对象时对象后面不用跟括号否则就成了函数声明// 以下代码的函数声明了d3函数该函数无参返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)Date d3();}
需要注意的是全缺省的构造函数和无参的构造函数都属于默认构造函数不能同时存在。
同时 函数2 与 函数3 无法构成函数重载会造成函数的重定义也无法同时存在。 补充由于编译器默认生成的构造函数无法对内置类型进行初始化在C11标准中对此引入了解决方案在变量声明时可以为其提供默认值。 class Date{private:int _year 2024;int _month 2;int _day 6;};void TestDate(){Date d; // 此时d的私有成员初始化为2024/2/6} 初始化列表
1、基本形式 初始化列表以一个 冒号开始 接着是一个以 逗号分隔的数据成员列表 每个 成员变量 后面跟 一个 放在括号中的初始值或表达式。 class Date
{
public:
Date(int year, int month, int day): _year(year), _month(month), _day(day){}private:
int _year;
int _month;
int _day;
}; 【注意】 1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次) 2. 类中包含以下成员必须放在初始化列表位置进行初始化 引用成员变量const成员变量自定义类型成员(且该类没有其默认构造函数时) class A
{
public:A(int a):_a(a){}
private:int _a;
};
class B
{
public:B(int a, int ref):_aobj(a), _ref(ref), _n(10){}
private:A _aobj; // 没有默认构造函数的自定义类型int _ref; // 引用const int _n; // const
}; 特性 1、即使不在初始化列表中初始化自定义类型成员其也会在初始化列表中隐式地调用其构造函数进行初始化。 2、初始化列表的执行顺序是其在类中的声明顺序与初始化列表中的顺序无关。 explicit关键字 explicit 是一个关键字用于修饰单参数构造函数它可以防止编译器进行隐式的类型转换。在 C 中当我们定义一个单参数构造函数时编译器会自动进行隐式类型转换将参数类型转换为目标类型然后调用该构造函数来创建对象。而使用 explicit 关键字修饰的构造函数将被标记为禁止隐式类型转换。 下面是两个具体的示例来说明 explicit 关键字的作用 1、单参构造函数 class MyClass {
public:explicit MyClass(int x) : value(x) {}int getValue() const {return value;}private:int value;
};int main() {MyClass obj1(5); // 直接调用带有 int 参数的构造函数int val1 obj1.getValue();MyClass obj2 10; // 编译错误禁止隐式类型转换int val2 obj2.getValue();return 0;
}在上述示例中MyClass 类定义了一个带有单参数的构造函数并使用 explicit 关键字进行修饰。这意味着我们只能通过显式地调用构造函数来创建对象而不能进行隐式的类型转换。在 main 函数中我们可以看到以下几点 创建 obj1 对象时我们直接调用了构造函数不会发生隐式类型转换。这是合法的。 尝试使用 obj2 10; 进行从 int---MyClass 隐式类型转换时编译器会抛出错误。因为我们使用了 explicit 关键字禁止隐式类型转换。 2、半缺省的构造函数 class MyClass {
public://虽然有多个参数但是创建对象时后两个参数可以不传递使用explicit修饰// 不具有类型转换作用编译不通过explicit MyClass(int x, int y 1, int z 1) : value(x), data(y), temp(z){}int getValue() const {return value;}private:int value;int data;int temp;
};int main() {MyClass obj1(5); // 直接调用带有 int 参数的构造函数int val1 obj1.getValue();MyClass obj2 10; // 编译错误禁止隐式类型转换int val2 obj2.getValue();return 0;
} 二、析构函数 1 .概念 通过前面构造函数的学习我们知道一个对象是怎么来的那一个对象又是怎么没呢的 析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。 【注意】析构函数只负责销毁占用的额外资源即额外申请的内存空间例如mallocnew等。而对象本身如果定义在函数中则其本身存在于栈区中随着函数的栈帧一同销毁与析构函数无关。 2. 特性 析构函数是特殊的成员函数其特征如下 1. 析构函数名是在类名前加上字符 ~。 2. 无参数无返回值类型。 3. 一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构 函数不能重载 4. 对象生命周期结束时C编译系统系统自动调用析构函数。 typedef int DataType;
class Stack
{
public:Stack(size_t capacity 3){_array (DataType*)malloc(sizeof(DataType) * capacity);if (NULL _array){perror(malloc申请空间失败!!!);return;}_capacity capacity;_size 0;}void Push(DataType data){// CheckCapacity();_array[_size] data;_size;}//析构函数~Stack(){if (_array){free(_array);_array NULL;_capacity 0;_size 0;}}
private:DataType* _array;int _capacity;int _size;
};
void TestStack()
{Stack s;s.Push(1);s.Push(2);
} 析构函数的调用顺序 三、拷贝构造函数
拷贝构造函数Copy Constructor是一种特殊的构造函数它接受一个对象作为参数用于创建一个新的对象这个新对象与原始对象具有相同的值。
在C中如果我们没有显式定义拷贝构造函数编译器会自动生成一个默认的拷贝构造函数。默认拷贝构造函数执行的是逐个成员的拷贝将源对象的每个成员变量的值复制给新创建的对象。
以下是一个简单的示例来说明拷贝构造函数的用法
class MyClass {
public:int value;// 拷贝构造函数MyClass(const MyClass other) {value other.value;}
};int main() {MyClass obj1;obj1.value 10;MyClass obj2(obj1); // 使用拷贝构造函数创建新对象// 或者 MyClass obj2 obj1return 0;
}在上述示例中我们定义了一个名为 MyClass 的类并在其中定义了一个拥有一个整数成员变量 value 的对象。然后我们在 main 函数中创建了两个对象 obj1 和 obj2 obj1 是通过默认构造函数创建的并将 value 设置为 10。 obj2 是通过拷贝构造函数创建的它的参数是另一个 MyClass 对象 obj1。在拷贝构造函数中我们将 obj1 的值复制给了 obj2。
需要注意的是拷贝构造函数的参数应该是一个 const 引用const MyClass other以确保不会修改被传递的源对象。
拷贝构造函数在以下情况下会被隐式调用
通过赋值运算符进行对象的初始化。将一个对象作为函数的参数传递给另一个对象。从一个函数返回对象时。
需要注意的是当类中包含指针成员变量时需要手动定义拷贝构造函数以确保指针指向独立的内存避免浅拷贝引发的指针悬挂问题。 特性
1. 拷贝构造函数是构造函数的一个重载形式。 2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错 因为会引发无穷递归调用。 class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}// Date(const Date d) // 正确写法Date(const Date d) // 错误写法编译报错会引发无穷递归{_year d._year;_month d._month;_day d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);return 0;
} 为什么值传递会引起无穷递归呢为什么引用不会呢 答假如参数不是引用是值传递那么传递给函数的值其实就是实参的一个临时拷贝那在对实参进行临时拷贝时还是需要再次去调用我Date类里的拷贝构造函数由于一直进行的是值传递所以这个过程是无限递归的。但是如果拷贝构造函数的参数是对实参的引用就不需要对实参再进行临时拷贝。引用是实参的一个别名他们指向的也是同一块内存所以不需要传递值而是通过实参的引用来对实参的内存进行读写。 3. 若未显式定义编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按 字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。
class Time
{
public:Time(){_hour 1;_minute 1;_second 1;}Time(const Time t){_hour t._hour;_minute t._minute;_second t._second;cout Time::Time(const Time) endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year 1970;int _month 1;int _day 1;// 自定义类型Time _t;
};
int main()
{Date d1;// 用已经存在的d1拷贝构造d2此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数则编译器会给Date类生成一个默认的拷贝构//造函数Date d2(d1); // Date d2 d1; 也是默认调用的拷贝函数return 0;
} 【注意】在编译器生成的默认拷贝构造函数中内置类型是按照字节方式直接拷贝的而自定义类型是调用其拷贝构造函数完成拷贝的。 4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了还需要自己显式实现吗 当然像日期类这样的类是没必要的。
但是对于初始化时成员需要额外申请内存的类此时我们使用默认拷贝函数会发生程序崩溃这时我们必须手动创建一个拷贝构造函数进行深拷贝。例如下面的栈class Stack
typedef int DataType;
class Stack
{
public:Stack(size_t capacity 10){_array (DataType*)malloc(capacity * sizeof(DataType));if (nullptr _array){perror(malloc申请空间失败);return;}_size 0;_capacity capacity;}// 如果我们在这只是进行简单的值拷贝程序将会崩溃Stack(const Stack s){_array new DataType[_capacity];memcpy(_array, s._array, _size * sizeof(DataType));_size s._size;_capacity s._capacity;}void Push(const DataType data){// CheckCapacity();_array[_size] data;_size;}~Stack(){if (_array){free(_array);_array nullptr;_capacity 0;_size 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
} 【注意】类中如果没有涉及资源申请时拷贝构造函数是否写都可以一旦涉及到资源申请 时则拷贝构造函数是一定要写的否则就是浅拷贝。
5. 拷贝构造函数典型调用场景 使用已存在对象创建新对象 函数参数类型为类类型对象 函数返回值类型为类类型对象
class MyClass {
public:int value;// 拷贝构造函数MyClass(const MyClass other) {value other.value;}
};MyClass Test(const MyClass obj3)
{MyClass temp;return temp; // 函数返回值类型为类类型对象// 由于temp为局部变量在函数结束后不存在所以需对temp进行临时拷贝// 返回值的实质是temp的一份临时拷贝
}int main() {MyClass obj1;obj1.value 10;MyClass obj2(obj1); // 或者 MyClass obj2 obj1使用已存在的对象创建新对象Test(obj2); // 函数的参数类型为类类型对象return 0;
} 四、运算符重载部分
1、概念 C 为了增强代码的可读性引入了运算符重载 运算符重载是具有特殊函数名的函数 也具有其 返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为关键字 operator 后面接需要重载的运算符符号 。 函数原型 返回值类型 operator 操作符 ( 参数列表 ) 【注意】 不能通过连接其他符号来创建新的操作符比如operator重载操作符必须有一个类类型参数用于内置类型的运算符其含义不能改变例如内置的整型不 能改变其含义 作为类成员函数重载时其形参看起来比操作数数目少 1 因为成员函数的第一个参数为隐 藏的 this .* :: sizeof ?: . 注意以上5个运算符不能重载。 2、赋值运算符重载
1、重载运算符的格式 参数类型const T传递引用可以提高传参效率 返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值 检测是否自己给自己赋值 返回*this 要复合连续赋值的含义
class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}Date(const Date d){_year d._year;_month d._month;_day d._day;}Date operator(const Date d){if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;}
private:int _year;int _month;int _day;
}; 2. 赋值运算符只能重载成类的成员函数不能重载成全局函数 【原因】赋值运算符如果不显式实现编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突了故赋值 运算符重载只能是类的成员函数。 【注意】如果需要在全局中重载其他运算符需要给两个参数。因为只有类的非静态成员函数具有this指针全局函数没有。
3. 用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝。
【注意】内置类型成员变量是直接赋值的而自定义类型成员变量需要调用对应类的赋值运算符 重载完成赋值。
3、前置和后置的实现
class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}// 前置返回1之后的结果// 注意this指向的对象函数结束后不会销毁故以引用方式返回提高效率Date operator(){_day 1;return *this;}// 后置// 前置和后置都是一元运算符为了让前置与后置形成能正确重载// C规定后置重载时多增加一个int类型的参数但调用函数时该参数不用传递编译器//自动传递// 注意后置是先使用后1因此需要返回1之前的旧值故需在实现时需要先将this保存//一份然后给this 1// 而temp是临时对象因此只能以值的方式返回不能返回引用Date operator(int){Date temp(*this);_day 1;return temp;}
private:int _year;int _month;int _day;
};int main()
{Date d;Date d1(2022, 1, 13);d d1; // d: 2022,1,13 d1:2022,1,14d d1; // d: 2022,1,15 d1:2022,1,15return 0;
}