作者前言
🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言、C++和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂
类和对象
- **作者前言**
- 类的构造函数
- 初始化列表
- static成员
- 静态成员
- 静态成员函数
- explicit关键字
- 单参数的隐式类型转换
- 多参数类型的隐式类型转换(c++11后支持)
- 友元
- 友元函数
- 友元类
- 内部类
- 扩展
- 拷贝小误区
- 构造优化(编译器的优化)
类的构造函数
前面我们学习了类的构造函数的定义,如下:
这个就是我们之前写的构造函数的定义,叫做函数体内初始化,除了这样定义,还有另外一种定义叫初始化列表
初始化列表
使用这个主要是为了解决一些类型无法初始化
- 类中的引用无法初始化
可以看到使用函数体内初始化引用时是无法初始化引用变量的,因为在类中的成员是声明,在函数内才是定义(创建对象),const 也要在定义时初始化,为了解决引用和const在类的成员函数里面初始化不了,就有了初始化列表,
初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段
Date()
//初始化列表
:_a(d1._a)
,_b(30)
{
//函数体内定义
_year = d1._year;
_month = (d1._month);
_day = (d1._day);
}
这里是一个类的默认构造函数,
- 假设初始化列表没有写内置类型的定义和函数体内定义也没有,内置类型就是随机值(编译器定义的)
- 如果类的成员有自定义类型,没有在初始化列表定义,自定义类型会自动调用它本身的默认构造函数。
无参的构造函数和全缺省的构造函数以及编译器自己生成的都称为默认构造函数
(不用传参的构造函数)
namespace naco
{
class D
{
public:
//默认构造
D(int a = 20)
{
_a = 20;
}
private:
int _a;
};
class Date
{
public:
Date(int year, int month, int day)
//初始化列表
:_a(year)
, _b(30)
,A(20)//不想调用自己默认值默认构造,就传值
{
//函数体内定义
_year = year;
_month = (month);
_day = (day);
}
~Date()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
//声明
int _year;
int _month;
int _day;
int& _a;
const int _b;
D A;
};
}
我们还可以在声明的时候定义,这样就可以哪怕初始化列表不写,也不会报错
class Date
{
public:
Date(int year, int month, int day)
//初始化列表
:_a(year)
,A(20)
{
//函数体内定义
_year = year;
_month = (month);
_day = (day);
}
~Date()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
//声明
int _year;
int _month;
int _day;
int& _a;
const int _b = 100;
D A;//自定义类型
};
所以可以总结一下:
初始化列表主要针对三类
- 引用
- const修饰的变量
- 没有默认构造函数的自定义类型成员,有默认构造的对象,哪怕没有定义也会调用自己的默认构造,或者是不使用默认值的自定义类型成员
建议:
- 构造函数由初始化列表和函数体构成,缺一不可,
- 每个成员只能再初始化列表出现一次,但是不会影响函数体内初始化,在函数体内还可以出现
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关建议类中声明次序和初始化顺序一样
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
结果是1和随机值,先初始化_a2,然后再初始化_a1
,
static成员
静态成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化,
例子:
写出一个类,计算出一个程序中的类对象有多少个
class A
{
public:
A(int a = 20)
{
_a = a;
++count;
}
A(const A& d1)
{
_a = d1._a;
++d1.count;
}
~A()
{
--count;
}
void Print()
{
cout << count << endl;
}
private:
//shngm
static int count;//属于全部对象共同使用的.不会每个对象都有专属的count,而是全部对象公用这个count
int _a;
};
//静态成员定义
int A::count = 0;
int main()
{
A a(10);
A b(30);
a.Print();
return 0;
}
静态成员函数
- 在main函数之前就初始化了.而不是创建对象后才初始化,
- 只是放到类中,属于类的私有成员,
- 静态成员是全局的,所有对象共用,
- 静态成员只能在类外面定义,不属于类外访问,不能走构造函数的路
静态成员访问形式:
A aa;
A::count;
aa.count;
//这两种都可以访问,
可能有些小可爱觉得不太方便,就会使用一下匿名对象,这个匿名对象和C语言的匿名结构体是不一样的,匿名对象的生命周期只是在一行中
class A
{
public:
A(int a = 20)
{
_a = a;
++count;
}
A(const A& d1)
{
_a = d1._a;
++d1.count;
}
~A()
{
--count;
}
void Print()
{
cout << A::count << endl;
}
private:
//shngm
static int count;
int _a;
};
//定义
int A::count = 0;
int main()
{
A().Print();//A()是一个匿名对象,生命周期在这一行,出了这一行,这个匿名对象就会被析构
return 0;
}
A()就是匿名对象,生命周期在定义的那一行中,
静态成员函数
特点:没有this指针
调用静态成员函数和调用静态成员的方法是一样的,
可以自定义类型::静态成员函数和对象.静态成员函数调用
注意: 静态成员函数可以直接调用静态成员
作用
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
- 静态成员可以在类中直接使用,
explicit关键字
单参数的隐式类型转换
class A
{
public:
A(int a = 20)
:_a(a)
{}
A(const A& d1)
{
_a = d1._a;
}
~A()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A tes = 20;
return 0;
}
上面代码涉及到了内置类型对象隐式转换成自定义类型对象
需要注意的是支持这个方式是要构造函数的参数只有一个int类型参数(this不包含在内),前面我们知道这个临时变量具有常性,需要用const修饰的变量来接收,
能隐式互相转的内置类型是整形(int)和浮点数,这里两种对是类型相近的,表示数据的大小。
内置类型转自定义类型的话,前提是自定义类型的构造函数必须是单参数构造函数
还有就是全缺省构造函数。和只有一个参数没有默认值的半缺省构造函数,都可以隐式转化
简单的理解就是只能传一个参数的构造函数可以
多参数类型的隐式类型转换(c++11后支持)
上面是只能传一个参数的隐式转换,如果是多参数的话可以这样
class A
{
public:
A(int a , int b = 20, int c=30)
:_a(a)
,_b(b)
,_c(c)
{
}
A(const A& d1)
{
_a = d1._a;
_b = d1._b;
_c = d1._c;
}
~A()
{
cout << _a << endl;
}
private:
int _a;
int _b;
int _c;
};
int main()
{
A tes = {30, 20 ,10};
const A& test = {60, 50, 10};//引用的是A(60,50,10)的临时变量
return 0;
}
这里是要在c++11后才支持的,多参数的类型转换,一样的道理,
为了防止这些转换,cpp就有了一个explicit关键字
使用这个关键字来修饰构造函数就会防止有隐式转换了
友元
友元分为:友元函数和友元类
友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。定义在外面就不要friend,
class A
{
public:
A(int a , int b = 20, int c=30)
:_a(a)
,_b(b)
,_c(c)
{
}
A(const A& d1)
{
_a = d1._a;
_b = d1._b;
_c = d1._c;
}
~A()
{
cout << _a << endl;
}
//友元函数(不是任何类的成员函数)
friend std::ostream& operator<<(std::ostream& out, const A& d1);
private:
int _a;
int _b;
int _c;
};
std::ostream& operator<<(std::ostream& out, const A& d1)
{
out << d1._a << d1._b << d1._c;//访问了类的私有成员,没有this指针
return out;
}
特征
-
友元函数可访问类的私有和保护成员,但不是类的成员函数
-
友元函数不能用const修饰
-
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
-
一个函数可以是多个类的友元函数
-
友元函数的调用与普通函数的调用原理相同
友元类
class A
{
public:
friend class B;
A(int a, int b = 20, int c = 30)
:_a(a)
, _b(b)
, _c(c)
{
}
A(const A& d1)
{
_a = d1._a;
_b = d1._b;
_c = d1._c;
}
~A()
{
cout << _a << endl;
}
private:
int _a;
int _b;
int _c;
};
class B
{
public:
B(int a = 20, int b = 30, int c = 40, int Ba = 50, int Bb = 60, int Bc = 70)
:t(a,b,c)
,_Ba(Ba)
,_Bb(Bb)
,_Bc(Bc)
{
}
void Print()
{
cout << t._a << " " << t._b << " " << t._c << endl;//直接访问t的私有成员
}
~B()
{
cout << "B析构了" << endl;
}
private:
int _Ba;
int _Bb;
int _Bc;
A t;
};
int main()
{
B te = 50;
te.Print();
return 0;
}
特征
- 友元关系是单向的,不具有交换性。(友元类可以访问普通类的私有成员或者成员函数,前提是普通类声明过这个友元类)
- 友元关系不能传递(C是B的友元类,B是A的友元类,但是C不是A的友元类)
- 友元关系不能继承
内部类
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,
它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越
的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访
问外部类中的所有成员。但是外部类不是内部类的友元。
如果内部类被public,可以在类外面进行定义,需要使用 外部类名:: 内部类,否则的话只能在外部类内定义使用,
class A
{
public:
A(int a, int b, int c)
:bb(20)
{
_a = a;
_b = b;
_c = c;
}
private:
class B
{
public:
B(int b)
{
_bb = b;
}
~B()
{
A aa(30,40,50);
aa._a++;
aa._b++;
aa._c++;
}
private:
int _bb;
};
private:
int _a;
int _b;
int _c;
B bb;//内部类
};
int main()
{
A aa(1,1,1);
//A::B bb(1);//内部类被oublic可以使用
return 0;
}
特性:
- 内部类可以定义在外部类的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
扩展
拷贝小误区
class A
{
public:
A(int a = 10)
{
_a = a;
}
A(const A& d)
{
_a = d._a;
}
private:
int _a;
};
int main()
{
A a1;
A a2 = a1;//拷贝
A a3(20);
a1 = a3;//赋值
return 0;
}
- 一个存在的对象拷贝构造另一个要创建的对象,就是拷贝构造
- 两个存在的对象使用=就是赋值
构造优化(编译器的优化)
随着时代的变迁,编译器对于一些代码优化了很好,比如构造函数
在同一个表达式中,连续的构造+构造/ 构造+拷贝构造/拷贝构造+拷贝构造合二为一
红框中的大概意思就是,用1构造一个临时对象,然后用这个临时对象拷贝构造a4,但是结果却是调用了构造函数,所以就会有一个结论:
构造 + 构造=>构造
拷贝构造+构造=>构造
拷贝构造 + 拷贝构造=>拷贝构造
不同的编译器优化成度不同,但是普遍有正常的优化,有一些编译器优化可能是多个表达式优化
比如VS2019:
Linux下的g++编译器
就可以看出差别,不同的编译器优化的程度会不同。