上海网站,网站定制制作公司,安徽工业大学两学一做网站,nginx wordpress php7目录 类的6个默认成员函数构造函数使用方法 析构函数使用方法 拷贝构造函数使用方法 赋值运算符重载赋值运算符重载 const成员 上篇末尾我们讲到了关于c实现栈相较于c语言在传递参数时的一些优化#xff0c;但实际上#xff0c;c在
初始化 清理 赋值 拷贝等方面也做了很大程… 目录 类的6个默认成员函数构造函数使用方法 析构函数使用方法 拷贝构造函数使用方法 赋值运算符重载赋值运算符重载 const成员 上篇末尾我们讲到了关于c实现栈相较于c语言在传递参数时的一些优化但实际上c在
初始化 清理 赋值 拷贝等方面也做了很大程度的优化本篇文章我们将研讨c的几个
默认成员函数。 类的6个默认成员函数
如果一个类中什么成员都没有简称为空类。 空类中真的什么都没有吗并不是任何类在什么都不写时**编译器会自动生成以下6个默认成员函数。** 默认成员函数用户没有显式实现编译器会生成的成员函数称为默认成员函数。
构造函数
我们先来看之前写过的日期类
class Date
{
public:void Init(int year, int month, int day){_year year;_month month;_day day;}void Print(){cout _year - _month - _day endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;d1.Init(2022, 7, 5);d1.Print();Date d2;d2.Init(2022, 7, 6);d2.Print();return 0;
}对于Date类可以通过 Init 公有方法给对象设置日期**但如果每次创建对象时都调用该方法设置信息未免有点麻烦**那能否在对象创建时就将信息设置进去呢 构造函数是一个特殊的成员函数名字与类名相同,创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次。 特征如下 1. 函数名与类名相同。 2. 无返回值。 3. 对象实例化时编译器自动调用对应的构造函数。 4. 构造函数可以重载。
使用方法
构造函数分为两种 1, 无参构造函数 2带参构造函数
class Date
{
public:Date(){}Date(int year,int month,int day){_year year;_month month;_day day;}void Print(){cout _year - _month - _day endl;}
private:int _year;int _month;int _day;
};void Test1()
{Date d1(2023,8,17);d1.Print();Date d2;d2.Print();
}int main()
{Test1();return 0;
}打印结果如下 如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成。 class Date{public:/*// 如果用户显式定义了构造函数编译器将不再生成Date(int year, int month, int day){_year year;_month month;_day day;}*/void Print(){cout _year - _month - _day endl;}private:int _year;int _month;int _day;};int main(){// 将Date类中构造函数屏蔽后代码可以通过编译因为编译器生成了一个无参的默认构造函
数// 将Date类中构造函数放开代码编译失败因为一旦显式定义任何构造函数编译器将不再
生成// 无参构造函数放开后报错error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return 0;}关于编译器生成的默认成员函数很多童鞋会有疑惑不实现构造函数的情况下编译器会 生成默认的构造函数。但是看起来默认构造函数又没什么用d对象调用了编译器生成的默 认构造函数但是d对象_year/_month/_day依旧是随机值。也就说在这里编译器生成的 默认构造函数并没有什么用 解答C把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型如int/char…自定义类型就是我们使用class/struct/union等自己定义的类型看看 下面的程序就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员 函数。
class Time
{
public:Time(){cout Time() endl;_hour 0;_minute 0;_second 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在 类中声明时可以给默认值。
class Time
{
public:Time(){cout Time() endl;_hour 0;_minute 0;_second 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(){}Date(int year,int month,int day){_year year;_month month;_day day;}void Print(){cout _year - _month - _day endl;}
private:int _year 1;int _month 1;int _day 1;Time _t;
};void Test1()
{Date d1(2023,8,17);d1.Print();Date d2;d2.Print();
}int main()
{Test1();return 0;
}打印结果如下 无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。 注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为 是默认构造函数。
析构函数
通过前面构造函数的学习我们知道一个对象是怎么来的那一个对象又是怎么没呢的 析构函数与构造函数功能相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作。
使用方法
析构函数是特殊的成员函数其特征如下 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);
}关于编译器自动生成的析构函数是否会完成一些事情呢下面的程序我们会看到编译器 生成的默认析构函数对自定类型成员调用它的析构函数。
class Time
{
public:Time(){cout Time() endl;_hour 0;_minute 0;_second 0;}~Time(){cout ~Time() endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(int year 1900,int month 1,int day 1){_year year;_month month;_day day;}void Print(){cout _year - _month - _day endl;}
private:int _year;int _month;int _day;Time _t;
};void Test1()
{Date d1(2023,8,17);d1.Print();Date d2;d2.Print();
}int main()
{Test1();return 0;
}打印结果如下 注意如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数比如Date类有资源申请时一定要写否则会造成资源泄漏比如Stack类。
拷贝构造函数 只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存在的类类型对象创建新对象时由编译器自动调用。
使用方法
拷贝构造函数也是特殊的成员函数其特征如下
拷贝构造函数是构造函数的一个重载形式。拷贝构造函数参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错 因为会引发无穷递归调用。
我们来看下面这段代码
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(const Date d)会引发无穷递归呢 这是因为当我们传值调用会引发对对象d的拷贝然后又会调用拷贝构造函数在调用时又会引发对象拷贝这样就会无限循环下去但如果我们采取传引用的方法形参和实参是同一个对象就不会引发调用拷贝构造。
若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。
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);
return 0; }注意在编译器生成的默认拷贝构造函数中内置类型是按照字节方式直接拷贝的而自定 义类型是调用其拷贝构造函数完成拷贝的。
那么既然编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了我们还需要自己来显示实现吗 答案是肯定的 这是因为对于日期类这种内置类型的类是没有必要自己写的但是像栈堆这种需要动态申请资源的类就需要自己显示实现。如果我们不写系统会默认调用自带的成员函数也就是浅拷贝
那么为什么浅拷贝不行而必须要深拷贝呢 这是因为如果我们使用浅拷贝也就是值传递那么原对象中的内容会原封不动拷贝到新对象中去如果里面有类似指针类型的这种成员变量那么它们将会指向同一块空间在析构时就会对同一块空间释放两次从而导致程序崩溃。 拷贝构造函数典型调用场景: ·使用已存在对象创建新对象 ·函数参数类型为类类型对象 ·函数返回值类型为类类型对象\font 看下面这段代码
class Date
{
public:Date(int year, int minute, int day){cout Date(int,int,int): this endl;}Date(const Date d){cout Date(const Date d): this endl;}
~Date() {cout ~Date(): this endl;}
private:int _year;
int _month;
int _day; };
Date Test(Date d)
{Date temp(d);return temp;
}
int main() {Date d1(2022,1,13);Test(d1);return 0;赋值运算符重载
C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其 返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 函数名字为: 关键字operator后面接需要重载的运算符符号。 函数原型:返回值类型 operator操作符(参数列表) 注意: 不能通过连接其他符号来创建新的操作符:比如operator 重载操作符必须有一个类类型参数 用于内置类型的运算符其含义不能改变例如:内置的整型不 能改变其含义 作为类成员函数重载时其形参看起来比操作数数目少1因为成员函数的第一个参数为隐藏的this 注意以下5个运算符不能重载。这个经常在笔试选择题中出现。 .* :: sizeof ?: .
看下面这段代码
// 全局的operator class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;
}
//private:int _year;int _month;int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的那么问题来了封装性如何保证? // 这里其实可以用我们后面学习的友元解决或者干脆重载成成员函数。
bool operator(const Date d1, const Date d2)
{return d1._year d2._year d1._month d2._month d1._day d2._day;
}
void Test () {Date d1(2018, 9, 26);Date d2(2018, 9, 27);cout(d1 d2)endl;
}
class Date
{
public:
Date(int year 1900, int month 1, int day 1){_year year;_month month;
_day day; }
// bool operator(Date* this, const Date d2) // 这里需要注意的是左操作数是this指向调用函数的对象 bool operator(const Date d2)
{return _year d2._year; _month d2._month _day d2._day;
} private:int _year;int _month;int _day;
};赋值运算符重载
赋值运算符重载格式 ·参数类型: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 ;
};赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;
}int _year;int _month;int _day;
};
// 赋值运算符重载成全局函数注意重载成全局函数时没有this指针了需要给两个参数 Date operator(Date left, const Date right)
{if (left ! right){left._year right._year;left._month right._month;left._day right._day;
}return left;
}
// 编译失败:
// error C2801: “operator ”必须是非静态成员原因:赋值运算符如果不显式实现编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载就和编译器在类中生成的默认赋值运算符重载冲突了故赋值运算符重载只能是类的成员函数。
还需注意的是赋值运算符重载函数和拷贝构造函数类似在调用需要动态申请内存空间的类时需要自己显示实现否则会使用编译器默认的赋值运算符重载函数进行值拷贝最终同一块空间又将被析构两次。
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;}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;Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 s1;return 0;const成员
将const修饰的“成员函数”称之为const成员函数const修饰类成员函数实际修饰该成员函数 隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。 还需注意以下几点 1.const对象不能调用非const成员函数。权限放大 2.非const对象可以调用const成员函数。权限缩小 3.const成员函数内不能调用其他非const成员函数。权限放大 4.非const成员函数内可以调用其他const成员函数。权限缩小