【C++类和对象(下)】——我与C++的不解之缘(五)

张开发
2026/5/3 19:35:58 15 分钟阅读
【C++类和对象(下)】——我与C++的不解之缘(五)
前言时隔多日继续学习C类和对象大致内容初始化列表explicit关键字static成员友元匿名对象一、再探构造函数1、接上篇我们实现构造函数时初始化成员变量主要在函数体内部进行赋值构造函数初始化还有一种方法就是初始化列表初始化列表的使用方式是以一个冒号开始接着一个逗号分隔的数据成员列表在每一个“成员变量”后面跟一个放在括号中的初始值和表达式。2、每一个成员变量在初始化列表中只能出现一次语法理解上初始化列表可以理解为是每一个成员变量定义初始化的地方。3、引用成员变量、cosnt成员变量、没有默认构造函数的类类型变量这些必须在初始化列表位置进行初始化不然会编译报错。4、尽量使用初始化列表因为不在初始化列表初始化的成员也会走初始化列表如果这个成员在声明的时候给了缺省值初始化列表会使用这个缺省值进行初始化如果没有给缺省值对于没有显示在初始化列表的内置类型成员变量是否初始化就取决于编译器了对于没有在初始化列表初始化的自定义类型成员变量会调用这个成员类型的默认拷贝构造如果没有就编译错误。5、初始化列表中按照成员变量在类中声明顺序进行初始化跟成员在初始化列表出现的先后顺序无关建议声明顺序和初始化列表顺序保持一致。初始化列表1、初始化列表可以理解为每一个成员变量定义和初始化的地方。初始化列表使用class Data { Data(int year 1,int month 1, int day 1) :_year(year) ,_month(month) ,_day(day) {} private: int _year; int _month; int _day; };2、引用成员变量 、const成员变量和没有默认拷贝构造的类类型变量必须使用初始化列表 #includeiostream using namespace std; class A { public: A(int a) :_a(a) {} //没有默认拷贝构造函数 private: int _a; }; class B { public: B(int b 1, int x) :_b(b) ,_i(x) ,_ci(520) ,_A(11) {} private: int _b; int _i; //引用成员变量 const int _ci; //const成员变量 A _A; //没有默认拷贝构造的类类型成员变量 }; int main() { int x 0; B b(11, x); return 0; }3、初始化列表中按照成员变量在类中声明的顺序进行初始化class A { public: A(int a) :_a1(a) , _a2(_a1) {} void Print() { cout _a1 _a2 endl; } private: int _a2 2; int _a1 2; }; int main() { A aa(1); aa.Print(); }二、类型转换1、C支持内置类型隐式类型转换成类类型对象需要有相关内置类型为参数的构造函数。2、构造函数前面加上explicit 就不再支持隐式类型转换。3、类类型的对象之间也可以隐式转换需要相应的构造函数支持cout _a1 _a2 endl; } int Get() const { return _a1 _a2; } private: int _a1 1; int _a2 2; }; class B { public: B(const A a) :_b(a.Get()) {} private: int _b 0; }; int main() { // 1构造⼀个A的临时对象再⽤这个临时对象拷⻉构造aa3 // 编译器遇到连续构造拷⻉构造-优化为直接构造 A aa1 1; aa1.Print(); const A aa2 1; // C11之后才⽀持多参数转化 A aa3 { 2,2 }; // aa3隐式类型转换为b对象 B b aa3; const B rb aa3; return 0; }explicit关键字explicit修饰的构造函数将会禁止构造函数的隐式类型转换。class Data { public: explicit Data(int year) :_year(year) {} private: int _year; }; int main() { Data y 1000; return 0; }三、static成员static1、static修饰的成员变量称之为静态成员变量静态成员变量一要在类外面进行初始化。2、静态成员变量是所有类对象共享的不属于某个对象不存放在对象中存放在静态区。3、用static修饰的成员函数叫做静态成员函数静态成员函数没有隐藏的this指针。4、在静态成员函数可以访问其他的静态成员但是不能访问非静态的成员静态成员函数没有this指针。5、非静态的成员函数可能访问静态成员和非静态成员。6、突破类域就可以访问静态成员可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。7、静态成员也是类的成员也受到public、private、protected这些访问限定符的限制。8、静态成员变量不能在声明位置给缺省值来初始化缺省值是给构造函数初始化列表的静态成员变量不属于某个对象不走构造函数的初始化列表。静态成员变量静态成员变量存储在静态区属于类要在类外面进行定义和初始化不能再声明位置给缺省值这里定义一个静态成员记录创建了多少个类对象。class A { public: A(int a 1) :_a(a) { i; } ~A() { --i; } static int GetI() { return i; } private: int _a; static int i; }; void Func1() { A aa1; cout A::GetI() endl; } int A::i 0; int main() { A a1; cout A::GetI() endl; Func1(); cout A::GetI() endl; return 0; }静态成员函数静态成员函数没有隐藏的this指针所以就无法访问其他的非静态成员突破类域就能够访问静态成员不需要像其他非静态成员一样通过类对象来访问类名::静态成员或者对象.静态成员一道小小的编程题来练习练习求123...n_牛客题霸_牛客网 (nowcoder.com)代码如下class A { public: A() { i; num i; } static int GetNum() { return num; } private: static int i; static int num; }; int A::i 0; int A::num 0; class Solution { public: int Sum_Solution(int n) { A sum[n]; return A::GetNum(); } };四、友元友元提供了一种突破封装的方式带来了一些便利同时也会破坏了封装不宜多使用。友元函数在实现运算符重载 operator 和operator时因为有隐藏的this指针的存在我们不能将其重载成成员函数cout输出流对象和隐藏的this指针抢占第一个参数的位置而如果重载成全局函数我们又没办法访问类的私有成员变量了所以就使用友元来解决这个问题。友元函数可以直接访问类的私有成员它定义在类外部的普通函数不属于任何类但是需要在类内部声明声明时加friend关键字定义的时候不需要加friend关键字。class Data { //类中声明 friend ostream operator(ostream out, const Data d); friend istream operator(istream in, Data d); public: Data(int year 1, int month 1, int day 1) :_year(year) ,_month(month) ,_day(day) {} private: int _year; int _month; int _day; }; //类外定义 ostream operator(ostream out, const Data d) { out d._year - d._month - d._day endl; return out; } istream operator(istream in, Data d) { cout 依次输入年月日 endl; in d._year d._month d._day; return in; } int main() { Data d1; cin d1; cout d1; return 0; }注意1、友元函数可以访问类的私有和保护成员但不是类的成员函数2、友元函数不能用const修饰const修饰的是隐藏的this指针3、友元函数可以在类的任何地方声明不受类访问限定符的限制4、一个函数可以是多个函数的友元5、友元函数的调用和普通函数一样友元类友元类的所有成员函数都可以是另一个类的友元函数都可以访问另一个类的非公有成员1、友元关系是单向的比如下面代码中Time类中声明Data类是其友元类那么就可以在Data类中直接访问Time类的私有成员但是不能再Time中直接访问Data类的私有成员。2、友元关系不能传递3、友元关系不能继承继承现在还没学习到class Time { friend class Data; public: Time(int hour 1, int minute 1, int second 1) :_hour(hour) , _minute(minute) , _second(second) {} private: int _hour; int _minute; int _second; }; class Data { public: Data(int year 1, int month 1, int day 1) :_year(year) , _month(month) , _day(day) {} void SetTime(int hour, int minute, int second) { //可以直接访问Time类的私有成员 //如果不是友元就会直接报错 _t._hour hour; _t._minute minute; _t._second second; } private: int _year; int _month; int _day; Time _t; };如果上面代码不在Time类中声明Data是友元类则会出错五、内部类内部类1、如果一个类定义在另一个类的内部这个定义在类内部的类就是内部类内部类是一个独立的类它只是受到外部类类域限制和类访问限定符限制所以外部类定义的对象中不包含内部类在计算类大小时内部类不计算在其中。2、内部类默认就是外部类的友元类可以直接访问外部类的私有成员。3、内部类本质上也是一种封装如果A类和B类联系紧密A类实现出来就是给B类用的那就可以考虑将A类设计为B的内部类如果放到private / protected位置那么A类就只是B类 的专属类其他地方无法使用。再回头看这一道题求123...n_牛客题霸_牛客网 (nowcoder.com)这里我们实现的类A就是为了实现类Solution求1到n就可以写成内部类代码如下class Solution { class A { public: A() { _i; _ret_i; } }; static int _i; static int _ret; public: int Sum_Solution(int n) { A arr[n]; return _ret; } }; int Solution::_i0; int Solution::_ret0;六、匿名对象1、匿名对象用类型直接定义出来的对象之前定义的 类型 对象名 定义出来的时有名对象。2、匿名对象生命周期只在当前这一行一般临时定义对象使用一下可以定义匿名对象。匿名对象生命周期class A { public: A(int a 1) :_a(a) {} ~A() { cout ~A() endl; } private: int _a; }; int main() { A a1(11);//有名对象生命周期是当前函数栈帧 A(22); //无名对象生命周期为当前这一行 cout ------------------------------- endl; return 0; }匿名对象使用当我们不想要创建类对象而要调用类的成员函数就可以使用匿名对象class A { public: A(int a 1) :_a(a) {} ~A() { cout ~A() endl; } void Test() { cout 我一直都在 endl; } private: int _a; }; int main() { A a1(11);//有名对象生命周期是当前函数栈帧 A(22); //无名对象生命周期为当前这一行 cout ------------------------------- endl; A().Test();//调用类的成员函数 return 0; }七、编译器优化1、现代编译器会为了尽可能提高程序的效率在不影响正确性的情况下会尽可能减少一些传参和传返回值的过程中可以省略的拷贝。2、如何优化C标准并没有严格规定各个编译器会根据情况自行处理。当前主流的相对新一点的编译器对于连续一个表达式步骤中的连续拷贝会进行合并优化有些更新更激进的编译器还会进行跨行跨表达式的合并优化。

更多文章