网站建设模版,项目管理pmp,旅游网页制作,交互效果好的移动端网站✨个人主页#xff1a; Yohifo #x1f389;所属专栏#xff1a; C修行之路 #x1f38a;每篇一句#xff1a; 图片来源 I do not believe in taking the right decision. I take a decision and make it right. 我不相信什么正确的决定。我都是先做决定#xff0c;然后把… ✨个人主页 Yohifo 所属专栏 C修行之路 每篇一句 图片来源 I do not believe in taking the right decision. I take a decision and make it right. 我不相信什么正确的决定。我都是先做决定然后把事情做好。 文章目录前言正文初始化列表️原初始化方式️使用初始化列表️注意事项explicit关键字️隐式转换️限制转换static修饰️static在类中匿名对象️使用场景友元️友元函数️友元类内部类️特性编译器优化️参数优化️返回优化️编码技巧再次理解类和对象总结前言
在前两篇关于类和对象的文章中我们学习了C类的基本形式、对象的创建与使用以及每个类中都有的六大天选之子默认成员函数现在对类的基本框架已经搭好关于类和对象的学习还存在一些细节深入理解这些细节就是本文的主要目的 正文
先从上篇文章中的结论开始学习 初始化列表
初始化列表是祖师爷认证的成员变量初始化位置初始化列表紧跟在默认构造函数之后形式比较奇怪主要通过 : 、, 和 ()实现初始化
class Date
{
public:Date(int year 1970, int month 1, int day 1):_year(year) //以下三行构成初始化列表, _month(month), _day(day){//………}
private:int _year;int _month;int _day;
};学习初始化列表前先来简单回顾下原初始化方式
️原初始化方式
之前我们的默认构造函数是这样的
class Date
{
public:Date(int year 1970, int month 1, int day 1){//此时是赋值变量在使用前仍然是随机值状态_year year;_month month;_day day;}
private:int _year;int _month;int _day;
};实例化一个对象调试结果如下 正式赋值前已被初始化为随机值 并且如果我们的成员变量中新增一个 const 修饰的成员
private:int _year;int _month;int _day;const int _ci程序运行结果就会变成这样
直接编译报错了证明当前初始化方式存在很大问题
原因
原默认构造函数是以赋值的方式实现“初始化”的被赋值的前提是已存在而存在必然伴随着初始化行为此时由编译器负责也就是编译器的惯用手段给变量置以随机值实现初始化成员变量在被赋值前已经被初始化了const 修饰的成员具有常性只能初始化一次也就意味着此时的成员 _ci 已经被初始化为随机值并且被 const 修饰具有常性无法被赋值总结 原赋值的初始化方式在某些场景下行不通
原赋值初始化方式的缺点
无法给 const 修饰成员初始化无法给 引用 成员初始化无法给 自定义 成员初始化(且该类没有默认构造函数时)
此时祖师爷看不下去了决定打造一种新的初始化方式初始化列表并为初始化列表指定了一个特殊位置默认构造函数之后
️使用初始化列表
初始化列表基本形式
紧跟在默认构造函数之后首先以 ; 开始初始化格式为 待初始化对象(初始值)之后待初始化成员都以 , 开头不存在结尾符号除了第一个成员用 ; 开头外其他成员都用 , 开头
class Date
{
public:Date(int year 1970, int month 1, int day 1, const int ci 0, const int ref 0):_year(year) //以下三行构成初始化列表, _month(month), _day(day), _ci(ci) //const 成员能初始化, _ref(ref) //引用成员也能初始化{//………}
private:int _year;int _month;int _day;const int _ci;const int _ref;
};在初始化列表的加持下程序运行结果如下 进入默认构造函数体内时成员变量已被初始化 初始化列表能完美弥补原赋值初始化的缺点
如此好用的初始化方式为何不用呢 祖师爷推荐 尽量使用初始化列表进行初始化全能又安心
强大的功能靠着周全的规则支撑初始化列表有很多注意事项使用规则
️注意事项
使用方式
; 开始 , 分隔() 内写上初始值
注意
初始化列表中的成员只能出现一次初始化列表中的初始化顺序取决类中的声明顺序以下几种类型必须使用初始化列表进行初始化 const 修饰引用 类型自定义类型且该自定义类型没有默认构造函数
建议
优先选择使用初始化列表列表中的顺序与声明时的顺序保持一致
规范使用初始化列表高高兴兴使用类 explicit关键字
explicit 是新的关键字常用于修饰 默认构造函数限制隐式转换使得程序运行更加规范
️隐式转换
所谓隐式转换就算编译器在看到赋值双方类型不匹配时会将创建一个同类型的临时变量将 左边的值拷贝给临时变量再拷贝给 右边的值比如
int a 10;
double b 3.1415926;
a b; //成功赋值将会截取浮点数 b 的整数部分拷贝给临时变量再赋值给 a具体赋值过程如下 需要借助一个同类型的临时变量 将此思想引入类中假设存在这样一个类
class A
{
public://默认构造函数A(int a 0):_a(a){//表示默认构造函数被调用过cout A(int a 0) endl;}//默认析构函数~A(){_a 0;//表示默认析构函数已被调用cout ~A endl;}//拷贝构造函数A(const A a){_a a._a;cout A(const A a) endl;}//赋值重载函数A operator(const A a){if(this ! a){_a a._a;}cout A operator(const A a) endl;return *this;}private:int _a;
};以下语句的赋值行为是合法的
int main()
{A aa1 100; //注此时整型 100 能赋给自定义类型return 0;
}合法原因
类中只有一个整型成员赋值时会先生成同类型临时变量即调用一次构造函数再调用拷贝构造函数将临时变量的值拷贝给 aa1 我们可以看看打印结果是否真的如我们想的一样 结果只调用了一次构造函数
难道编译器偷懒了
并不是实际这是编译器的优化与其先生成临时变量再拷贝不如直接对目标进行构造这样可以提高效率
这是编译器的优化行为大多数编译器都支持
看代码会形象些
A aa1 100; //你以为的
A aa1(100); //实际编译器干的优化单参数类赋值时编译器有优化那么多参数类呢
class B
{
private:int _a;int _b;int _c;
}多参数类编译器也会有优化
B bb1 {1, 2, 3}; //你以为的
B bb1(1, 2, 3); //实际上编译器优化的编译器是这样认为的构造临时变量拷贝构造不如让我直接给你构造
这是编译器针对隐式转换做出的优化行为
不难发现这样的隐式转换虽然方便但会影响代码的可读性和规范性我们可以通过关键字explicit 限制隐式转换行为
️限制转换
在默认构造函数前加上explicit修饰
class A
{
public://默认构造函数//限制隐式转换行为explicit A(int a 0):_a(a){//表示默认构造函数被调用过cout A(int a 0) endl;}
private:int _a;
};此时再次采用上面那种赋值方式会报错
A aa1 100; //报错此时编译器无法进行隐式类型转换优化也就不存在了何时采用 explicit 修饰
想提高代码可读性和规范性时 static修饰
static 译为静态的修饰变量时变量位于静态区生命周期增长至程序运行周期
static 有很多讲究可不敢随便乱用
修饰普通局部变量时使其能在全局使用修饰全局变量时破坏其外部链接属性static 修饰时只能被初始化一次static 不能随便乱用 ️static在类中
类中被 static 修饰的成员称为 静态成员变量 或 静态成员函数
静态成员为所有类对象所共享不属于某个具体的对象存放在静态区静态成员变量必须在类外定义定义时不添加static关键字类中只是声明类静态成员可用 类名::静态成员 或者 对象.静态成员 来访问静态成员函数没有隐藏的 this 指针不能访问任何 非静态成员静态成员也是类的成员受 public 、protected 、private 访问限定符的限制
课代表简单总结
静态成员变量必须在类外初始化(定义)静态成员函数 失去了 this 指针但当为 public 时可以通过 类名::函数名直接访问
class Test
{
public://Test(int val 0, static int sVal 0)// :_val(val)// , _sVal(sVal) //非法的初始化列表也无法初始化静态成员变量//{}static void Print(){//cout _val endl; //非法的没有 this 指针无法访问对象成员cout _sVal endl;}
private:int _val;static int _sVal; //静态成员变量
};int Test::_sVal 0; //静态成员变量必须在类外初始化(定义)需指定属于哪个类静态成员变量只能初始化一次
静态成员函数没有 this 指针
静态成员函数是为静态成员变量而生
如此刁钻的成员变量究竟有何妙用呢
答 有的存在即合理
利用静态成员变量只能初始化一次的特定写出函数统计程序运行中调用了多少次构造函数
class Test
{
public:Test(int val 0):_val(val){_sVal; //利用静态成员变量进行累加统计}static void Print(){cout _sVal;}
private:int _val 0;static int _sVal; //静态成员变量
};int Test::_sVal 0; //静态成员变量必须在类外初始化(定义)int main()
{Test T[10]; //调用十次构造函数//通过静态成员变量验证cout 程序共调用了;Test::Print();cout 次成员函数 endl;return 0;
}输出结果如下 得益于 static 修饰的成员变量统计 注意
静态成员函数 不可以调用 非静态成员变量没有 this 指针非静态成员函数 可以调用 静态成员变量具有全局属性
匿名对象
C语言结构体支持创建匿名结构体C 则支持创建匿名对象 匿名对象使用如下
//假设存在日期类 Date
int main()
{Date(); //此处就是一个匿名对象return 0;
}匿名对象拥有正常对象的所有功能缺点就是生命周期极短只有一行
//演示
Date(2023, 2, 10); //匿名对象1 初始化
Date().Print(); //匿名对象2 调用打印函数//注意两个匿名对象相互独立创建 匿名对象2 时 匿名对象1 已被销毁️使用场景
匿名对象适合用于某些一次性场景也适合用于优化性能
Date(2023, 2, 10).Print(); //单纯打印日期 2023 2 10//函数返回时
Date d(2002, 1, 1);
return d;
//等价于
return Date(2002, 1, 1); //提高效率友元
新增关键字 friend 译为朋友常用于外部函数在类中的友好声明
类中的成员变量为私有类外函数无法随意访问但可以在类中将类外函数声明为友元函数此时函数可以正常访问类中私有成员
友元函数会破坏类域的完整性有利有弊 注意
友元是单向关系友元不具有传递性友元不能继承友元声明可以写在类中的任意位置
️友元函数
friend 修饰函数时称为友元函数
class Test
{
public://声明外部函数 Print 为友元函数friend void Print(const Testd);Test(int val 100):_val(val){}private:int _val;
};void Print(const Test d)
{cout For Friend d._val endl;
}int main()
{Test t;Print(t);
}程序正常编译结果如下 友元函数可以用来解决外部运算符重载函数无法访问类中成员的问题但还是不推荐这种方法
️友元类
friend 修饰类时称为友元类
class Time
{friend class Date; // 声明日期类为时间类的友元类则在日期类中就直接访问Time类
public:Time(int hour 0, int minute 0, int second 0): _hour(hour), _minute(minute), _second(second){}
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 SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量_t._hour hour;_t._minute minute;_t._second second;}
private:int _year;int _month;int _day;Time _t;
};友元有种继承的感觉但它不是继承它也不支持继承 内部类
将类B写在类A中B 称作 A 的内部类
class A
{
public://B 称作 A 的内部类class B{private:int _b;}
private:int _a;
}内部类天生就是外类的友元类
也就是说B 天生就能访问 A 中的成员
️特性
内部类在C中比较少用属于了解型知识
内部类的最大特性就是使得内部类能受到外类的访问限定符限制
内部类特点
独立存在天生就是外类的友元
用途
可以利用内部类将类隐藏现实中比较少见
注意
内部类跟其外类是独立存在的计算外类大小时是不包括内部类大小的内部类受访问限定符的限定假设为私有内部类无法被直接使用内部类天生就算外类的友元即可以访问外类中的成员而外类无法访问内部类 编译器优化
前面说过编译器存在优化行为这里就来深入探讨一下 把上面的代码搬下来用一下方便观察发生了什么事情
class A
{
public://默认构造函数A(int a 0):_a(a){//表示默认构造函数被调用过cout A(int a 0) endl;}//默认析构函数~A(){_a 0;//表示默认析构函数已被调用cout ~A endl;}//拷贝构造函数A(const A a){_a a._a;cout A(const A a) endl;}//赋值重载函数A operator(const A a){if(this ! a){_a a._a;}cout A operator(const A a) endl;return *this;}private:int _a;
};️参数优化
在类外存在下面这些函数
void func1(A aa)
{}int main()
{func1(100);return 0;
}预计调用后发生了这些事情 构造(隐式转换) - 拷贝构造(传参) - 构造(创建aa接收参数)
编译器会出手优化
实际只发生了这些事情 构造(直接把aa构造为目标值) ️返回优化
除了优化传参外编译器还会优化返回值
A func2()
{return A(100);
}int main()
{//func1(100);A a func2();return 0;
}预计调用后发生了这些事情 构造(匿名对象的创建) - 构造(临时变量) - 拷贝构造(将匿名对象拷贝给临时变量) - 拷贝构造(将临时变量拷贝给 a)
编译器会出手优化
实际只发生了这些事情 构造(直接把函数匿名对象值看作目标值构造除出 a) 现在可以证明编译器会将某些非必要的步骤省略点执行关键步骤
优化场景
涉及拷贝构造构造时编译器多会出手传值返回时涉及多次拷贝构造编译器也会出手
注意
引用传参时编译器无需优化因为不会涉及拷贝构造实际编码时如果能采用匿名构造就用匿名构造会加速编译器的优化接收参数时如果分成两行先定义、再接收编译器无法优化效率会降低
编译器只能在一行语句内进行优化如果涉及多条语句编译器也不敢擅自主张
️编码技巧
下面是一些编码小技巧可以提高程序运行效率
接收返回值对象时尽量拷贝构造方式接收不要赋值接收函数返回时尽量返回匿名对象函数参数尽量使用 const 参数 再次理解类和对象 出自比特教育科技 总结
以上就是 类和对象下的全部内容了我们在本文章学习了一些类和对象的小细节比如明白了善用初始化列表的道理、懂得了友元函数的用法、了解了编译器的优化事实、最后还简单理解了类和对象与现实的关系相信在这些细节的加持之下对类和对象的理解能更上一层楼
如果你觉得本文写的还不错的话可以留下一个小小的赞你的支持是我分享的最大动力
如果本文有不足或错误的地方随时欢迎指出我会在第一时间改正 …相关文章推荐 类和对象上 类和对象中 C入门基础