【c++类与对象 -- 补充 】
目录:
- 前言
- 一、拷贝构造
- 1、初始化列表
- 2、explicit关键字
- 浅谈编译器优化
- 二、static成员
- 1、static成员变量
- 2、static成员函数
- 特性总结
- 友元
- 1、友元函数
- 特性总结
- 2、友元类
- 特性总结
- 内部类
- 五、匿名对象
- 总结
前言
打怪升级:第37天 |
---|
![]() |
在上一篇文章中我们详细讲解了类与对象的主要内容,那是一块难啃的骨头,
不过还好,我们已经跨越了重重阻碍走到了这最后一步,这里呢我们对类与对象的一些边边角角进行补充,这关打完–原地飞升。
一、拷贝构造
class A
{
public:
A(int a1 = 1, int a2 = 1)
{
_a1 = a1;
_a2 = a2;
}
private:
int _a1;
int _a2;
};
int main()
{
A aa;
return 0;
}
上面这种是我们常用的构造类的方式:类中有两个成员变量,通过构造函数进行赋值;
class A
{
public:
A(int a1 = 1, int a2 = 1, const int c = 1)
{
_a1 = a1;
_a2 = a2;
_c = c;
}
private:
int _a1;
int _a2;
const int _c;
};
int main()
{
A aa;
return 0;
}
1、初始化列表
为了解决有些成员变量必须初始化问题,我们的祖师爷当时引入了 初始化列表的概念,
用法:在拷贝构造中使用,
以一个冒号开始,接着是以逗号分割的数据成员列表,每个成员变量后面跟着一个放在括号内的初始值或表达式。
class A
{
friend ostream& operator<<(ostream& out, A& aa);
public:
A(int a1 = 100, int a2 = 1, const int c = 1)
: _a1(a1) // 初始化列表 进行初始化操作
, _a2(a2)
, _c(c)
{ }
private:
int _a1;
int _a2;
const int _c;
};
ostream& operator<<(ostream& out, A& aa)
{
out << aa._a1 << " " << aa._a2 << " " << aa._c << endl;
return out;
}
int main()
{
A aa;
cout << aa;
return 0;
}
运行结果:
注意:
- 写在初始化列表中是初始化, 每个成员变量只可以初始化一次,
写在构造函数内部是赋值, 变量的赋值可以进行任意次。
- 必须初始化的成员变量有三种:
①const成员变量;
②引用成员变量;
③自定义类型成员变量(并且自定义类型无默认构造函数 – 无参构造)
class A
{
private:
int a_val = 20;
};
class B
{
public:
B(int d1, int d2) // 有参构造
{
b_data1 = d1;
b_data2 = d2;
}
private:
int b_data1;
int b_data2;
};
class C
{
public:
C() : b(10, 10) {}
private:
A a;
B b;
};
int main()
{
C c;
return 0;
}
- 尽量使用初始化列表来初始化成员变量,因为不管使用不使用初始化列表,对于自定义类型都会自动走一遍初始化列表,先使用初始化列表进行初始化。
这里我们就可以更好地理解:构造函数对内置类型不作处理,对自定义类型会调用它的默认构造函数,
对自定义类型默认构造函数的调用是在初始化列表里进行的。
- 成员变量在类中的声明次序就是其在初始化列表中的初始化次序,与它在初始化列表中的次序无关。
// 我们来看一下 第 4条
class AA
{
friend ostream& operator<<(ostream& out, const AA& a);
public:
AA()
: _data2(10)
,_data1(_data2)
{}
private:
int _data1;
int _data2;
};
ostream& operator<<(ostream& out, const AA& a)
{
out << a._data1 << " " << a._data2 << endl;
return out;
}
int main()
{
AA a;
cout << a;
return 0;
}
深入理解构造函数的初始化和赋值
这一部分确实绕来绕去不好理解,不过c++也并非一开始就是这样的,例如第二部分的:初始化缺省值 是在C11的时候才加入的,目的是为了方便对自定义变量进行初始化,前辈们各种各样的增添功能,虽然使得我们这些初学者觉得头痛不已,但是当我们真正掌握了这些内容之后,使用起来那就是“纵享丝滑”,希望大家以后都可以“祖师爷附体”、“如有神助”。
2、explicit关键字
构造函数不仅可以构造和初始化对象,对于单个参数或者第一个参数无默认值的构造函数还具有类型转换的作用。
void Test01()
{
int a = 10;
float f = 1.0;
char c = 'a';
// 隐式类型转换:赋值运算符两边的数据类型不同时,编译器会创建一个临时变量,
// 将赋值运算符右边的数据转换为运算符左边的数据类型,放入临时变量中,
// 之后使用临时变量对左边的数据进行赋值;
// 注意:临时变量具有常性。
int tmp = a;
cout << tmp << endl;
tmp = f;
cout << tmp << endl;
tmp = c;
cout << tmp << endl;
}
class A
{
friend ostream& operator<<(ostream& out, A& aa);
public:
A(int a1, int a2 = 1)
:_a1(a1)
,_a2(a2)
{
cout << "A(int, int)" << endl;
}
A(const A& aa)
{
_a1 = aa._a1;
_a2 = aa._a2;
cout << "A(const A&)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a1;
int _a2;
};
ostream& operator<<(ostream& out, A& aa)
{
out << aa._a1 << " " << aa._a2 << endl;
return out;
}
浅谈编译器优化
class A
{
friend ostream& operator<<(ostream& out, A& aa);
public:
A(int a1 = 10, int a2 = 1)
:_a1(a1)
,_a2(a2)
{
cout << "A(int, int)" << endl;
}
A(const A& aa)
{
_a1 = aa._a1;
_a2 = aa._a2;
cout << "A(const A&)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
A& operator=(const A& aa)
{
_a1 = aa._a1;
_a2 = aa._a2;
cout << "operator=(const A&)" << endl;
return *this;
}
private:
int _a1;
int _a2;
};
ostream& operator<<(ostream& out, A& aa)
{
out << aa._a1 << " " << aa._a2 << endl;
return out;
}
A Test02A()
{
A a;
return a;
}
void TestExplicit()
{
A aa = Test02A();
}
二、static成员
static成员分为static成员变量和static成员函数,使用方法就是在函数或者变量前面加上 static
1、static成员变量
上面的是我们平时使用static的方法,
- 而在类中,类的静态成员变量也是一样会存放到静态区,并且,静态成员变量只会创建一个,所用类成员共用同一个静态成员变量,
也就是说:静态成员变量不是属于某一个对象,而是属于整个类的,类的所以对象都可以访问该静态变量。
- 静态成员变量应该在类内进行声明,在类外初始化。
2、static成员函数
既然静态成员函数没有隐藏的this指针,那么就无法访问任何非静态成员函数和变量,
它的作用也就被大大减小了,其实,静态成员函数的存在就是为了返回静态成员变量的值,
其他情况基本用不到它,也就是说:静态成员函数因为静态成员变量而存在。
特性总结
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区;
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明;
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问;
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员;
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
友元
友元分为:友元函数和友元类。
友元提供了一种突破封装的方式,有时提供了便利,但是这种方式破坏了完整性,增加了耦合度,所以友元不宜多用。
友元关键字:friend
1、友元函数
使用方法:在类中写出函数声明,并在声明前加上 friend关键字。
友元就是朋友的意思,友元函数可以访问类中的所有成员,类似但不等同于成员函数 – 友元实际上还是普通函数,并没有默认的this指针,只是给了它访问这个类的权限。
#include<iostream>
using namespace std;
class Date
{
public:
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
void operator<<(ostream out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year = 2023;
int _month = 3;
int _day = 8;
};
void Test02()
{
Date d1;
cout << d1; // 错误示范
}
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
public:
// friend ostream& operator<<(ostream& out, const Date& d); // 友元声明可以写在类内任何位置,一般写在最上面,避免和成员函数声明混淆
private:
int _year = 2023;
int _month = 3;
int _day = 8;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
void Test02()
{
Date d1;
//cout << d1;
//d1 << cout; // 成员函数使用形式
cout << d1 << endl << d1 << endl; // 全局函数使用形式
}
特性总结
- 友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,
- 不属于任何类,但需要在类内部进行声明,声明时需要加上friend关键字。
- 友元函数不能用const修饰;
- 一个函数可以是多个类的友元;
- 友元函数可以在类定义的任何地方声明,不受类访问限定符的限制;
- 友元函数的调用和普通函数的调用相同。
2、友元类
// 下方是两个类:Date、Time
class 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 = 0, int month = 0, int day = 0)
:_year(year)
,_month(month)
,_day(day)
,_t(7,20,0)
{}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
cout << _hour << "时" << _minute << "分" << _second << "秒" << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
void Test05()
{
Date d(2023, 3, 10);
d.Print();
}
_t 的数据在初始化阶段就已经确定,我们之后想要改变 _t 的数据是办不到的,
这样当然就很不方便,我们想要在Date类中直接改变 _t 的数据就需要 Date类可以访问 Time类中的非公有成员;
特性总结
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员;
- 友元关系是单向的,不存在交换性 – A喜欢B,B不一定喜欢A,只有互相设置为友元才属于双向奔赴 ;
- 友元关系不能传递 – A是B的朋友,B是C的朋友,A不一定是C的朋友,这要看C有没有把A设置为友元;
- 友元关系不能继承 ;
class Time
{
friend class Date; // 声明Date是Time的友元类
public:
Time(int hour = 0, int minute = 0, int second = 0)
:_hour(hour)
, _minute(minute)
, _second(second)
{}
void Print()
{
cout << _hour << "时" << _minute << "分" << _second << "秒" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year=0, int month=0, int day=0)
:_year(year)
,_month(month)
,_day(day)
,_t(7,20,0)
{}
void SetTime(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
void Print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
_t.Print();
}
private:
int _year;
int _month;
int _day;
Time _t;
};
内部类
概念:如果一个类在另一个类的内部声明,这个内部类就叫做内部类。
- 内部类天生就是外部类的友元类,可以访问外部类的非公有成员,但外部类不是内部类的友元;
- 外部类对内部类成员的访问没有任何优先权限;
- 内部类和外部类是相互独立的两个类,并不存在包含关系;
- sizeof(外部类) = 外部类,和内部类没有任何关系;
- 对内部类的访问会受到外部类类域限制 ;
- 对内部类的访问会受到访问权限限制 – 后两点为与友元类的区别所在。
class A
{
public:
class B // 内部类
{
public:
void Print()
{
cout << "_date = " << _date << endl;
}
private:
int _date = 10;
};
private:
int _val1;
int _val2;
};
void Test06()
{
/*cout << "sizeof(A) = " << sizeof(A) << endl;*/
A::B b;
b.Print();
}
五、匿名对象
匿名既不写名字;
匿名对象就是没有名字的对象,匿名对象的生命周期只有一行。
更多的使用场景我们以后会继续补充。
总结
静态成员可以直接使用类名进行访问,因为静态成员存在于静态区;
普通成员函数不可以使用类名直接访问(为什么?成员函数不是存在于代码段吗?),因为普通成员函数会默认传this指针,
因此必须创建变量来使用它的this指针,而静态成员函数没有this指针,
当然,我们也可以使用nullptr指针去访问成员函数,仅限成员函数,否则如果访问成员变量会出现空指针解引用;
也可以使用匿名对象。