您的位置:首页 > 教育 > 培训 > C++:类和对象(下)

C++:类和对象(下)

2024/10/5 7:56:23 来源:https://blog.csdn.net/2301_80373479/article/details/141062054  浏览:    关键词:C++:类和对象(下)

目录

1.初始化列表

2.隐式类型转换

3.static成员

​编辑

4.友元与内部类

友元

内部类 

5.匿名对象


1.初始化列表

在类和对象中,实现构造函数时,初始化成员变量是用函数体内赋值,其实还有另一种方式初始化成员变量:初始化列表

  • 初始化列表:用一个冒号开始,逗号分隔,每个成员变量后跟一个放在括号里的初始值或表达式。

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 d(2024,8,5);//实例化对象d.Print();return 0;
}

有关初始化列表:

1.初始化列表是每个成员变量定义初始化的地方,每个成员变量只能在列表中出现一次。

2.我们以前所写的构造函数,其实也走了初始化列表(对成员变量的定义),然后我们在函数体对成员变量赋值。

我们不显示写初始化列表,对于内置类型的成员变量不做处理对于自定义类型的会去调用它的构造函数

所以总的来说,我们写不写初始化列表,成员变量都会走初始化列表,因为这是成员变量定义的地方。

3.初始化列表会按照成员变量在类中的声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持一致

class A{public:A(int a):_a1(a), _a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}private:int _a2;int _a1;};

 

这是一个声明顺序和初始化列表的顺序不一致导致的错误,_a2声明在前,初始化列表时先初始化_a2,用的是还初始化的_a1的值,所以_a2是个随机数。

4.C++11支持了在成员变量声明位置给缺省值,这个缺省值是没有显示在初始化列表初始化的成员用的

class Date
{
public:Date(int year, int month, int day)//初始化列表:_year(year)                 ,_month(month),_day(day){}Date()//没有显示写初始化列表{}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private://年月日int _year = 2024;   //给缺省值int _month = 5;int _day = 1;
};

5.引用成员变量,const成员变量,没有合适或者没有默认构造的类类型变量必须在初始化列表位置进行初始化,否则会编译报错。

class C
{
public:C(int c):_c(c){}
private:int _c;
};class A
{
public:A(int a, int b ,int c):_a(a),_b(b),_c(c){}
private:int& _a;const int _b;C _c;
};

在对象实例化时,引用成员变量必须传递一个定义好的变量,如果是传递一个值的话,那类中的引用成员变量是用这个值构建的临时变量来初始化,构造函数结束后,临时对象销毁,类中的引用成员变量就是一个野引用。

被const修饰的变量只有一次初始机会,此后不能被修改,必须在初始化列表的定义。

2.隐式类型转换

C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。

单参数构造函数支持隐式类型转换

​
class A
{
public:A(int a):_a(a){}
private:int _a;
};int main()
{//3构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aaA aa = 3;return 0;
}​

值得一提的是,编译器遇到连续构造+拷贝构造,会直接优化成用3去构造aa出来。

  • 如果是引用的隐式类型转换,需要加const,不加编译器会报错。

原因是2构造出来的临时对象具有常性,不能被修改,不加const会触发权限放大,编译器报错。

int main()
{const A& aa = 2;return 0;
}

如果我们不想隐式类型转换发生,那么可以在构造函数前加explicit

class A
{
public:explicit A(int a) //不支持隐式类型转换了:_a(a){}
private:int _a;
};

在C++11后,支持了多参数构造。

class A
{
public:A(int a1, int a2) :_a1(a1),_a2(a2){}
private:int _a1;int _a2;
};int main()
{A aa = { 1, 2 };return 0;
}

3.static成员

static修饰的成员变量,是静态成员变量,同理也有静态成员函数。

有关静态成员变量:

  • 静态成员变量一定要在类外进行初始化
  • 静态成员变量为所有类共享,不存在对象中,存放在静态区
  • 静态成员变量不能在声明位置给缺省值,因为它不走初始化列表。

有关静态成员函数:

  • 静态成员函数没有this指针,所以访问不了类中的非静态成员,但可以访问静态成员。

非静态的成员函数可以访问任意的静态成员变量或者函数。

如果想在类外面访问静态成员,可以使用域作用限定符::或者点.来突破类域,从而访问,如:类名::静态成员或者对象.静态成员

静态成员也是类成员,也受到public、protected、private访问限定符的影响。

静态成员的应用:

统计创造出多少个类对象

class A
{
public:A(){++_count;}A(const A& a){++_count;}~A(){--_count;}static int GetCount(){return _count;}private:static int _count; //类里面声明
};int A::_count = 0; //类外面定义int main()
{A aa1;cout << A::GetCount() << endl;A aa2(aa1);cout << aa2.GetCount() << endl;return 0;
}

求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

class Sum
{
public:Sum(){_ret += _i;++_i;}static int GetRet(){return _ret;}
private:   static int _i;static int _ret;};
int Sum::_i = 1;
int Sum::_ret = 0;class Solution {
public:int Sum_Solution(int n) {Sum* s = new Sum[n];delete[] s;s = nullptr;return Sum::GetRet();}
};

假设有A、B、C、D4个类的定义,程序中构造函数的调用顺序为:C A B D

析构函数的调用顺序为:B A D C

(先定义先构造,后定义的先析构,但是static改变了对象的生存作用域,会放在局部对象之后进行析构)

C c;int main(){A a;B b;static D d;return 0
;
}

4.友元与内部类

友元

友元是一种突破类访问限定符封装的方式,合适的使用可以提供便利,但友元会增加耦合度,破坏封装,谨慎使用。

  • 关键字friend,并将其声明放到一个类里
  • 友元分为:友元函数和友元类。

有关友元函数:

  • 友元函数是一种声明,可以在类定义的任何地方,不受访问限定符影响。
  • 外部友元函数可以访问类的私有和保护成员。
  • 一个函数可以是多个类的友元函数。
  • 友元函数不能被const修饰。(const是修饰成员函数的)

有关友元类:

  • 友元类的成员函数都是另一个类的友元函数,可以访问另一个类的私有和保护成员。
  • 友元类是单向的,如:A类是B类的友元,但反过来不是。
  • 友元类的关系也不具有传递性,如:A是B的友元,B是C的友元,但A不是C的友元
class A
{friend void func(const A& a); //友元函数声明private:int _a = 1;
};void func(const A& a)
{cout << a._a << endl;
}int main()
{A aa;func(aa);return 0;
}

内部类 

一个类定义在另一个类的内部,这个类就是内部类。

  • 内部类是独立的类,只是受外部类和访问限定符限制,外部类定义的对象不包含内部类。
  • 内部类默认是外部类的友元类。
  • 内部类是一种封装方式,当A类专属B类使用时,可以将A类设计为B类的内部类。
class A
{
private:int _a = 5;static int _b;
public:class B{public:void Print(const A& a){cout << a._a << endl;cout << _b << endl;}};
};int A::_b = 10;int main()
{cout << sizeof(A) << endl;//4//A的大小不受内部类的影响,说明内部类是独立的类A aa;A::B bb;bb.Print(aa);return 0;
}

5.匿名对象

用类型定义出来的对象就是匿名对象。

class A
{
public:A(int a = 10):_a(a){}
private:int _a;
};int main()
{A aa(5); //有名对象A(5);    //匿名对象return 0;
}
  • 匿名对象生命周期只在当前一行,当你需要临时定义一个对象,就可以用匿名对象
  • 匿名对象也具有常性。
const A& a1 = A(3); //必须加const,因为匿名对象也具有常性

拜拜,下期再见😏

摸鱼ing😴✨🎞

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com