专栏导读
🍁作者简介:余悸,在读本科生一枚,致力于 C++方向学习。
🍁收录于 C++专栏,本专栏主要内容为 C++初阶、 C++ 进阶、STL 详解等,持续更新中!
🍁相关专栏推荐: C语言初阶 、C语言进阶 、数据结构与算法 ( C语言描述)、 C++、 Linux、 Mysql
本文介绍主要静态成员的概念,友元、内部类匿名对象,包括匿名对象,相信看完这篇文章你会加深对类和对象的认识。
文章目录
- 类和对象终章
- Static成员
- 什么是static成员
- 类的静态成员特点
- 全局函数的函数名重复问题
- 友元
- 友元函数
- 友元函数的特点
- 友元类
- 友元类的特点
- 内部类
- 内部类的大小
- 内部类访问
- 内部类特点
- 匿名对象及编译器的优化行为
- 匿名对象
- 单参数、多参数的隐式类型转换的优化行为
- 匿名对象传参时的优化写法
- 编译器对返回值的优化
类和对象终章
Static成员
什么是static成员
C++中的静态成员是指属于类而不属于类的任何特定实例的成员。静态成员存在于整个程序生命周期中,即使没有创建任何类实例,它仍然可以访问和使用。
静态成员可以是数据成员或成员函数。静态数据成员是类的一个数据成员,被所有该类的对象共享。声明静态数据成员时,需要在数据类型前加上static关键字,并在类声明中初始化静态数据成员。例如:
class MyClass {
public:
static int myStaticInt;
};
// 初始化静态成员
int MyClass::myStaticInt = 9854;
int main() {
MyClass obj1;
MyClass obj2;
// 访问静态成员
cout << MyClass::myStaticInt << endl;
// 通过对象访问静态成员
cout << obj1.myStaticInt << endl;
cout << obj2.myStaticInt << endl;
return 0;
}
静态函数成员在类中被声明为static,并使用类名作为前缀来调用。它们没有this指针,因为它们不与任何特定对象相关联。
class MyClass {
public:
static void myStaticFunc() {
// myStaticFunc function body
}
};
int main() {
MyClass::myStaticFunc();
return 0;
}
类的静态成员特点
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
Q:为什么静态成员函数不能调用非静态成员变量?
A:因为静态成员函数没有this指针,而非静态成员变量是与类的对象相关联的。所以如果静态成员函数需要访问非静态成员变量,则需要通过传递对象或其他参数的方式来实现。静态成员函数可以调用静态成员变量和其他静态成员函数,因为它们是属于类而不是对象的。
全局函数的函数名重复问题
1、问题描述
当一个函数定义在全局的头文件时,它将会在预处理阶段在所有的.cpp文件中展开并包含。并在链接阶段发生函数名重复的问题。
2、解决方案
- 声明和定义分离;
- 在函数前加上static,改变函数的链接属性。当该函数在预处理阶段被多个.cpp包含时,因为是静态的函数,不会进符号表。(仅当前文件可见)
- 在函数前加上inline,内联函数在编译时不会进符号表。
头文件中,尽量不要定义全局的变量或函数。
友元
类可以使用友元函数。友元函数是在类外部定义的非成员函数,但可以访问该类的非公有成员。
友元函数
在类中,使用 friend 关键字声明友元函数,并指定友元函数的名称,如下所示:
class MyClass {
private:
int x;
public:
friend void myFriendFunc(MyClass obj);
};
在上面的例子中,myFriendFunc 函数被声明为 MyClass 的友元函数。这意味着 myFriendFunc 函数可以直接访问 MyClass 的私有成员 x。
在定义友元函数时,可以直接访问 MyClass 类的私有成员如下:
void myFriendFunc(MyClass obj) {
cout << obj.x << endl; // 可以直接访问 MyClass 类的私有成员 x
}
在类的外部定义了一个普通函数,并将其声明为该类的友元函数,这样该函数就可以通过对象访问关联类的非公有成员。需要注意的是,友元关系一般是单向的,这意味着如果函数 A 是类 B 的友元函数,并不能直接访问类 A 的非公有成员。
友元函数的使用应该谨慎,因为它打破了类的封装性,使外部函数可以访问类的私有成员,从而增加了代码的耦合性。
//类内声明为友元函数
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
//需要在类外定义
inline ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day << endl;
return out;
}
inline istream& operator>>(istream& in, Date& d)
{
in >> d._year >>d._month >> d._day;
return in;
}
流插入和流提取运算符在重载时,根据使用习惯,第一个参数不应是本身,所以将这两个运算符放在类外定义,在类中放入该声明,并在声明前加上friend修饰。让这个函数成为友元函数。
友元函数的特点
友元函数是一种特殊的函数,在 C++ 中常用于访问类的私有成员。
-
友元函数不是类的成员函数,但可在类中声明,在类外部进行定义和调用。 友元函数的声明可以放在类中的任意位置,它不受类访问限定符限制。
-
友元函数可以直接访问类的私有成员,包括私有数据和私有函数。一个函数可以是多个类的友元,这样可以提高程序的灵活性和效率,但也可能会降低封装性和安全性。
-
友元函数可以被定义为全局函数、类的成员函数或其他类的成员函数。对于类的成员函数,可以通过访问控制符来限制访问权限。
-
声明友元函数不影响该函数的作用域,也不会影响该函数在全局作用域内的可见性,友元函数不能用const修饰。
-
友元关系一般是单向的,即如果函数A是类B的友元函数,则函数A可以访问类B的私有成员,但不一定可以访问函数A所在类的私有成员。如果要访问多个类的私有成员,需要为每个类分别定义友元函数或将它们放在同一个类中。
-
友元关系不能被继承,即派生类不能访问其基类的友元函数。
需要注意的是,友元函数在程序设计中应该尽量少用。友元函数打破了类的封装性,可能会引入新的安全隐患,而且容易影响代码的可读性和可维护性。在使用友元函数时,应该根据实际情况严格控制访问权限,避免滥用。
友元类
在C++中,可以使用友元类来授权其他类或函数访问类的私有成员。友元类是指可以访问类的私有成员的其他类。在类的定义中,使用 friend 关键字声明友元类,例如:
class MyClass2; // 前置声明
class MyClass1 {
private:
int x;
friend class MyClass2; // 声明 MyClass2 为 MyClass1 的友元类
public:
void setX(int a) {
x = a;
}
};
class MyClass2 {
public:
void display(MyClass1 obj) {
cout << obj.x << endl; // 可以访问 MyClass1 的私有成员 x
}
};
int main() {
MyClass1 obj1;
MyClass2 obj2;
obj1.setX(123);
obj2.display(obj1); // 调用 MyClass2 的 display() 函数
return 0;
}
我们声明了一个友元类 MyClass2,该类可以访问 MyClass1 的私有成员 x,在 MyClass2 中的 display函数中,使用 obj.x 访问了 MyClass1 类的私有成员 x。
需要注意的是,友元关系不能被继承,即派生类不能访问其基类的友元类。此外,使用友元类也可能降低类的封装性,应谨慎使用。
友元类的特点
-
友元关系没有继承性,即派生类不能访问基类的友元类。
-
友元没有传递性,比如A是B的友元,B是C的友元,但是不能说A是C的友元。
-
友元类可以访问类的私有成员,但不能继承类的成员。
使用友元类需要注意以下几点:
建议将友元类的访问权限设置为private,以限制友元类的使用范围。
友元类不能访问类的静态成员和静态成员函数,但可以通过访问非静态成员函数来访问静态成员。
总的来说,友元类可以提供方便灵活的类之间成员访问控制,但不当地使用可能会降低代码质量。建议使用友元关系时要考虑完整项目的设计和开发策略。
内部类
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
内部类的大小
class A
{
public:
class B//B类不在A中
{
int b;
};
private:
int _a;
};
sizeof(外部类)=外部类,和内部类没有任何关系。这里A的大小是4字节。B是A的内部类,但是B不存储于A的类中。
内部类访问
A::B b;
内部类特点
1、注意B类也会受A类访问限定符的影响
2、B类天生是A类的友元。(B可以偷A的家,但A无法偷B的家)所以内部类也尽量少用。
-
内部类可以定义在外部类的public、protected、private都是可以的。
-
注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
匿名对象及编译器的优化行为
匿名对象
Date();//创建了个匿名对象
Date().Func();//使用匿名对象调用成员函数
Date Func()//使用匿名对象返回
{
return Date(10);
}
匿名对象的生命周期在这一行。匿名对象具有常性。
单参数、多参数的隐式类型转换的优化行为
//日期类略
int main()
{
//单参数的构造,构造+拷贝,编译器直接优化为构造C++98
Date d1 = 2022;
//临时对象具有常性,这里就发生了一次构造,编译器没有优化空间了
const Date& d2 = 2023;
//多参数的构造C++11
Date d3 = { 2022,10,16 };
return 0;
}
单参数、多参数的构造由构造+拷贝构造优化为直接构造。
匿名对象传参时的优化写法
- 优化前
void Func(Date d)
{
}
int main()
{
Date d1(2022);//构造
Func(d1);//传参发生拷贝构造
return 0;
}
- 优化后
void Func(Date d)//形参改成const Date& d也是一种优化
{
}
int main()
{
Func(Date(2022));//使用匿名对象传参,构造+拷贝构造,编译器直接优化为构造
Func(2022);//隐式类型转换的优化,也是由构造+拷贝构造,编译器直接优化为构造
return 0;
}
编译器对返回值的优化
- 无优化
Date Func()
{
Date d(2022);//构造
return d;//return时拷贝构造一份临时对象
}
int main()
{
Date ret;
ret = Func();//使用返回的临时对象进行赋值
return 0;
}
- 构造优化
Date Func()
{
Date d(2022);//构造
return d;//return时拷贝构造一份临时对象
}
int main()
{
Date ret = Func();//使用返回的临时对象拷贝构造ret
return 0;
}
正常的流程如注释所示,需要构造+拷贝构造+拷贝构造,但是编译器会将其优化为构造+拷贝构造。
- 使用匿名对象极致优化
Date Func()
{
return Date(2022);//使用2022构造一个临时匿名对象,return时再拷贝构造一份临时对象
}
int main()
{
Date ret = Func();//使用返回的临时对象进行拷贝构造
return 0;
}
这样写由构造+拷贝构造+拷贝构造,会被编译器直接优化为构造。是最优的写法。由于匿名对象的生命周期只在这一行,所以只能传值返回,而不能传引用返回。
本节结束,希望可以帮助到读者,如果对你有帮助请关注、点赞、评论支持一下。