0.前言
您好这里是limou3434的博文,感兴趣可以看看我的其他博文系列。
这一部分是对C++类的一些补充:初始化列表、友元、内部类。
1.初始化列表
1.1.函数体内初始化
在给对象进行初始化的时候,如果选择函数体内进行初始化是一件很麻烦的事情。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
Data1(int a1)//没有提供了默认构造函数
{
_a1 = a1;
}
private:
int _a1;
};
class Data2
{
public:
Data2(int a2, int b2, int c2, int a1)
{
_a2 = a2;
_b2 = b2;
_c2 = c2;
Data1 D(a1);//这里先去调用了Data的构造函数来形成D对象,然后才来_a2开始进行逐步逐步初始化
_d2 = D;
}
private:
int _a2;
int _b2;
int _c2;
Data1 _d2;
};
int main()
{
int i = 2;
Data1 a(i);
Data2 b(5, 5, 5, 5);
return 0;
}
但是这样写是没有办法过的,要成功运行的话只能写一个默认构造函数。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
Data1(int a1 = 0)//提供了默认构造函数
{
_a1 = a1;
}
private:
int _a1;
};
class Data2
{
public:
Data2(int a2, int b2, int c2, int a1)
{
//函数体内初始化
_a2 = a2;
_b2 = b2;
_c2 = c2;
Data1 D(a1);
_d2 = D;
}
private:
int _a2;
int _b2;
int _c2;
Data1 _d2;
};
int main()
{
int i = 2;
Data1 a(i);
Data2 b(5, 5, 5, 5);
return 0;
}
虽然成功进行了初始化,但是这种初始化未免太过恶心和麻烦,因此C++又设计了“初始化列表”这种用法。
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
Data1(int a1)//提供了默认构造函数
{
_a1 = a1;
}
private:
int _a1;
};
class Data2
{
public:
Data2(int a2, int b2, int c2, int a1)
//函数体外初始化
:_a2(a2)
,_b2(b2)
,_c2(c2)
,_d2(a1)//相当于显示调用上面的构造函数
{}
private:
int _a2;
int _b2;
int _c2;
Data1 _d2;
};
int main()
{
int i = 2;
Data1 a(i);
Data2 b(5, 5, 5, 5);
return 0;
}
1.2.初始化列表初始化
初始化列表的格式:以一个冒号“:”开头,接着以一个逗号分割的数据成员列表,每一个成员变量后面跟着一个放在括号中的初始值或表达式。
在一般情况下,使用“函数体内初始化”和“函数体外初始化(初始化列表初始化)”没有太大区别,但是有一些情况就必须使用初始化列表进行初始化。
初始化列表可以认为是成员变量定义的地方。
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时,因此在使用自定义成员的时候,推荐使用初始化列表)
上面的问题代码实际上就是上面的情况3,下面介绍其他的情况
//const成员变量
//这是因为const必须在定义的地方初始化
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
Data1(int a1 = 0)//提供了默认构造函数
{
_a1 = a1;
}
private:
int _a1;
};
class Data2
{
public:
Data2(int a2, int b2, int c2, int a1, int e2)
{
_a2 = a2;
_b2 = b2;
_c2 = c2;
Data1 D(a1);
_d2 = D;
_e2 = e2;
}
private:
int _a2;
int _b2;
int _c2;
Data1 _d2;
const int _e2;//这里虽然可以定义_e2,但是却不允许后续赋值了,因为已经提前定义了const变量,没有办法赋值了
};
int main()
{
int i = 2;
Data1 a(i);
Data2 b(5, 5, 5, 5, 5);
return 0;
}
对于const成员变量,就只能改成初始化列表了
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
Data1(int a1 = 0)//提供了默认构造函数
{
_a1 = a1;
}
private:
int _a1;
};
class Data2
{
public:
Data2(int a2, int b2, int c2, int a1, int e2)
//函数体外初始化
:_a2(a2)
,_b2(b2)
,_c2(c2)
,_d2(a1)//相当于显示调用上面的构造函数
,_e2(e2)
{}
private:
int _a2;
int _b2;
int _c2;
Data1 _d2;
const int _e2;//这里虽然可以定义_e2,但是却不允许后续赋值了,因为已经提前定义了const变量,没有办法赋值了
};
int main()
{
int i = 2;
Data1 a(i);
Data2 b(5, 5, 5, 5, 5);
return 0;
}
对于引用成员变量也是需要使用初始化列表的,这是因为引用也必须在定义的时候指定初始化,不可以通过赋值的方式,给一个变量起别名(“int& a; a = b;”这种写法是不被允许的,必须写成“int& a = b”)
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
Data1(int a1 = 0)//提供了默认构造函数
{
_a1 = a1;
}
private:
int _a1;
};
class Data2
{
public:
Data2(int a2, int b2, int c2, int a1, int e2, int& f2)
//函数体外初始化
:_a2(a2)
,_b2(b2)
,_c2(c2)
,_d2(a1)
,_e2(e2)
,_f2(f2)
{
_f2++;
}
private:
int _a2;
int _b2;
int _c2;
Data1 _d2;
const int _e2;
int& _f2;
};
int main()
{
int i = 2;
Data1 a(i);
int j = 5;
Data2 b(5, 5, 5, 5, 5, j);
return 0;
}
其实我们可以发现C++在这块整得还是挺复杂的……
能使用初始化列表就使用初始化列表,肯定比在函数体内使用比较好。当然不能一话说到底,有些时候也是需要在函数体内初始化更好,比如对指针的检查……
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class A
{
public:
A(int N)
:_a((int*)malloc(sizeof(int)* N))
,_N(N)
{
if (_a == NULL)
{
perror("malloc fail\n");
}
memset(_a, 0, sizeof(int) * N);
}
private:
int* _a;
int _N;
};
int main()
{
A aa0(100);
return 0;
}
1.3.初始化列表的初始化顺序
注意初始化列表的初始化顺序问题:初始化列表是按照声明的先后顺序来依次进行初始化的,与初始化列表的顺序无关。
#include <iostream>
using namespace std;
class A
{
public:
A(int a)
: _a1(a)//初始化列表是按照声明的先后顺序来依次进行初始化的
, _a2(_a1)//所以这里的_a2被初始化了随机值
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;//只需要调换这里的定义先后顺序即可得到输出为“1 1”的情况
int _a1;
};
int main()
{
A aa(1);
aa.Print();
return 0;
}
//因此这里输出的应该是“1 随机值”
2.explicit关键字
explicit 关键字用于修饰只有一个参数的构造函数,它的作用是防止编译器进行隐式类型转换。具体来说,当使用 explicit 关键字修饰构造函数时,编译器将不会自动将参数类型转换为类类型,而要求显式地调用该构造函数。使用 explicit 关键字的主要目的是避免因隐式类型转换而引发的潜在问题和歧义,以增强代码的可读性和安全性。
#include <iostream>
using namespace std;
class Data1
{
public:
Data1(int year)
:_year(year)
{
cout << "Data1(int year)" << endl;
}
Data1(const Data1& d)
{
cout << "Data1(const Data1& d)" << endl;
}
private:
int _year;
};
int main()
{
Data1 dd1(2021);
Data1 dd2 = 2022;
//上述两种写法效果一样,但是过程不一样
//1.dd1是:“直接调用构造函数”
//2.dd2是:隐式转化---->“构造函数”+“拷贝构造函数”+编译器优化---->“直接调用构造函数”
//如果在构造函数前面加上explicit关键字,就会取消这种隐式转化
//而上面的dd2的创建过程实际上就是一种隐式类型转化,所有的隐式类型转化都会生成临时变量,临时变量具有常属性
int i = 10;
//double& d = i;//这样做是不被允许的
const double& d = i;//这里引用的是“i赋值给d时产生”的临时变量,所以是被允许的
const Data1& dd3 = 2023;
return 0;
}调用构造函数” //2.dd2是:隐式转化---->“构造函数”+“拷贝构造函数”+编译器优化---->“直接调用构造函数” //如果在构造函数前面加上explicit关键字,就会取消这种隐式转化 //而上面的dd2的创建过程实际上就是一种隐式类型转化,所有的隐式类型转化都会生成临时变量,临时变量具有常属性 int i = 10; //double& d = i;//这样做是不被允许的 const double& d = i;//这里引用的是“i赋值给d时产生”的临时变量,所以是被允许的 const Data1& dd3 = 2023; return 0; }
3.static成员
声明为static的类成员被称为类的静态成员。
- 用static修饰的成员变量,称之为静态成员变量
- 用static修饰的成员函数,称之为静态成员函数
静态成员变量一定要在类外进行初始化。
静态成员的特性:
- 静态成员变量为所有相同类的对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中的只是声明
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员(但是非静态成员函数可以访问所有的成员变量,包括静态成员变量)
- 类静态成员可用“类名::静态成员”或者“对象.静态成员”来访问
- 静态成员也是类的成员,受访问限定符的限制
#include <iostream>
using namespace std;
class A
{
public:
A() { ++_scount; }
A(const A& t) { ++_scount; }
void Print(void)
{
cout << _scount << endl;
}
static int _scount_x;
private:
static int _scount;//1.静态成员
};
int A::_scount = 1;//6.所以只能在类外面定义和初始化
int A::_scount_x = 0;
int main()
{
A a1;
//2.出现链接错误,因为只有静态成员变量只有声明没有定义,普通成员是跟着类一起定义的
//3.但是普通成员C++规定最好好需要使用初始化列表初始化
//4.而静态成员变量是不能使用初始化列表的初始化的
//5.并且静态成员变量也不能给缺省值,这是因为缺省值给的是初始化列表的,因此还是不能用
//a1._scount;//7.由于受到访问限定符的影响,所以不能直接进行访问
a1.Print();//8.但是可以通过类来间接使用_scount,也证明了静态成员变量确实是被对象们所共有的
A a2;
a2.Print();
//9.不受访问限定符限制,所以有两种方式访问静态成员变量
cout << a1._scount_x << endl;
A::_scount_x++;
cout << a1._scount_x << endl;
cout << a2._scount_x << endl;
return 0;
}
静态的一些使用场景:创建一个只能在栈上开辟的类
#include <iostream>
using namespace std;
class B
{
public:
static B CreateObj()//3.提供一个接口,来间接使用构造函数
{
B b;
return b;
}
private:
B(int x = 0, int y = 0)//2.在构造的时候只能在栈上开辟空间,否则无法使用类,因为这里访问限定符是private
:_x(x)
,_y(y)
{}
private:
int _x;
int _y;
};
int main()
{
//static B data1;//1.无法创建
B data2 = B::CreateObj();//4.依靠接口间接创建了data2对象,并且保证一定是在栈上开辟
//5.CreateObj前面之所以加上static
//是为了避免“先有鸡,还是先有蛋”的问题
//因为假设没有static,要使用CreateObj()就要先创建一个B类型的对象才能使用
//但是由于限定符private的原因没有办法直接使用构造函数创建变量
//使得能够直接使用CreateObj()
return 0;
}
4.友元
之前在上半篇部分有用过一点友元的知识,本次我将带您真正了解友元的知识。
友元的关键字是“friend”友元提供了一种突破封装的方式,有时候会比较便利,也有的时候会破坏封装,友元是不建议多用的。 友元又分为“友元函数”和“友元类”。
4.1.友元函数
- 友元函数可以访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
1.在C++中,const成员函数的目的是确保在函数内部不会修改对象的状态,并且它们具有一个隐含的this指针类型为const指针。这意味着在const成员函数内部,对象被视为常量,不能通过this指针对其进行修改。然而,在友元函数中,由于友元函数不属于类的成员函数,不存在this指针。因为友元函数不连接到任何特定的对象,无法访问对象的成员变量或成员函数,也无法使用this指针来引用对象。
2.由于没有this指针的存在,我们无法在友元函数中使用const来限定该函数。const关键字用于修饰隐含的this指针,但由于友元函数没有this指针,所以使用const是无效的。
3.因此,因为友元函数不是类的成员函数且没有this指针,我们不能在友元函数中使用const修饰符。友元函数可以自由地修改对象的成员,包括私有成员,而不受const的限制。
#include <iostream>
using namespace std;
class Data
{
friend void function(void);//1.把function()设置为Data类的友元函数
public:
Data(int a = 0, int b = 0, int c = 0)
:_a(a), _b(b), _c(c)
{
cout << "Data()" << endl;
}
private:
int _a;
int _b;
int _c;
};
void function(void)
{
Data D;
cout << D._a << " " << D._b << " " << D._b << " " << endl;//2.可以访问Data对象受保护的成员
}
int main()
{
function();
return 0;
}
4.2.友元类
- 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
- 友元关系是单向的,不具有交换性
- 友元关系不能传递(C是B的友元,B是A的友元,但是C不是A的友元)
- 友元关系不能继承,在以后讲解继承的时候在做补充
#include <iostream>
using namespace std;
class Data
{
friend class FriendData;//1.给Data类设置了友元类FriendData
friend void function(void);
public:
Data(int a = 0, int b = 0, int c = 0)
:_a(a), _b(b), _c(c)
{
cout << "Data()" << endl;
}
void Print(void)
{
cout << _a << " " << _b << " " << _b << " " << endl;
}
private:
int _a;
int _b;
int _c;
};
class FriendData
{
public:
void Fun1()
{
Data D;
cout << D._a << endl;//2.可以毫无顾忌访问Data对象中的成员变量
}
void Fun2()
{
Data D;
cout << D._a << endl;//3.除了变量还能访问Data对象中的成员函数
D.Print();
}
void Fun3()
{
Data D;
cout << D._a << endl;
}
};
void function(void)
{
Data D;
cout << D._a << " " << D._b << " " << D._b << " " << endl;
D.Print();
FriendData FD;
FD.Fun1();
FD.Fun2();
}
int main()
{
function();
return 0;
}
5.内部类
把一个类定义另外一个类里面,就叫“内部类”,Java会用的比较多,但是C++会比较少。文章来源:https://www.toymoban.com/news/detail-512807.html
#include <iostream>
using namespace std;
class Data1
{
public:
class Data2//2.Data2定义在Data1内部,仅仅只是:
//2.1.被A的类域限制
//2.2.Data2天生是Data1的友元类
{
public:
void Function(const Data1& a)
{
cout << a._a1 << endl;//3.友元的体现
}
private:
int _a2;//4.需要注意的是A不是B的友元,A无法访问B的成员
int _b2;
int _c2;
};
private:
int _a1;
int _b1;
int _c1;
};
int main()
{
cout << sizeof(Data1) << endl;
//1.输出12,说明Data1没有计算Data2大小
Data1::Data2 D;
return 0;
}
抛去“类域限制”和“天生友元”,基本外部类和内部类没有太大关系……在地位关系上可以认为是并列的。文章来源地址https://www.toymoban.com/news/detail-512807.html
到了这里,关于C++类与对象(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!