一、静态成员
什么是静态成员:被static修饰的成员变量和成员函数就叫静态成员
普通成员的特点:
成员变量:每个类对象中都有一份属于自己的成员变量,相互之间没有关联、独立的
成员函数:隐藏这一个this指针,接收调用者地址用于区分调用者
静态成员的特点:
静态成员变量:
1、存储在data或者bss内存段中,一个类中的所有静态成员变量只有唯一一份,被所有类对象共享
2、一旦成员变量被声明为静态成员变量后,通过sizeof计算类字节数时,就不统计该变量的字节数
3、静态成员变量必须在类内加static声明,在类外单独定义、初始化,定义时无需加static
4、静态成员变量的生命周期不再依赖于某个类对象,伴随整个程序生命周期
5、因为静态成员变量的存在不依赖于任何类对象,所以可以无需实例化类对象,直接访问静态成员变量
对象名.成员变量/静态成员变量
对象名->成员变量/静态成员变量
类名::静态成员变量 (无需实例化对象,但是一般成员变量都是私有的)
静态成员函数:
1、没有隐藏的this指针,所以在静态成员函数中无法直接访问普通成员变量、普通成员函数,但是手动传递对象的地址间接访问,毕竟静态成员函数还是属于类的
2、但是可以直接访问静态成员变量、静态成员函数
3、调用方式
对象名.成员函数()/静态成员函数()
对象名->成员函数()/静态成员函数()
类名::静态成员函数() (无需实例化对象)
静态成员的作用:
1、静态成员相当于多了一层类作用域的全局变量、全局函数
2、静态成员变量适合存储所有类对象的公共属性,从而节约资源(税率、利率等)
3、静态成员函数可以当做访问私有的静态成员变量的公开接口,一方面不破坏类的封装性,另一方面可以无需实例化对象也能调用类的静态成员,让类本身具有管理自己成员的能力
常见面试题:
C和C++中的static的区别?
二、单例模式
什么是单例模式:只能实例化一个类对象
什么场景下使用单例模式:
进程管理器、日志管理器
网站访问计数器、应用配置程序
线程池、服务器的连接管理器
实现单例模式的原理:
1、禁止在类外随意实例化对象,把构造函数\拷贝构造私有化
2、确保类对象只有一份,在类中定义一个静态的类对象成员变量
3、提供一个获取静态类对象成员变量的公开的接口,设计静态成员函数用于获取那唯一的一个静态类对象
饿汉模式的单例:
程序运行开始时就立即实例化单例类对象,不管后期是否用得到都会完成实例化
优点:不可能被多个线程同时运行创建多份(线程安全)
缺点:如果后期使用不到,就浪费时间、资源
懒汉模式的单例:
什么时候使用,什么时候才去实例化单例类对象
优点:使用时才会创建,节约时间、资源
缺点:可能会被多个线程同时实例化,有可能会创建出多个单例类对象(线程不安全)
常考笔试题:实现饿汉模式的单例、懒汉模式的单例、线程安全的懒汉模式的单例
三、运算符函数
在C++中会把运算符当做函数处理,一个表达式,其实可能调用了很多运算符函数来完成 计算,这种特性对内建类型没有用,但是对于自建类型而言,通过设计运算符函数能够进行个性化运算,以此提高代码的可读性、易用性,例如string类
string str;
str += “hehe”; strcat(str,“hehe”);
str == str1; strcmp(str,str1);
运算符函数的格式:#表示运算符 O表示运算符对象
单目运算符: #O O#
成员函数:
[] O::operator#(void)
{
}
返回值不确定,唯一的参数就是调用者本身
全局函数:
[] operator#(O& o)
{
}
某种运算符成员函数、全局函数只能同时实现一个,不能一起实现
双目运算符: a # b
注意:左操作数是运算符函数的发起者
成员函数:
[] A::operator#(B& b)
{
}
全局函数:
[] operator#(A& a,B& b)
{
}
设计一个坐标类
Point
{
int x;
int y;
}
Point p1(1,1),p2(2,2);
p1 + p2;
p1 - p2;
四、运算类的双目运算符 O是类名
成员函数:a + b
const O O::operator+(const O& b)const
{
return O(x+b.x,y+b.y);
}
全局函数: a + b
const O operator+(const O& a,const O& b)
{
}
友元:
在实现类的全局运算符函数时,可能会使用到类内的私有成员,此时全局函数是没有访问权限,如果改变私有为公开会破坏类的封装性,如果提供公开的访问函数又非常麻烦,最好的方式是给该全局函数给与独家授权让其能够访问类内私有成员,这种行为称把该全局函数设置为友元函数
方式:在类内对全局函数声明,并在声明前加 friend
friend const O operator+(const O& a,const O& b);
五、输入输出运算符
在C++中 << >> 运算符不光是按位左移、按位右移,同时还是cout该类的输出运算符 cin该类的输入运算符
输出运算符:
cout << 10 << endl;
Test t;
cout << t << t2 << endl;
由于 << 运算符的调用者是cout对象,我们是无法在该对象的类中去设计一个输出运算符的成员函数,所以只能实现 << 运算的全局函数
ostream& operator<<(ostream& os,const O& t)
{
return os << t.x << t.y;
}
输入运算符:
cin >> num;
cin >> t1 >> t2;
istream& operator>>(istream& is,O& t)
{
return is >> t.x >> t.y;
}
注意:
1、由于输出、输入是可以连续进行的,所以返回值还应该是ostream、istream引用
2、因为无法在ostream、istream中重载运算符成员函数,所以<< >>只能重载成全局函数
3、如果在重载全局函数中使用到自己类中私有的成员变量,需要声明为友元函数
4、输出运算符函数中,第二个参数一定要加const,而输入运算符函数中不能加
六、运算类的单目运算符
单目:++/-- ! ~ - * & sizeof
成员函数: ~ ! -
const int num;
~0; ~num; true
~num = 10; false
O O::operator~(void)const
{
return O(x,y);
}
注意:运算对象可以具备常属性,因此需要是常函数,运算结果只是一个临时值,并且是右值
全局函数:
O operator~(const O& a)
{
return O(~a.x,~a.y);
}
七、自变运算符函数
C++的前后自变左右值问题:
能位于赋值运算符= 左边的就是左值,反之为右值
有名称、可以获取到存储地址的称为左值,反之为右值
C++前自变:++num = 10; // 成功 num=10
直接修改原对象,在原对象基础上实现自变,然后将原对象的引用返回,所以操作和返回的一直是原对象,是左值
C++后自变:num++ = 10; // 报错
先将原对象的数据存储到临时变量中,接着在原对象基础上自变,然后把临时变量以只读方式返回,并且该临时变量执行语句结束后立即销毁了,无法访问,因此结果是右值
++num++ // 后自变优先级更高,报错
(++num)++ // 先前自变为左值,成功
注意:C语言中,无论前后自变,结果都是右值
前自变运算符: ++a/--a
成员函数:
O& O::operator++(void)
{
x++,y++;
return *this;
}
全局函数:
O& operator++(O& a)
{
a.x++,a.y++;
return a;
}
后自变运算符:a++/a--
哑元:在参数列表末尾增加一个不使用且无形参名的int哑元类型,唯一目的就是用于区分是前自变还是后自变
成员函数:
O O::operator++(int)
{
return O(x++,y++);
}
全局函数:
O operator++(O& a,int)
{
return O(a.x++,a.y++);
}
注意:笔试题中出现过写前后自变运算符重载函数