一、static 成员
在类中用 static 修饰的成员称为静态成员变量和静态成员函数,静态成员变量存放在静态区
-
静态成员被所有类对象共享,不属于某个具体的对象
外部访问类的静态成员时,只需要说明静态成员所在的类域即可,因此可以用 类名::静态成员 或者 对象.静态成员 进行访问 -
静态成员也是类的成员,因此非静态成员函数内既可以访问类的静态成员变量,也可以调用静态成员函数,外部访问时,受 public、protected、private 访问限定符的限制
-
静态成员变量只能在全局初始化,类中只是声明
静态成员变量并不属于某个对象,因此不能放到初始化列表中,也不能在声明时指定缺省值(C++11) -
静态成员函数没有隐藏的 this 指针
因此静态成员函数内不能访问任何非静态成员变量,也不能调用非静态成员函数
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1970, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
++_size; //成员函数可以访问静态成员
}
//静态成员函数
//通常静态成员变量都是私有的,因此外部不能通过类或对象直接访问静态成员
//为了不用创建对象就可以获取静态成员变量的值
//于是通常提供静态函数来获取静态成员变量,然后用类名来调用该静态成员函数
static int GetDateSize()
{
return _size; //静态成员函数内不可以访问任何非静态成员
}
private:
int _year;
int _month;
int _day;
static int _size; //静态成员变量
};
//静态成员变量必须在全局域中初始化
int Date::_size = 0;
int main()
{
Date d1;
Date d2;
//可以通过 类名:: 或者 对象. 来调用静态成员函数
cout << Date::GetDateSize() << endl;//输出2
cout << d1.GetDateSize() << endl; //输出 2
return 0;
}
二、友元
友元提供了一种突破类的封装的方式,使得外部可以直接访问类的私有成员,因此友元会增加模块的关联程度,不宜多用,友元分为 友元函数和友元类
在类中使用 friend 关键字声明外部函数为类的友元函数,友元函数可以直接访问类中的私有成员
#include <iostream>
using namespace std;
class Date
{
//友元函数声明
friend void Print(Date& d);
public:
//...
private:
int _year = 1970;
int _month = 1;
int _day = 1;
};
//Date 类的友元函数,虽然是类外部的函数,但是可以直接访问其私有成员
void Print(Date& d)
{
cout << "我是 Date 类的好朋友" << endl;
cout << d._year << "/" << d._month << "/" << d._day << endl;
}
int main()
{
Date d;
//我是 Date 类的好朋友
//1970 / 1 / 1
Print(d);
return 0;
}
- 友元函数是类外部的函数,不是类的成员函数,可以访问类的私有和保护成员
因此友元函数中没有该类的 this 指针,不能用 const 修饰 - 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
假设在 Date 类中,声明 Time 类为友元类,那么 Time 类的所有成员函数都是 Date 类的友元函数,都可以访问 Date 类中的私有成员
#include <iostream>
using namespace std;
class Date
{
//声明 Time 类为 Date 类的友元类
friend class Time;
public:
//...
private:
int _year = 1970;
int _month = 1;
int _day = 1;
};
//Date 类的友元类
//Time 类中的所有成员函数都可以直接访问 Date 类的私有成员
class Time
{
public:
void Print(Date& d)
{
cout << d._year << "/" << d._month << "/" << d._day << " ";
cout << _hour << ":" << _minute << ":" << _second << endl;
}
private:
int _hour = 0;
int _minute = 0;
int _second = 0;
};
int main()
{
Date d;
Time t;
t.Print(d); //输出 1970/1/1 0:0:0
return 0;
}
- 友元类的关系是单向的,不具有对称性
在上述的 Date类和 Time 类中,在 Date类中声明了 Time类是 Date类的友元类,但 Date类并不是 Time类的友元类,Date 类的成员函数不能直接访问 Time类的私有成员 - 友元类的关系不是传递性的
如果 C 是 B 的友元类, B 是 A 的友元类,并不能说明 C 是 A 的友元类
三、内部类
在类 A 中定义类 B,类 B 就称为类 A 的内部类
内部类和外部类是相互独立的,只是在访问上受到约束
-
内部类对外部类的访问:内部类天生就是外部类的友元类,内部类可以直接访问外部类的静态成员,不需要外部类的类名/对象,也可以通过外部类的对象参数来访问外部类中的所有成员
-
外部类对内部类的访问: 受到内部类的访问限定符限制
-
外部类的外部对内部类的访问: 受到外部类的访问限定符限制,内部类可以定义在外部类的 public、protected、private 下
#include <iostream>
using namespace std;
class A
{
public:
class B
{
public:
static void Print()
{
cout << "外部类的静态成员:" << _aaa << endl;
}
private:
int _b;
};
private:
static int _aaa;
};
int A::_aaa = 10;
int main()
{
//类 A 和 类 B 空间是独立的
cout << sizeof(A) << endl; //输出 4
cout << sizeof(A::B) << endl; //输出 4
A::B::Print(); //输出 外部类的静态成员:10
return 0;
}
四、匿名对象
类可以实例化没有名字的对象,特点是:匿名对象的声明周期只有一条语句,匿名对象即用即销毁
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1970, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
cout << "Date()" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//输出 Date()
// ~Date()
Date(); //匿名对象
//输出 Date()
// ~Date()
Date(2023); //匿名对象
return 0;
}
五、explicit关键字
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1970, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//C++98 中调用单参数的构造函数时可以进行隐式类型转换,多参数不行
//编译器会通过 1 构造一个 Date类型的临时对象然后拷贝给 d1 对象
Date d1 = 1; //<==> Date d1 = Date tmp(1);
const Date& ref1 = 1; //即可说明隐式类型转换存在临时对象
//C++11 后,调用多参数的构造函数也可以进行隐式类型转换
//编译器会通过 2023, 2, 11 构造一个 Date类型的临时对象然后拷贝给 d2 对象
Date d2 = {
2023, 2, 11 }; //<==> Date d2 = Date tmp(2023, 2, 11);
const Date& ref2 = {
2023, 2, 11 }; //即可说明隐式类型转换存在临时对象
return 0;
}
在构造函数前加上关键字 explicit,可以禁止实例化对象时的隐式类型转换
explicit Date(int year = 1970, int month = 1, int day = 1)
六、拷贝对象时的一些编译器优化
在 值传递参数 和 函数值传递返回 的某些场景下,一般编译器会做一些优化,减少对象的拷贝,有时还是非常有用的,但是 在能传引用的时候尽量传引用
编译器优化一般只会优化一条语句中的过程
#include <iostream>
using namespace std;
class Demo
{
public:
Demo(int d1 = 1, int d2 = 2)
: _d1(d1)
, _d2(d2)
{
cout << "Demo()" << endl;
}
Demo(const Demo& d)
: _d1(d._d1)
, _d2(d._d2)
{
cout << "Demo(const Demo& d)" << endl;
}
void operator=(const Demo& d)
{
_d1 = d._d1;
_d2 = d._d2;
cout << "operator=" << endl;
}
private:
int _d1;
int _d2;
};
void Test1(Demo d)
{
;
}
Demo Test2()
{
Demo tmp;
//...
return tmp;
}
Demo Test4()
{
return 1;
}
Demo Test5()
{
return Demo(1, 2);
}
int main()
{
//隐式类型转换,这里会创建临时变量
//构造临时变量 + 拷贝构造 -> 编译器会优化为直接构造
Demo d1 = 1; //只输出一个 Demo
Test1(1); //只输出一个 Demo
Test1(Demo(1, 2)); //构造匿名对象 + 拷贝构造 -> 编译器会优化为直接构造
cout << "-------------" << endl;
//函数传值返回对象时,会通过临时变量返回
//拷贝构造临时变量 + 拷贝构造d2 -> 编译器优化为 直接拷贝构造d2
Demo d2 = Test2();
cout << "-------------" << endl;
Demo d3;
//拷贝构造临时变量 + 赋值重载d3 -> 编译器不优化
d3 = Test2();
cout << "-------------" << endl;
//构造临时变量 + 拷贝构造d4 -> 编译器优化为 直接构造d4
Demo d4 = Test4();
cout << "-------------" << endl;
//构造匿名对象 + 拷贝构造临时变量 + 拷贝构造d4 -> 编译器优化为 直接构造d4
Demo d5 = Test5();
return 0;
}
八、类和对象
计算机只认识二进制格式的数据,对于现实生活中的实体,计算机并不认识,因此用户需要通过某种面向对象的语言,对实体进行描述,计算机才可以认识该实体
当我们想让计算机认识洗衣机实体时,需要经过以下步骤:
-
用户先对现实中的洗衣机实体进行抽象,在人为思想层面对洗衣机进行认识,抽象出洗衣机的属性和功能,即对洗衣机进行抽象认知的一个过程
-
经过1之后,在人的头脑中已经对洗衣机有了一个清晰的认识,为了让计算机可以识别人想象中的洗衣机,便需要人通过某种面相对象的语言(比如:C++、Java、Python等)将人头脑中的洗衣机用类来进行描述
-
经过2之后,在计算机中拥有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣机对象进行描述的,还需要通过洗衣机类实例化出具体的洗衣机对象,此时计算机也就认识了洗衣机实体
-
用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了
类就是描述实体(对象)具有那些属性和方法,描述完成后就可以形成一种新的自定义类型,在用该自定义类型便可以实例化具体的对象
八、面向过程和面向对象
C语言是面向过程的语言,在使用 C语言解决一个问题时,通常都是分析出解决问题的步骤,然后根据步骤设计对应的函数,通过调用函数从而解决这个问题
C++ 是面向对象的语言,在使用 C++解决一个问题时,通常是将问题涉及到的事物分类,然后根据分类设计出每一个类,然后用类实例化创建对象,通过对象之间的交互解决问题
九、封装
封装是面向对象的三大特性之一
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互,封装可以使得用户不用知道内部的实现细节可以更方便用户使用
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来
隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用