类和对象(下):static成员、友元类、内部类、匿名对象、优化——对象拷贝时的编译器优化
🔥个人主页:胡萝卜3.0
📖个人专栏: 《C语言》、《数据结构》 、《C++干货分享》、LeetCode&牛客代码强化刷题
⭐️人生格言:不试试怎么知道自己行不行
🎥胡萝卜3.0🌸的简介:
前言:在前面的学习中,我们探讨了C++构造函数的初始化列表和类型转换机制。首先详细讲解了初始化列表的使用方式、注意事项和必要性,强调引用成员、const成员及无默认构造的自定义类型成员必须使用初始化列表进行初始化。其次分析了成员变量声明顺序对初始化顺序的影响。然后系统介绍了C++中内置类型到类类型的隐式转换机制,包括单参数和多参数构造函数的转换支持,以及如何通过explicit关键字禁用隐式转换。最后简要说明了自定义类型间的转换条件。文章建议优先使用初始化列表进行成员初始化,并注意类型转换的优化和限制。这篇文章,我们接着学习类和对象的剩余内容!!!
目录
一、static 成员
1.1 知识点讲解
1.2 题目
二、友元类(“轻度社交”)
2.1 友元函数
2.2 友元类
2.3 成为互相的友元
三、内部类
四、匿名对象
4.1 知识点
4.2 应用场景
场景一
场景二
五、优化——对象拷贝时的编译器优化
5.1 知识点
5.1.1 概念
5.1.2 优化
5.2 优化展示(对比无优化)
5.3 手写笔记
一、static 成员
1.1 知识点讲解
用static修饰的成员变量,称之为静态成员变量。
嗯?这是什么意思?ok,我们通过代码就可以很清晰的看出其中的意思:
这时候就有uu想问了,当我们计算这个类的大小的时候,静态成员变量的大小有没有被计算在内?
其实是没有的,静态成员变量为所有类对象共享的,不属于某个具体的对象,并且是不存在对象中的,而是存放在静态区,生命周期是全局的,只是受到类域和访问限定符的限制
ok,既然我们已经知道如何在一个类中写一个静态成员变量,那我们该如何使用呢?
静态成员变量的使用是通过类名::静态成员 或者 对象.静态成员 或者 类型指针 -> 静态成员 来访问静态成员变量
那让我们尝试使用一下静态成员变量:
那这里为什么会报错呢?——
这是因为当静态成员变量属于私有的时候,类外是不能使用,只能在类里面可以使用,所以如果我们想要访问静态成员变量,可以将静态成员变量放在共有的位置
通过上面的操作,我们就可以访问了
但是当我们运行上面的代码时还是会报错,这又是什么原因?
那我们该怎么定义静态成员变量并在定义时初始化静态成员变量呢?
静态成员变量是属于整个类,属于私有时,使用访问限定符无法访问;只有当属于公有时才可以访问。
那这里博主就有个问题:如果是这样的话,那如果我们非得将静态成员变量放在私有,我们该怎么访问这个静态成员变量?
这时我们可以在类中写一个成员函数,并让static修饰这个成员函数,这样就可以通过通过类名::静态成员函数 或者 对象.静态成员函数 或者 类型指针 -> 静态成员函数 对属于私有的静态成员变量进行访问:
注意:
- 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
- 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针。
但是非静态的成员函数,可以访问任意的静态成员变量和静态成员函数!!!
这里就不能使用类名::静态成员函数,::这样访问,只能访问静态成员函数或者变量的。
- 突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
注意:
静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。
1.2 题目
求1+2+3+……+n
代码:
#include <regex>
class Sum
{
public:Sum(){_ret+=_i;_i++;}static int get_num(){return _ret;}
private:static int _i;static int _ret;
};
int Sum::_i=1;
int Sum::_ret=0;
class Solution {
public:int Sum_Solution(int n) {Sum arr[n];return Sum::get_num();}
};
二、友元类(“轻度社交”)
友元提供了一种突破类访问限定符封装的⽅式
友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到⼀个类的里面。
2.1 友元函数
当一个类外的函数想要访问类里面的私有成员变量时,我们就可以让这个函数成为这个类的友元函数,这样这个函数就是这个类的朋友,(是我的朋友就可以访问我的私有成员),并且一个函数可以成为多个类的友元。
代码演示:
我们看到当func函数想要访问类中的私有成员_a1,_b1,就必须让该func 函数成员这两个类的友元,这样就可以访问了。
但是上面的代码还是有一点错误:
所以我们需要给B这个类搞个前置声明,让编译器知道我这个B是一个类!!!
正确代码:
class B;
class A
{
public:friend void func(const A& aa, const B& bb);
private:int _a1 = 1;int _a2 = 1;
};
class B
{friend void func(const A& aa, const B& bb);
private:int _b1 = 1;int _b2 = 2;
};
void func(const A& aa, const B& bb)
{cout << aa._a1 << endl;cout << bb._b1 << endl;
}
int main()
{A aa1;B bb1;func(aa1, bb1);return 0;
}
2.2 友元类
当我们需要在一个类中大量访问另一个类中的东西,就可以将这个类变成友元类
对于上面这种,我们也可以让D中需要访问到C类中的私有成员的函数成为C的友元函数,但是如果有多个函数,此时就比较麻烦。
这时候就会有同学想问了:这里不需要在class C的前面加上class D的声明吗?
ok,这里是不需要的,因为友元函数仅仅是⼀种声明,友元声明可以是前置声明,友元函数不是类的成员函数
注意:
- 友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。
友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
2.3 成为互相的友元
C是D的友元,D是C的友元。但是当我们运行上面的代码,会发现编译器怎么又报错了?(博主写到这快要崩了😭),这是因为:
所以我们要将声明和定义分离:
//声明
class C
{friend class D;
public://声明void func3(const D& dd);void func4(const D& dd);
private:int _c1 = 2;int _c2 = 3;
};
class D
{friend class C;
public:void func1(const C& cc);void func2(const C& cc);
private:int _d1 = 4;int _d2 = 5;
};
//定义
void D::func1(const C& cc)
{cout << cc._c1 << endl;cout << _d1 << endl;
}
void D::func2(const C& cc)
{cout << cc._c2 << endl;cout << _d2 << endl;
}
void C::func3(const D& dd)
{cout << dd._d1 << endl;cout << _c1 << endl;
}
void C::func4(const D& dd)
{cout << dd._d2 << endl;cout << _c2 << endl;
}
int main()
{C cc;D dd;dd.func1(cc);dd.func2(cc);cc.func3(dd);cc.func4(dd);return 0;
}
我们可以将声明放到.h文件中,定义放到.cpp文件中!!!
三、内部类
博主第一次看到这个,就想起了,这是不是在问我们“累不累”(好累啊!!!)
那啥是内部类呢?
- 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是⼀个独立的类,跟定义在全局的类相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
- 内部类默认是外部类的友元类。
- 内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要就是给B使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地放都用不了。
代码演示:
其中B就是A的内部类,B中可以访问A的私有成员,但是A不能访问B的私有成员!!!
四、匿名对象
4.1 知识点
用类型(实参) 定义出来的对象叫做匿名对象,相比之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象。
那这时候就有uu想问了,那有名对象和匿名对象有啥区别呢?
有名对象的生命周期在整个域中,而匿名对象的生命周期只在当前这一行!!!
4.2 应用场景
场景一
如果我们想让某个成员函数只在当前一行使用,那我们可以创建匿名对象,用这个匿名对象来调用这个成员函数。
注意:
引用可以引用匿名对象,匿名对象只是具有常属性(像是被const修饰一样),所以需要在引用的前面加上const。
场景二
如果此时我们想给类类型给缺省值,应该怎么给?ok,这时候我们就可以给个匿名对象。
注意:const引用会延长匿名对象的生命周期,此时匿名对象的生命周期就会跟着s走,s的生命周期在哪,匿名对象的生命周期就在哪。
五、优化——对象拷贝时的编译器优化
5.1 知识点
5.1.1 概念
1、现代编译器会为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少一些传参和传返回值的过程中可以省略的拷贝。
2、如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新一点的编译器对于连续一个表达式步骤中的连续拷贝会进行合并优化,有些更新更“激进”的编译器还会进行跨行跨表达式的合并优化。
3、linux下可以将下面代码拷贝到test.cpp文件,编译时用
g++test.cpp-fno-elide-constructors
的方式关闭构造相关的优化。
5.1.2 优化
临时对象比较小,可以存在寄存器;优化就是省略掉之间的临时对象。