C++类和对象下
1.深析构造函数
1.1 函数体构造赋值
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
这种以前的函数体并不叫初始化,这其实是叫函数体赋值。并不能叫初始化。赋值可以很多次,但 初始化只有一次
1.2 初始化列表
初始化列表格式:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
样例:
class Date
{
public:
Date(int year, int month, int day)
:_year (year)
,_month (month)
,_day (day) // 是括号而不是 =
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
你以为这样就完了? 其实要注意的问题还有很多。
注意:
1、每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2、类中包含以下成员,必须放在初始化列表位置进行初始化:
(1)引用成员变量(前面说过引用必须初始化)
(2)const 成员变量 (常变量必须初始化)
(3)自定义类型成员(类中没有默认构造函数)
3、尽量使用初始化列表初始化。
因为不论你写不写都会初始化。但是初始化的值不一定是0 ,也可能是随机值。初始化就是系统分配空间的步骤,就是你不写,系统也会帮你写(只是自己不写可能有些场景会出现问题)。
4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
也就是说我还可以写成这样
class Date
{
public:
Date(int year, int month, int day)
:_month (month)
,_year (year)
,_day (day) // 是括号而不是 =
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
并不会是先初始化 _month 而是 _year, 在声明时是_year先于_month。所以初始化也会。只不过调试的时候有点奇怪而已。
1.3 explicit关键字
前面文章我们讲过不同类型的变量可以通过隐式类型转换变成一个类。
class Date
{
public:
// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
// explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
explicit Date(int year)
:_year(year)
{}
/*
// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具
有类型转换作用
// explicit修饰构造函数,禁止类型转换
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()
{
Date A(2022);
//2023 本来 在背后会生成一个匿名对象 然后给A赋值
//但被explicit 修饰后的函数 是不可以进行强制类型转换 所以会报错
A = 2023;
return 0;
}
总结 explicit 修饰的单参构造函数(全缺省构造函数) 具有不可以进行构造函数的隐士类型转换。
2. static 成员
2.1 static 成员的概念
概念: 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
写一个程序 测试你创造了多少个类:写代码你们可以自己研究
class A
{
public:
A() { ++_scount; }//创建了多少个对象
A(const A & t) { ++_scount; } //重载函数
~A() { --_scount; } //销毁了多少个对象
static int GetACount() { return _scount; }
private:
static int _scount;//先声明
};
int A::_scount = 0;//在全局中定义
void TestA()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
}
2.2 static 成员的特点
- 静态成员为所有类对象(创建的所有对象)所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
3. 友元
3.1 友元函数
学C++的都知道cout>> 和 cin<< 两个流输入 流输出 其实就是函数重载。
可以输出内置类型(自动识别类型) 。但可以不可以输出一个对象呢?
可以!! 我们只需要封装好 输出 或输入的东西封装成一个函数,进行运算符重载即可。
class Date
{
public:
Date(int year = 2023, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date A = (2023, 8, 3);
//cout >> A; //这样 this指针 和 cout 参数配对不上
A << cout;//应该这样去调用 ; 但是乖乖的不是吗?
return 0;
}
A << cout 乖乖的有没有什么方法解决? 当然有!!可以用友元函数 。
来看看友元函数的定义把:
3.2 友元函数概念
概念:友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
来看看示例代码:
class Date
{
//友元函数输入输出流
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
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;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
int main()
{
Date d;
Date d1;
cin >> d >> d1;
cout << d << endl;
return 0;
}
友元函数的注意和特性
注意: 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
特性:
<1>友元关系是单向的,不具有交换性。
比如Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
<2>友元关系不能传递。
如果C是B的友元, B是A的友元,则不能说明C时A的友元。
<3>友元关系不能继承。
4. 内部类
4.1 内部类概念
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。
解释:内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元**(单向性)**。
举个栗子:
B是A的内部类:
class A
//A并不可以访问B中任何成员
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元
//B可以访问A中所有成员
{
public:
void foo(const A& a)
{
cout << k << endl;//直接访问外部类中的static成员
cout << a.h << endl;//外部类的对象/类名
}
private:
int a;
};
};
int A::k = 1;//访问限定符来访问
4.1 内部类特性
-
内部类可以定义在外部类的public、protected、private都是可以的。
-
注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
-
sizeof(外部类)=外部类,和内部类没有任何关系。
5. 匿名对象
什么是匿名对象:
来看代码
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main()
{
A aa1;
// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
//A aa1();
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A aa2(2);
// 匿名对象在这样场景下就很好用
//因为我只需要调用一次就足够,不需要创造一个有名对象
//匿名对象在完成工作之后自动销毁
Solution().Sum_Solution(10);
return 0;
}
注意:匿名对象的生命周期只有这一行,下一行他就会自动调用析构函数。
但是可以延长它的生命周期 知道的小伙伴可以打在评论区里。
6.拷贝对象时的一些编译器优化
说明:在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还是非常有用的。
来一段代码让你们自己体会哦
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;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
}
int main()
{
// 传值传参
A aa1;
f1(aa1);
cout << endl;
// 传值返回
f2();
cout << endl;
// 隐式类型,连续构造+拷贝构造->优化为直接构造
f1(1);
// 一个表达式中,连续构造+拷贝构造->优化为一个构造
f1(A(2));
cout << endl;
// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
A aa2 = f2();
cout << endl;
// 一个表达式中,连续拷贝构造+赋值重载->无法优化
aa1 = f2();
cout << endl;
return 0;
}
好啦 这期C++的入门版已经全部完结。对你有帮助的小伙伴,请点赞收藏哦。