用手机可以建设一个手机网站吗,安庆网站建设电话,自己做网站 什么软件,新手怎么推广自己的店铺本文已收录至《C语言》专栏#xff01; 作者#xff1a;ARMCSKGT 目录
前言
正文
构造函数
对比C和C的初始化
构造函数的使用与特性
默认构造函数
C11关于默认构造缺陷的补丁
析构函数
析构函数特性
默认析构和自定义析构
拷贝构造函数
问题聚焦
拷贝构造的定… 本文已收录至《C语言》专栏 作者ARMCSKGT 目录
前言
正文
构造函数
对比C和C的初始化
构造函数的使用与特性
默认构造函数
C11关于默认构造缺陷的补丁
析构函数
析构函数特性
默认析构和自定义析构
拷贝构造函数
问题聚焦
拷贝构造的定义和特性
使用场景
构造函数小结
运算符重载
定义方式
特性
使用说明
运算符重载原理
赋值运算符重载
前后置和--
const修饰this
取地址重载和const取地址重载
最后 前言
C类在设计之时规定类中有六个默认的成员函数这些成员函数天生就存在而且功能都很强大类和对象的关键点就在这六个默认成员函数的学习本篇将会逐一介绍这六个成员函数让我们向类和对象的深处出发 正文 C规定在每个类中有六个默认函数成员 函数功能重要性构造函数定义和初始化成员变量重要析构函数释放申请的内存空间(销毁成员变量)重要拷贝构造(函数)实现对象间的深拷贝重要赋值重载(函数)实现对象间的深赋值重要取地址重载(函数)自定义类对象取地址操作符功能一般const取地址重载(函数)自定义对象取地址操作符功能const修饰返回的地址一般这些函数我们不写编译器也会自己写一个默认的函数代替对应函数 //以日期类的方式初见六大默认成员函数
class Date
{
public://构造函数Date(size_t year 1970, size_t month 1, size_t day 1){_year year;_month month;_day day;}//析构函数~Date(){_year 0;_month 0;_day 0;}//拷贝构造函数(简称拷贝构造)Date(const Date d){_year d._year;_month d._month;_day d._day;}//赋值重载函数(赋值运算符重载)Date operator(const Date d){_year d._year;_month d._month;_day d._day;return *this;}//取地址运算符重载Date* operator(){return this;}//const返回取地址运算符重载const修饰thisconst Date* operator() const{return this;}private:size_t _year;size_t _month;size_t _day;}; 构造函数 对比C和C的初始化 我们在使用C语言实现一些例如顺序表栈等简单数据结构时一般都会写一个初始化函数Init防止野指针访问但这样很容易让我们忘记去调用 在C中为了避免这种事情引入了构造函数在对象实例化时编译器自动调用构造函数进行初始化所以构造函数是为对象自动初始化而生的 我们以日期对象为例对比C与C的初始化方案 //C语言实现日期功能
typedef struct C_Date //日期数据结构体
{size_t _year;size_t _month;size_t _day;
}C_Date;void InitDate(C_Date* L)//初始化函数
{L-_year 0;L-_month 0;L-_day 0;
} //C实现日期类
class CPP_Date //日期对象
{
public://默认构造CPP_Date(size_t year){_year year;_month 1;_day 1;}//重载实现多种默认构造方式CPP_Date(size_t year, size_t month, size_t day){_year year;_month month;_day day;}private:size_t _year;size_t _month;size_t _day;
}; 可以发现C中类融入构造函数后只需要实例化对象就能同时完成初始化非常方便而且结合C的缺少参数,函数重载等新特性可以让初始化丰富多样增强程序可用性 对比C与C可以发现C非常贴心就像汽车中的手动挡与自动挡但两者在不同场合各有千秋在程序开发上C的更胜一筹在较为底层且需要更细节的程序控制时C语言更胜一筹不过一般在程序开发中C与C可以搭配一起编程 构造函数的使用与特性 构造函数是一个特殊的成员函数名字与类名相同创建类类型对象时由编译器自动调用以保证每个数据成员都有 一个合适的初始值并且在对象整个生命周期内只调用一次(实例化时被编译器调用)。 构造函数是特殊的成员函数需要注意的是构造函数虽然名称叫构造但是构造函数的主要任务并不是开空间创建对象而是初始化对象成员变量。 构造函数的定义方式 class Test
{
public: //构造函数必须公开Test(参数) {} //构造函数的函数名与类名相同且没有返回值
}; 构造函数的特性 函数名与类名相同。无返回值(不需要写返回值类型void也不需要写)。对象实例化时编译器自动调用对应的构造函数。构造函数可以重载支持多个构造函数但是默认的构造函数只有一个。 使用构造函数初始化日期对象 //C实现日期类
class CPP_Date //日期对象
{
public://默认构造函数只允许出现一种显示如果定义全缺省就可以代替默认构造了//CPP_Date()//默认构造函数初始化-功能比较局限//{// _year 0;// _month 0;// _day 0;//}//构造函数可以重载实现多种构造方式CPP_Date(size_t year){_year year;_month 1;_day 1;}//一般使用全缺省值方式代替默认构造-智能方便CPP_Date(size_t year 1970, size_t month 1, size_t day 1){_year year;_month month;_day day;}private:size_t _year;size_t _month;size_t _day;
};int main()
{//CPP_Date d(); //注意这种调用默认构造的方式是错误的调用默认构造不需要加()CPP_Date d1;CPP_Date d2(2022);CPP_Date d3(2023,3,12);//可以通过调用不同的构造函数实例化多个不同的对象
} 默认构造函数 如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦 用户显式定义编译器将不再生成。 那有人可能会问既然编译器会自动生成构造函数我们为什么还要去写 注意编译器生成的默认构造函数对内置类型不做处理对自定义类型会调用该对象的构造函数 数据类型区分 内置类型int,char,double等。自定义类型structclass等这些自定义类型可以有自己的默认构造函数。 编译器默认构造对内置类型的初始化 编译器默认构造初始化内置类型 自定义默认构造: //C实现日期类
class CPP_Date //日期对象
{
public://无参构造函数//CPP_Date()//{// _year 0;// _month 0;// _day 0;//}//全缺省默认构造CPP_Date(size_t year 1970, size_t month 1, size_t day 1){_year year;_month month;_day day;}//当存在两种实例化相同的构造方法时只能存在一种否则在调用时会出错//例如 (CPP_Date d;) 编译器无法判断d对象调用的是哪一个构造函数private:size_t _year;size_t _month;size_t _day;
}; 自定义默认构造完成内置类型的初始化 无参的构造函数和全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。 注意无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数都可以认为是默认构造函数。 C11关于默认构造缺陷的补丁 为了解决内置类型无法被编译器生成的默认构造初始化的问题在C11中支持内置类型在声明阶段给缺省值当编译器生成默认构造函数时使用这些缺省值初始化内置类型。 class CPP_Date //日期对象
{
public:private:size_t _year 1970; //声明阶段赋予缺省值size_t _month 1;size_t _day 1;//注意类成员变量定义在构造函数的初始化列表并不是在声明阶段
}; 所以对于内置类型要么自定义默认构造函数或者在声明时赋予缺省值对于自定义类型编译器会调用对应的构造函数 析构函数 我们在写顺序表时在结束使用时需要调用销毁函数是否内存空间但是我们可能经常也会忘记释放空间析构函数就是用来销毁对象和释放空间的 析构函数是特殊的成员函数其功能与构造函数相反析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的。而对象在销毁时(生命周期结束时)会自动调用析构函数完成对象中资源的清理工作。 析构函数特性 析构函数名是在类名前加上字符 ~。无参数无返回值类型(void也不需要写)。一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。析构函数不能重载。对象生命周期结束时C编译系统系统自动调用析构函数。 对于析构函数最大的特性是在对象生命周期结束时被自动调用与构造函数的区别在于不支持重载也就是说一个对象只能有一个析构函数 默认析构和自定义析构 如果我们不写析构函数编译器也会默认生成但编译器生成的析构函数对内置类型仍然不做处理对自定义类型会调用对应的析构函数处理 //析构函数定义
class Test
{~Test() {//释放方法}
}; //简易栈对象
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;cout 构造函数初始化栈对象 endl;}void Push(DataType data){// CheckCapacity();_array[_size] data;_size;}// 其他方法...~Stack(){if (_array){free(_array);_array NULL;_capacity 0;_size 0;}cout 析构函数释放栈对象空间 endl;}
private:DataType* _array;int _capacity;int _size;
}; 对于自定义类型和内置类型是否存在析构函数进行释放影响都不大当涉及我们自己动态开辟空间时就需要使用析构函数释放空间 注意创建哪个类的对象则调用该类的析构函数销毁那个类的对象则调用该类的析构函数对于析构函数只要是对象中涉及动态内存申请则需要使用析构函数释放 拷贝构造函数 拷贝构造也是构造函数的一种但是参数不同则构成重载功能和特性与构造函数不同 问题聚焦 我们在创建对象时如果需要拷贝一个一模一样的对象也就是复制一个对象那么就需要创建一个一模一样的对象后将数据拷贝一份过去。 编译器默认生成的拷贝构造函数只支持浅拷贝也就是值拷贝对于内置类型浅拷贝是没有影响的但是如果对象中申请了空间那么该对象中必定有一个指针指向该空间的首地址浅拷贝只会讲该地址拷贝一份给另一个对象那么会导致一个严重的问题就是两个对象申请的空间是同一个地址在增删查改和析构时拷贝的对象析构函数会对同一片空间进行修改和重复释放一片空间导致异常最后成为野指针问题 浅拷贝只是简单的逐字节拷贝对于对象内部有空间申请的会发生共用空间重复析构的情况而深拷贝是在新对象中开辟一块属于自己的新空间然后将数据逐一拷贝过来新旧对象之间的数据相互独立 浅拷贝下对象共用一块空间 拷贝构造的定义和特性 拷贝构造函数只有单个形参该形参是对本类类型对象的引用(一般常用const修饰)在用已存在的类类型对象创建新对象时由编译器自动调用。 特性 拷贝构造函数是构造函数的一个重载形式。拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用。 定义和调用 class Test
{//拷贝构造形参的定义方式是唯一的(const 类型 )Test(const Test T) {//拷贝方法} //定义方式
};int main()
{Test t1;Test t2(t1); //调用拷贝构造方式一Test t3 t2; //调用拷贝构造方式二return 0;
} 定义须知 编译器默认生成的拷贝构造只是简单拷贝只能用于非动态内存开辟的空间拷贝构造的参数定义方式是唯一的 拷贝构造函数函数名与构造函数相同不过参数类型为类对象的引用不加引用则会发生无穷拷贝(因为形参是拷贝而来的这样形参会陷入无限递归拷贝形参) 形参无穷递归拷贝 使用场景 //日期类示例
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); //拷贝构造方式一Date d3 d2; //拷贝构造方式二return 0;
} 像日期类这样没有动态内存申请的对象使用编译器默认生成的拷贝构造进行浅拷贝即可但是向数据结构需要动态内存申请等相对复杂的对象就需要自定义拷贝构造实现深拷贝 拷贝构造函数典型调用场景 使用已存在对象创建新对象函数参数类型为类类型对象函数返回值类型为类类型对象说明 对于深拷贝是有一定代价的为了提高程序效率一般对象传参时尽量使用引用类型返回时根据实际场景能用引用尽量使用引用默认拷贝构造函数与默认构造函数名相同当我们只写拷贝而不写构造时编译器就会报错因为此时的拷贝会被误以为是默认构造函数也就是说默认拷贝构造函数存在的前提是默认构造函数已存在。构造函数小结 构造大家族到这里基本内容就介绍的差不多了 类型用途处理情况构造函数初始化对象不对内置类型作处理自定义类型调用对应构造函数析构函数销毁对象也不对内置类型作处理自定义类型调用对应析构函数拷贝构造函数拷贝对象只能对简单内置类型做处理自定义类型需要自己实现深拷贝运算符重载 C为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数也具有其返回值类型函数名字以及参数列表其返回值类型与参数列表与普通的函数类似。 运算符重载的引入主要是为了解决基本运算符功能不足以满足自定义类型需求的情况例如日期类的加减以及前后置和--等需要自定义对象运算符的功能 定义方式 返回值类型 operator操作符(参数)
{//自定义操作符功能
}// operator 是运算符重载的关键字 特性 不能通过连接其他符号来创建新的操作符比如operator 重载操作符必须有一个类类型参数 用于内置类型的运算符其含义不能改变例如内置的整型不 能改变其含义 作为类成员函数重载时其形参看起来比操作数数目少一个因为成员函数的第一个参数为隐藏的this 对于内置运算符不能改变其含义 operator操作符 就是函数名 注意这5个运算符不能重载(1) .* (2) :: (3)sizeof(4)?:(5) . //日期类
class Date
{
public:Date(int year 1900, int month 1, int day 1){_year year;_month month;_day day;}//为了保证封装性类成员变量一般是私有所以运算符重载一般定义在类内部bool operator(const Date d){//这里相当于 *this 与 d 的 比较return _year d._year _month d._month _day d._day;}private:int _year;int _month;int _day;
};int main()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);cout (d1 d2) endl; //这里相当于调用 d1.operator(d2)return 0;
} 使用说明 operator 函数中的操作数取决于参数个数operator 一般定义在类中方便访问类成员当定义在类中时运算符左边的对象默认是*thisoperator 如果定义在类外则无法访问类的所有成员此时要么在类中定义特定函数获取私有成员要么声明为友元函数但是大部分场景下都没有定义在类中更合适运算符重载原理 运算符重载的原理与函数重载原理基本相同也是对函数名修饰。 如果定义在类中在Linux环境下修饰规则为_ZN4D类名运算符英文简称ERKS_ 赋值运算符重载 通过上面的铺垫我们就要介绍下一个默认成员那就是赋值重载函数 class Test
{}; 赋值重载是将一个对象赋值給另一个对象与拷贝构造相似但是拷贝构造是通过一个对象去实例化一个相同的对象而赋值重载的前提是两个对象已经实例化存在相互之间再赋值本质区别在于一个是对象尚未实例化另一个是两个对象都已存在当两个对象都被创建并发生赋值行为时才叫做赋值重载 对于这种自定义类型复制的问题就会涉及深拷贝和浅拷贝的问题所以赋值重载是很有必要的 //日期类
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; //返回赋值的对象的引用(也就是自己)//这里之所以返回自己的引用是因为会发生 d1 d2 d3 这样连等的情况//使用引用可以有效避免拷贝}private:int _year;int _month;int _day;
}; 赋值运算符重载格式 参数类型const T传递引用可以提高传参效率返回值类型T返回引用可以提高返回的效率有返回值目的是为了支持连续赋值检测是否自己给自己赋值返回*this 要复合连续赋值的含义赋值运算符只能重载成类的成员函数不能重载成全局函数 如果赋值运算符重载成全局函数就没有this指针了需要给两个参数而且此时编译器就会报错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;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 s1;return 0;
} 前后置和-- 对于自定义对象前后置和--还是经常会使用到的 class Test
{Test operator() {} //前置Test operator(int) {} //后置Test operator--() {} //前置--Test operator--(int) {} //后置--
}; //以日期类进行介绍
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;}// 后置
// 注意后置是自己1然后返回1之前的值
// 而temp是临时对象因此只能以值的方式返回不能返回引用Date operator(int){Date temp(*this);_day 1;return temp;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1;d1;return 0;
} 原理前置和后置都是一元运算符为了让前置与后置形成能正确重载C规定后置重载时多增加一个int类型的参数但调用函数时该参数不用传递编译器自动传递(前后置--实现与相同)。 const修饰this 将const修饰的“成员函数”称之为const成员函数const修饰类成员函数实际修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。 const常被用来修饰引用和指针使用const修饰可以提高程序的健壮性 使用场景 被(指针)指向对象是常亮或临时变量被引用对象是常亮或临时变量这些对象必须使用const修饰避免权限放大的问题 修饰this指针格式 有小伙伴可能会疑惑this指针我们不能显示定义那么如果要const修饰怎么办 对于this指针的修饰格式为: class Test
{void Fun() const //将const加在函数参数后即可{//函数方法}
}; //设想以下日期类代码的运行结果
class Date
{
public:Date(int year, int month, int day){_year year;_month month;_day day;}void Print(){cout Print() endl;cout year: _year endl;cout month: _month endl;cout day: _day endl endl;}void Print() const{cout Print()const endl;cout year: _year endl;cout month: _month endl;cout day: _day endl endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};int main()
{Date d1(2022, 1, 13);d1.Print();const Date d2(2022, 1, 13);d2.Print();return 0;
} 代码运行结果 总之const修饰this指针可以起到权限平移的作用 取地址重载和const取地址重载 这两个默认成员函数一般不用重新定义 编译器默认会生成。 class Date
{
public:Date* operator() //返回对象的地址{return this;}const Date* operator() const //返回const修饰对象的地址{return this;}
private:int _year; // 年int _month; // 月int _day; // 日
}; 这两个运算符一般不需要重载使用编译器生成的默认取地址的重载即可只有特殊情况才需要重载比如想让别人获取到指定的内容 最后
以上就是 类和对象 - 中 的全部内容了本篇介绍了类的六大默认成员函数对于构造函数何时用编译器自动生成的何时自己实现都需要依照情况而定对于每个成员其规则和细节都很多需要我们在长期的使用中去牢固的掌握掌握了这六大成员函数的使用那么你将对类和对象的掌握又进一步
本次 C类和对象 - 中 就介绍到这里啦希望能够尽可能帮助到大家。
如果文章中有瑕疵还请各位大佬细心点评和留言我将立即修补错误谢谢 其他文章阅读推荐 C 类和对象 - 上 C 入门知识 数据结构初阶 栈 欢迎读者多多浏览多多支持!