专门做尾单的那个网站叫啥,网站建设后需要交费吗,营销型网站怎么收费,黄冈网站建设流程相关代码gitee自取#xff1a;
C语言学习日记: 加油努力 (gitee.com)
接上期#xff1a;
【C初阶】五、类和对象 #xff08;日期类的完善、流运算符重载函数、const成员、“”取地址运算符重载#xff09;-CSDN博客 目录 一 . 初始化列表 构造函数…
相关代码gitee自取
C语言学习日记: 加油努力 (gitee.com) 接上期
【C初阶】五、类和对象 日期类的完善、流运算符重载函数、const成员、“”取地址运算符重载-CSDN博客 目录 一 . 初始化列表 构造函数体内赋值 初始化列表的作用和使用 初始化列表的作用 初始化列表的使用 补充explicit关键字 二 . static成员 static成员概念 static成员特性 三 . 友元 友元函数 友元类 四 . 内部类 内部类的概念 内部类的特性 补充拷贝对象时的一些编译器优化 补充调用拷贝构造函数还是“”赋值运算符重载函数 优化一“未初始化对象 内置类型” 优化二“通过匿名对象调用函数” 优化三“通过内置类型调用函数” 优化四“未初始化对象接收函数传值返回的临时对象” 本篇博客相关代码 Test.cpp文件 -- C文件 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 一 . 初始化列表 构造函数体内赋值 在创建对象时编译器通过调用构造函数给对象中各个成员变量一个合适的初始值在调用完构造函数执行完构造函数体内的赋值后虽然对象中已经有了一个初始值但是还不能将其称为对对象中成员变量的初始化构造函数体中的语句只能将其称为赋初值而不能称为初始化。因为初始化只能初始化一次而构造函数体内是可以多次赋值的 初始化列表的作用和使用 初始化列表的作用 初始化列表的作用可以简单理解成在成员变量定义时先对其进行初始化 先执行初始化列表再执行构造函数体中的内容 当一个类的成员变量中有引用变量类型、const成员变量、自定义类型成员且该类中没有默认构造函数时当有这三类成员变量时就必须要使用到初始化列表了 对于成员变量中的 引用变量类型 和 const成员变量这两种成员变量都要求在定义时就得被初始化构造函数体内赋值无法满足这个条件 而对于成员变量中的自定义类型成员且该类中没有默认构造函数时这种成员变量初始化时编译器通常会调用其默认构造函数但因为没有默认构造函数只有有参构造函数所以需要在编译器调用默认构造函数前就先通过初始化列表调用其有参构造函数进行初始化这也是构造函数体内赋值无法实现的 --------------------------------------------------------------------------------------------- 初始化列表的使用 使用格式以一个冒号开始接着是一个以逗号分隔的数据成员列表每个“成员变量”后面跟一个放在括号中的初识值或表达式 注意事项 每个成员变量在初始化列表中只能出现一次初始化只能初始化一次 类中包含以下成员必须放在初始化列表位置上进行初始化引用成员变量、const成员变量、自定义类型成员且该类没有默认构造函数时 图示 尽量使用初始化列表进行初始化因为不管你是否使用初始化列表对于自定义类型成员变量一定会先使用初始化列表初始化 成员变量在类中的声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关所以最好将成员变量的声明次序和其在初始化列表中的先后次序保持一致 图示 补充explicit关键字 构造函数不仅可以构造和初始化对象对于单个参数或者除第一个参数无缺省值其余均有缺省值的构造函数还具有隐式类型转换的作用 使用这种隐式类型转换时代码可读性可能不是很好有些地方可能不允许出现这种隐式类型转化的情况发生这时就可以使用 explicit关键字 来修饰该构造函数这样就会禁止构造函数的隐式转换 图示 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 二 . static成员 static成员概念 声明为static的类成员称为类的静态成员 用static修饰的成员变量称为静态成员变量用static修饰的成员函数称为静态成员函数。 其中静态成员变量是在类中声明在类外初始化的实现 / 定义 --------------------------------------------------------------------------------------------- static成员特性 静态成员是所有类对象共享的不属于某个具体的对象存放在静态区 静态成员变量必须在类外初始化实现 / 定义定义时不用添加static关键字类中只是声明 类静态成员可以使用 类名::静态成员 或者 对象.静态成员 来访问 静态成员函数没有隐藏的this指针所以不能访问任何非静态成员但非静态成员是可以访问类的静态成员函数的因为非静态成员可以找到其对应的类通过类就能找到类中的静态成员函数 只要有办法找到静态成员函数的类域就能访问其中的静态成员函数 静态成员也是类的成员所以也会受到 public 、protected 、private 访问限定符的限制 图示 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 三 . 友元 友元提供了一种突破封装的方式有时能够提供便利。但是友元会增加代码的耦合度一定程度上破坏了封装所以友元不宜多用。友元分为友元函数和友元类 友元函数 上期博客中我们对 “” 和 “” 两个流运算符进行了重载 实现了 “”流运算符重载函数 和 “”流运算符重载函数 实现过程中 我们发现无法将两者实现在类中重载定义为成员变量因为这两个流运算符对左操作数有一定的要求其左操作数一般为 cout输出流对象 或 cin输入流对象如果将其重载为成员函数的话成员函数隐藏的this指针就会抢占其左操作数的位置第一个参数的位置 所以要将这两个流运算符重载为全局函数但这时又会导致类外的全局函数无法访问类中的私有成员变量此时就需要通过友元来解决该问题 友元函数可以直接访问类的私有成员它是定义在类外部的普通函数不属于任何类但是需要在类的内部进行声明声明时需要加 friend关键字 友元函数可以访问类中的私有private和保护protected成员但其不是类的成员函数 友元函数不能用const进行修饰 友元函数可以在类定义的任何地方进行声明且其不受类访问限定符的限制 一个函数可以是多个类的友元函数 友元函数的调用和普通函数的调用原理相同 图示 友元类 友元类的所有成员函数都可以是另一个类的友元函数都可以访问另一个类中的非公有成员。 友元关系是单向的不具有交换性如假设有A类和B类在A类中声明B类为其友元类那么就可以在B类中直接访问A类的私有成员变量但想在A类中访问B类中的私有成员变量则不行 友元关系不能传递如C是B的友元B是A的友元则不代表C就是A的友元 友元关系不能继承继承会在之后了解到 图示 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 四 . 内部类 内部类的概念 如果将一个类定义在另一个类的内部那么这个类就叫做内部类。内部类是一个独立的类它不属于外部类更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何”优越“的访问权限 注意 内部类就是外部类的友元类根据友元类的定义内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元 --------------------------------------------------------------------------------------------- 内部类的特性 将内部类定义在外部类的 public、protected、private 中都是可以的内部类也会受到相应的访问权限限制 内部类中可以直接访问外部类中的static成员不需要通过外部类的 对象 / 类名 计算外部类大小sizeof(外部类)外部类大小和内部类没有任何关系 图示 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 补充拷贝对象时的一些编译器优化 在传参和传返回值的过程中一般编译器会做一些优化减少对象的拷贝这个在一些场景下还是非常有用的注以下的优化都是在建立在同一个表达式的基础上的 补充调用拷贝构造函数还是“”赋值运算符重载函数 调用拷贝构造函数当使用 “” 左操作数是没进行初始化的对象且右操作数是已经存在的对象时这种情况就会调用拷贝构造函数通过右操作数的对象拷贝初始化左操作数的对象 调用“”赋值运算符重载函数当使用“”且左右操作数都是已经存在的对象时这种情况就会调用“”赋值运算符重载函数将右操作数对象赋值给左操作数对象 图示 --------------------------------------------------------------------------------------------- 优化一“未初始化对象 内置类型” 在同一个表达式中在这种情况下第一步先通过内置类型构造出一个临时对象 调用构造函数 第二步再通过临时对象拷贝构造初始化左操作数的未初始化对象 调用拷贝构造函数 优化构造函数 拷贝构造函数 构造函数编译器中这两次调用实际只会调用一次构造函数 不同编译器优化不同这里以VS2020为例 图示 --------------------------------------------------------------------------------------------- 优化二“通过匿名对象调用函数” 在同一个表达式中在这种情况下第一步先初始化匿名对象 调用构造函数 第二步再传值传参匿名对象调用函数 调用拷贝构造函数 优化构造函数 拷贝构造函数 构造函数编译器中这两次调用实际只会调用一次构造函数 不同编译器优化不同这里以VS2020为例 图示 --------------------------------------------------------------------------------------------- 优化三“通过内置类型调用函数” 在同一个表达式中在这种情况下第一步先通过内置类型构造出一个临时对象 调用构造函数 第二步再传值传参临时对象调用函数 调用拷贝构造函数 优化构造函数 拷贝构造函数 构造函数编译器中这两次调用实际只会调用一次构造函数 不同编译器优化不同这里以VS2020为例 图示 --------------------------------------------------------------------------------------------- 优化四“未初始化对象接收函数传值返回的临时对象” 在同一个表达式中在这种情况下第一步函数传值返回时拷贝临时对象进行返回 调用拷贝构造函数 第二步再通过返回的临时对象进行拷贝初始化对象 调用拷贝构造函数 优化拷贝构造函数 拷贝构造函数 拷贝构造函数编译器中这两次调用实际只会调用一次拷贝构造函数 不同编译器优化不同这里以VS2020为例 图示 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本篇博客相关代码 Test.cpp文件 -- C文件 //#define _CRT_SECURE_NO_WARNINGS 1//包含IO流头文件
#include iostream
//展开std命名空间
using namespace std;//class A
//{
//public:
// //全缺省构造函数默认构造函数
// A(int a 0)
// //初始化列表
// :_a(a)
// {
// /*
// * Date类中成员变量 A _aa 初始化时
// * 会调用这个默认构造函数
// * 这个默认构造函数再通过初始化列表
// * 对 _aa 进行初始化
// */
// }
//private:
// //成员变量
// int _a;
//};
//
//
日期类
//class Date
//{
//public:
//
// //Date(int year, int month, int day)
// //{
// // //构造函数体内初始化
// // _year year;
// // _month month;
// // _day day;
//
// // _ref year; //引用变量
// // _n 1; //const变量
// //}
//
// //Date(int year, int month, int day)
// // //初始化列表冒号开始逗号分割
// // :_year(year) //初始化年
// // ,_month(month) //初始化月
// // ,_day(day) //初始化日
// // ,_ref(year) //初始化引用变量
// // ,_n(1) //初始化const变量
// //{
// // /*
// // * 引用变量 和 const变量
// // * 都必须在定义时就进行初始化
// // * 初始化列表就可以解决这个问题
// // */
// //}
//
// Date(int year, int month, int day)
// //初始化列表冒号开始逗号分割
// :_ref(year) //初始化引用变量
// ,_n(1) //初始化const变量
// ,_aa(10) //初始化自定义对象
// /*
// * 引用变量 和 const变量
// * 都必须在定义时就进行初始化
// * 初始化列表就可以解决这个问题
// *
// * 执行到这里
// * 剩下的3个成员变量没有在初始化列表中初始化
// * 但它们也已经被定义了只是因为是内置类型
// * 编译器会默认给它们一个随机值
// * 如果是自定义类型成员变量的话则会去调用
// * 其默认构造函数
// *
// * 如果该自定义类型没有合适的默认构造函数
// *全缺省构造函数、显式定义无参构造函数、
// * 编译器默认生成的构造函数
// * 只有显式定义的有参构造函数那么该对象
// * 的初始化也可以放在初始化列表中。
// * 就像这里的_aa一样在初始化列表中
// * 直接调用其有参构造函数进行初始化。
// * 就是在编译器调用其默认构造函数前
// * 先在初始化列表中调用其有参构造函数进行初始化
// *
// * 初始化列表中的初始化顺序和
// * 成员变量声明的顺序是一样的
// * 所以建议初始化列表顺序和声明顺序保持一致
// */
// {
// //构造函数体内初始化
// _year year; //初始化年
// _month month; //初始化月
// _day day; //初始化日
// }
//
//private:
// //声明成员变量未开空间
//
// int _year 1;
// int _month 1;
// int _day 1;
// /*
// * 这里给的1是缺省值
// * 如果 初始化列表 中没有
// * 对应成员变量的初始化
// * 那么该成员变量的值就会是这里设置的缺省值
// *这里缺省值的功能就类似初始化列表
// */
//
// //引用变量必须在定义时就初始化
// int _ref;
// //const变量必须在定义时就初始化
// const int _n;
// //自定义类型对象
// A _aa;
//};
//
//
//class Stack
//{
//public:
// //栈类构造函数
// Stack(int n 2)
// :_a((int*)malloc(sizeof(int)*n))
// ,_top(0)
// ,_capacity(n)
// /*
// * 虽然说尽量使用初始化列表进行初始化
// * 但也不是说就完全不在构造函数体中写代码了
// *
// * 初始化列表中也可以进行动态内存开辟
// * 但有些初始化或检查的工作初始化列表也不能全部搞定
// * 想这里就没有办法对开辟的动态空间进行检查
// */
// {
// //……
// //构造函数体内
//
// //动态空间检查工作
// if (_a nullptr)
// {
// perror(malloc fail);
// exit(-1);
// }
//
// //数据拷贝工作
// memset(_a, 0, sizeof(int) * n);
//
// //想这里的两种工作初始化列表就完成不了
// }
//
// //……
//private:
// int* _a;
// int _top;
// int _capacity;
//};
//
//class MyQueue
//{
//public:
// MyQueue(int n1 10, int n2 20)
// :_s1(n1)
// ,_s2(n2)
// //通过初始化列表自己控制自定义的初始化值
// //不受制于自定义类型中构造函数的缺省值
// {}
//
//private:
// Stack _s1;
// Stack _s2;
//};
//
主函数
//int main()
//{
// //实例化对象--定义成员变量对象整体定义
// //每个成员变量在 初始化列表 中进行定义
// Date d1(2023, 10, 31);
// /*
// * 对象中的 引用变量(_ref) 和 const变量(_n)
// * 应该在示例化对象时就已经被定义好了
// * 所以我们实例化时不需要传这两个变量的参数
// * 要完成这个步骤就需要依靠 初始化列表了
// */
//
// MyQueue();
// MyQueue(100, 1000);
//
// /*
// * 总结--初始化列表的作用
// *
// * 1、解决必须在定义时就要求初始化的类型变量问题
// * (如引用类型成员变量、const成员变量、
// * 自定义类型中只有有参构造函数的初始化)
// *
// * 2、让一些自定义类型的成员变量自己显式控制初始化值
// *
// * 3、尽量使用初始化列表进行初始化
// * 初始化列表就是成员变量定义的地方
// * 在定义的地方就进行初始化会更好一点
// *80%-100%的工作初始化列表能完成
// * 还有一些工作只能在函数体中完成
// * 所以要将初始化列表和函数体结合起来使用
// */
//
// return 0;
//}//namespace ggdpz
//{
// //定义一个全局变量
// int count 0; //统计一共创建了多少个A对象
//
// /*
// * 因为我们完全展开了stdC标准库
// * 且库中有count同名变量
// * 所以为了避免命名冲突
// * 定义一个命名空间在命名空间中定义自己的count
// */
//}A类
//class A
//{
//public:
// //构造函数
// A() { ggdpz::count; }
//
// //拷贝构造函数
// A(const A t) { ggdpz::count; }
//
// /*
// * 当调用了一次构造函数或拷贝构造函数时
// * 就说明创建了一个对象
// * count即创建了一个对象
// */
//
// //析构函数
// ~A() { }
//
//private:
//
//};
//
创建一个函数
//A func()
//{
// //创建一个A对象
// A aa;
//
// //返回该对象
// return aa;
//}
//
主函数
//int main()
//{
// //创建一个A对象
// A aa;
// //调用func()函数
// func();
//
// ggdpz::count;
// /*
// * 但如果使用全局变量
// * 我这里也可以直接调用让其1
// * 但我们实际并没有创建对象
// * 这时候统计的创建对象个数就是错的了
// *
// * 但如果把count统计变量设置为成员变量的话
// * 就可以解决该问题了
// */
//
// //打印创建了多少个对象
// cout ggdpz::count endl;
//
// return 0;
//}A类
//class A
//{
//public:
// //构造函数
// A() { count; }
//
// //拷贝构造函数
// A(const A t) { count; }
//
// /*
// * 当调用了一次构造函数或拷贝构造函数时
// * 就说明创建了一个对象
// * count即创建了一个对象
// */
//
// //析构函数
// ~A() { }
//
// //Get()方法
// static int GetCount() //只读不写
// {
// /*
// * 静态成员函数没有隐藏的this指针
// * 所以没法在函数体中使用非静态成员变量了
// */
//
// //返回想要获得的count
// return count;
// }
//
//private:
// //私有成员变量
//
// //统计创建对象个数
// static int count; //只是声明
// /*
// * 如果是int count 0;
// * 这里虽然是用于统计创建对象个数
// * 但是每个对象中都有一个count成员变量
// * 而且都是独立的统计计数时不是加到同一个count上
// * 所以起不到统计的作用
// *
// * 所以这里应该使用static进行修饰
// * 就可以让被修饰的成员变量能够被共享
// * 让该类的所有对象共用该成员变量
// * 这样就可以在成员变量上统计创建对象个数了
// *
// * 成员变量使用static进行修饰后就不支持给缺省值了
// * 即不能写成static int count 0;
// * 因为这里给的缺省值实际上是给初始化列表的
// * 而初始化列表中是初始化某个对象
// * static不是对象不会在初始化列表中执行
// * 所以不能这样给缺省值
// */
//};
//
///*
//* static成员变量声明是在类中声明
//* 但实现定义是在类外面实现定义的
//* 只能初始化一次而且其初始化不是在实例化对象时
//* 而是在执行主函数之前就已经初始化了
//* 这也是其不能在类中初始化列表中进行初始化的原因
//*
//* 其实本质还是一个全局变量只不过被放在类域中了
//* 是这个类专属的“全局变量”私有成员变量
//*/
//int A::count 0;
//
创建一个函数
//A func()
//{
// //创建一个A对象
// A aa;
//
// //返回该对象
// return aa;
//}
//
主函数
//int main()
//{
// //创建一个A对象
// A aa;
// //调用func()函数
// func();
//
// /*
// * 此时就不能直接对其进行了
// * 因为是该类专属的“全局变量”
// * 属于整个类属于这个类的所有对象
// * 受到了访问限定符的影响private
// * 所以不能直接对static成员变量进行调用
// */
// A::count;
//
// /*
// * 如果static成员变量是共有的public
// * 则可以通过类域直接访问
// * 也可以通过对象进行调用,
// * 但通过对象进行调用实际是找到该对象的类
// * 然后再进行访问的
// */
// cout A::count endl; //通过类域直接访问
// cout aa.count endl; //通过对象进行调用
// //aa.count - aa是A类的对象,count是A类中的 - A::count
//
// /*
// * 如果static成员变量是私有的private
// * 那要怎么获得它呢这时我们可以定义对应的Get()方法
// * 再通过对象调用对应的Get()方法来获取static成员变量
// * Get()方法只读不写只能读不能写
// */
// //通过对应Get()方法获得count的值
// cout aa.GetCount() endl;
//
// /*
// * 但如果static成员变量是私有的同时又没有对象
// * 要怎么获得static成员变量呢
// *
// * 方式一为了调用static成员变量而创建一个对象
// * 因为是专门为了调用而创建的对象所以调用时要-1
// * 减去创建这个对象的次数不是很好的方式
// *
// * 方式二如果 类型() 匿名对象调用对应的Get()方法
// * 匿名对象的生命周期只有写它的那一行过了这一行后
// * 就会调用析构函数“销毁”匿名对象匿名对象也会调用构造函数
// * 所以如果使用匿名函数查看这里的count的话同样需要-1
// * 也不是很好的方式
// *
// * 方式三将对应的Get()方法设置为静态成员函数
// * 静态成员函数的特点没有this指针方式一和方式二中
// * 因为Get()方法中有this指针所以必须通过对象调用this指针
// * 来使用Get()方法获取count的值。
// * 而将其设置为静态成员函数没有this指针的话在类域中
// * 调用时就不用依靠对象了通过类域进行调用即可
// */
// //方式一通过有名对象
// A aa; //有名对象
// cout aa.GetCount() - 1 endl; //非静态成员函数
//
// //方式二通过匿名对象
// //这里的 A() 就是匿名对象
// cout A().GetCount() - 1 endl; //非静态成员函数
//
// //方式三通过静态成员函数
// cout A::GetCount() endl; //静态成员函数
// //直接通过A类域调用到对应的Get( )方法
// cout A().GetCount() endl;
// /*
// * 设置成静态成员函数后依旧可以通过对象来调用
// * A()匿名对象不是通过this指针调用Get方法的
// * 而是通过A()找到对应类域再在类域中调用到Get方法的
// *静态成员函数只要能找到其对应的类域即可进行调用
// *
// * 总结
// * 一、
// * 可以将静态成员函数和静态成员变量看成是
// * “类域中受到了限制的全局函数和全局变量”
// * 两者本质没太大的区别使用sizeof计算有
// * 静态成员函数或静态成员变量的类时
// * 也不会计算类中静态成员函数或变量的大小。
// *类的大小不包括静态成员函数或变量
// * 使用静态(static)修饰本质是为了和非静态进行区别
// *
// * 二、
// * 静态成员函数和静态成员变量属于这个类的所有对象
// * 而且它们受到类域和访问限定符的限制
// *
// */
//
// return 0;
//}A类单参数构造函数
//class A
//{
//public: //共有成员函数
//
// //有参构造函数
// explicit A(int a)
// //初始化列表
// :_a(a)
// {}
//
//private: //私有成员变量
// int _a 0;
//};
//
日期类多参数构造函数
//class Date
//{
//public: //共有成员函数
//
// //多参数构造函数
// explicit Date(int year, int month 1, int day 1)
// : _year(year)
// , _month(month)
// , _day(day)
// {}
//
//private: //私有成员变量
// int _year;
// int _month;
// int _day;
//};
//
主函数
//int main()
//{
// //正常调用构造函数进行初始化
// A aa1(1);
// A aa2(2);
//
// /*
// * 之前了解过的赋值运算符重载
// * 其两个操作数都是类类型
// * 但如果左操作数是类类型
// * 右操作数是内置类型呢
// */
// A aa3 3; //正常赋值类类型 内置类型
// //左操作数为类类型右操作数为内置类型
// /*
// * 对应构造函数为A(int i)
// * 此时就是将内置类型隐式转换成了自定义类型对象
// * 涉及到类型转换那就会产生一个临时变量具有常属性
// * 这里会产生一个 A(3) 的临时对象通过构造函数产生
// * 再对 A(3) 进行拷贝构造到 aa3 通过拷贝构造函数
// * 所以会先调用构造函数再调用拷贝构造函数
// * 之所以支持这个转换
// * 是因为A类中有 int类型的单参数构造函数
// *单参数构造函数的类型不同支持的隐式转换类型也不同
// */
//
// const A ra 3; //引用类类型 内置类型
// /*
// * 这里对象ra引用的是 类型转换时产生的临时对象(变量)
// * 临时对象(变量)具有常属性其类型是 const A ,
// * 所以引用时的类型应该是const A
// */
//
// //那么如果不想让类类型接收隐式转换的内置类型该怎么办呢
// /*
// * 在构造函数前加上关键字explicit
// * 即在构造函数 A(int a) 前加上变成
// * explicit A(int a) 此时该构造函数就不支持
// * 类类型接收隐式转换的内置类型了
// *加上explicit后的构造函数就不支持隐式类型转换了
// *但显式类型转换还是可以的
// */
//
// //“特殊”多参数构造函数正常调用初始化
// Date d1(2023, 11, 2);
//
// //类类型 “多个内置类型”
// Date d2 (2023, 11, 3);
// //等价于Date d2 3
// /*
// * 对于“特殊”多参数构造函数
// * Date(int year, int month 1, int day 1)
// *
// * 这里是可以编译过的但d2的日期是 3/1/1
// * 因为这里的 (2023113) 其实是逗号表达式
// * 只以最后的值为准即33被放在“年”的位置
// * 而后面的月和天其实是缺省值所以结果是 3/1/1
// *
// * 这里的多参数构造函数是半缺省构造函数
// * 只有一个“年”必须给初始化值所以该构造函数
// * 是支持用一个值进行初始化的。
// *
// * 所以只要一个对象的构造函数能够支持一个值完成初始化
// * 就可以通过 类类型 内置类型 进行初始化
// * 构造函数通过缺省值进行调整
// */
//
// //多参数构造函数
// Date d4 { 2023,11,2 };
// /*
// * C11中是支持类对象多参数初始化的
// * 不过使用的是大括号{}列表初始化
// * 初始化过程中也有产生临时对象
// * 实际执行还是通过构造函数进行初始化的
// */
//
// const Date d5 { 2023,11,2 };
// //证明列表初始化也有产生临时对象其类型也有常属性
//
// return 0;
//}时间类
//class Time
//{
// /*
// * 日期类想访问时间类私有成员变量
// * 应在时间类中将日期类设置为友元类
// *
// * 声明日期类为时间类的友元类
// * 则在日期类中就可以直接访问Time类中
// * 的私有成员变量
// */
// friend class Date; //友元类
// /*
// * 友元关系是单向的
// * 此时Date日期类能访问Time时间类的私有成员变量
// * 但Time时间类不能访问Date日期类的
// */
//
//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
//{
// //友元函数声明
// //“”流运算符重载函数类中友元声明
// friend ostream operator(ostream _cout, const Date d);
// //“”流运算符重载函数类中友元声明
// friend istream operator(istream _cin, Date d);
//
// /*
// * 友元函数让函数能够访问类中的私有成员
// *
// * 友元声明虽然写在类中但它并不是成员函数
// * 没有隐藏的this指针友元函数类中声明类外定义
// * 没有指定类域。
// * 就是一个全局函数并声明“我是你的友元“朋友””
// */
//
// /*
// * 友元会让耦合度变高某种程度上破坏了封装
// */
//
//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; //时间类对象
//};
//
“”流运算符重载函数全局中实现
//ostream operator(ostream _cout, const Date d)
//{
// _cout d._year - d._month - d._day;
//
// return _cout;
//}
//
“”流运算符重载函数全局中实现
//istream operator(istream _cin, Date d)
//{
// _cin d._year d._month d._day;
//
// return _cin;
//}A类
//class A
//{
//private: //私有成员变量A类
// int h; //4个字节
//
//public: //公有成员函数
//
// //在A类内部再定义一个B类
// class B //内部类B
// {
// public: //公有B类
// int _b;
//
// void func()
// {
// A aa;
// aa.h; //直接访问外部类的私有成员
// /*
// * 因为内部类天生就是外部类的友元
// * 所以内部类中可以直接访问外部类的私有成员
// */
// }
// };
//
// /*
// * A类和内部类B的关系
// * 内部类B可以认为就是一个普通的类
// * 只是其会受到 A的类域 和 访问限定符 的限制
// * 而且内部类天生就会是外部类的友元重点
// *
// * 跟静态成员变量和函数类似性质跟在全局时一样
// * 只是受到了 类域 和 访问限定符 的限制
// *
// * C中内部类比较少用
// */
//};
//
主函数
//int main()
//{
// //计算拥有内部类的A类的大小
// cout sizeof(A) endl;
// /*
// * 这里A类的大小计算后是4个字节
// * 也就是说计算A类时是不会计算内部类B的
// */
//
// //可以直接定义A对象
// A aa;
//
// //但无法直接定义内部类B对象
// B bb; //爆红
//
// //指定其类域后就可以定义其对象了
// A::B bb; //内部类B的权限为公有
//
// A::B bb;
// /*
// * 指明类域后但如果内部类B的权限为私有
// * 那单单指明类域也无法创建内部类B对象
// * 而是只能在类内部进行使用
// */
//
// return 0;
//}//扩展内容一些构造时的优化不同编译器优化不同//A类
class A
{
public: //公有成员函数//构造函数有参A(int a 0):_a(a){cout A(int a) endl;}//拷贝构造函数A(const A aa):_a(aa._a){cout A(const A aa) endl;}//“”赋值运算符重载函数A operator(const A aa){cout A operator(const A aa) endl;if (this ! aa){_a aa._a;}return *this;}//析构函数~A(){cout ~A() endl;}private: //私有成员变量int _a;
};//主函数
int main()
{A aa1(1); //调用(有参)构造函数A aa2(aa1); //调用拷贝构造函数//如果有一个已经存在的对象aa1A aa3 aa1; //调用拷贝构造函数/** 一个已经存在的对象拷贝初始化* 另一个要创建的对象 -- 赋值拷贝* * 其实就相当于上面的A aa2(aa1)*///如果两个都是已经存在的对象aa2 和 aa3aa2 aa3; //调用赋值运算符重载函数return 0;
}//优化一 -- 主函数
int main()
{//优化一A aa1 1; //这里就是以下第三步中的b类型/** 第一步先用1构造一个临时对象构造函数* 第二步再通过临时对象拷贝构造出aa1拷贝构造函数** 第三步优化* 同一个表达式中注意* 连续构造构造 / 构造拷贝构造 / 拷贝构造拷贝构造* 这些操作可能会被合二为一* a、 构造构造 - 构造* b、 构造拷贝构造 - 构造* c、 拷贝构造拷贝构造 - 拷贝构造** 这里就是b类型先调用 构造拷贝构造 ,实际只调用了一次构造函数*/return 0;
}//func函数
void func(A aa)
{}//主函数
int main()
{A aa1(1); //初始化调用构造函数func(aa1); //传值传参调用拷贝构造函数/** 这里虽然也是先调用构造函数* 再调用拷贝构造函数但这里不是再同一个表达式中*///优化二//通过匿名对象调用函数func(A(2));/** 这里需要先初始化匿名对象* 调用构造函数* 再传值传参匿名对象调用func函数* 调用拷贝构造函数* * 这里跟上面不同是在同一个表达式中* 所以 构造 拷贝构造 - 构造函数* 合二为一只会调用一次构造函数*///优化三和优化一类似func(3);/** 先用3构造一个临时对象* 调用构造函数* 再用临时对象进行传值传参* 调用拷贝构造函数* * 这里也是在同一个表达式中* 所以所以 构造 拷贝构造 - 构造函数* 合二为一只会调用一次构造函数*/return 0;
}A func()
{A aa;/** 函数中初始化一个A类型对象* 调用构造函数*/return aa;/** 因为是传值返回所以拷贝出aa的临时对象* 调用拷贝构造函数*/
}//主函数
int main()
{//优化四A aa1 func();/** 传值返回时调用了一次拷贝构造函数* 再通过返回的临时变量拷贝初始化aa1* 调用拷贝构造函数* * 即传值返回时调用了一次拷贝构造函数* 然后又继续调用一次拷贝构造函数进行* 拷贝初始化对象aa1* 所以是连续调用了两次拷贝构造函数* 同一个表达式中* * 所以 拷贝构造 拷贝构造 - 拷贝构造* 合二为一只会调用一次拷贝构造函数*/A aa2;aa2 func();/** 这里则不会进行优化* 因为这时的“”两边都是已经初始化了的对象* 所以这里会调用“”赋值运算符重载函数进行赋值* 所以不会像上面一样被优化*/return 0;
}/** 总结* 再同一个表达式中* 构造 构造 - 构造* 构造 拷贝构造 - 构造* 拷贝构造 拷贝构造 - 拷贝构造* 现在的编译器基本都会做到* * 但一些新的编译器可能还会跨表达式优化* 把中间一些不必要对象也优化掉 -- 跨表达式优化* 不同编译器可能不同* 是一种激进优化会使编译器的维护更加复杂* 可能会使之前的代码在优化后有bug*/