1. 类的实例化
类不占用空间,在使用的时候才发生类的实例化,才开辟了空间
- 开辟了空间叫定义,没有开辟空间的叫声明
- static 修饰的声明,只在当前文件可见,链接时没有放进符号表,所以两次打印的地址不同
- extern 修饰的声明,在链接时会放进符号表,所以两次打印的地址相同2类对象模型
2. 类对象的存储方式猜测
2.1 猜测一:对象中包含类的各个成员
- 缺陷:每个对象中成员变量是不同的,都调用同一份函数,每次调用相同的代码就会保存一次,多个对象的话,相同代码保存多次,浪费空间。
2.2 猜测二:代码只保存一份,在对象中保存存放代码的地址
- 这里在访问类成员函数的时候,是通过对象中保留了类成员函数表的指针,通过指针的解引用进行访问的,
2.3 猜测三:只保存成员变量,成员函数存放在公共的代码段
- 编译链接时就根据函数名去公共代码区找到函数地址
- C++类对象的存储方式是第三种,只保存成员变量,成员函数存放在公共的代码段
3. 类的大小
#include<iostream>
using namespace std;
class A1
{
public:
void PrintA()
{
cout << _a << endl;
}
void func()
{
cout << "void A::func()" << endl;
}
private:
char _a;
int _i;
};
class A2 {
public:
void f2() {}
};
class A3{
};
int main()
{
cout << sizeof(A1) << endl;
cout << sizeof(A2) << endl;
cout << sizeof(A3) << endl;
return 0;
}
- 一个类的大小,实际就是该类中”成员变量”之和,这里和C语言一样也会发生内存对齐
- 空类的大小:编译器给了空类一个字节来唯一标识这个类的对象。
4. 成员变量命名规则的建议
驼峰法——单词和单词之间首字母大写间隔
class Date
{
public:
void Init(int year)
{
_year = year;
}
private:
int _year;
};
- 函数名、类名等所有单词首字母大写 DateMgr
- 变量首字母小写,后面单词首字母大写 dateMgr
- 成员变量,首单词前面加_ _dateMgr
5. this指针
#include<iostream>
using namespace std;
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年 -> 声明
int _month; // 月
int _day; // 日
};
int main()
{
Date d1;
d1.Init(2022, 7, 17);
Date d2;
d2.Init(2022, 7, 18);
d1.Print();
d2.Print();
return 0;
}
-
Date类中有 Init 与 Print 两个成员函数,他们都是放在公共代码区中的,而函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,lnit这个函数如何知道应该对d1进行初始化,而不是给d2进行初始化
-
C++中通过引入this指针解决该问题,即:
-
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,
-
让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。
-
只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
-
5. 1 显示this指针
#include<iostream>
using namespace std;
class Date
{
public:
void Init(Date* const this, int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
void Print(Date* const this)
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
private:
int _year; // 年 -> 声明
int _month; // 月
int _day; // 日
};
int main()
{
Date d1;
d1.Init(&d1,2022, 7, 17);
Date d2;
d2.Init(&d2,2022, 7, 18);
d1.Print();
d2.Print();
return 0;
}
- 这里我将编译器隐藏起来的this指针显示出来了,但这样编译器是会报错的,
- 注意:实参和形参位置不能显示传递和接收this指针,但是可以在成员函数内部使用this指针
5.2 this指针的特性
- this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
- 只能在“成员函数”的内部使用
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是“成员函数”第一个隐含的指针形参,vs下面传递是通过ecx寄存器传递的,这样this访问变量的时候可以提高效率,不过这些优化取决于编译器,
5.3 关于this指针的存储位置
- 栈区,因为this指针本质上是“成员函数”的形参
5.4 用this指针证明类对象的存储方式
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "Print()" << endl;
//cout << this << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print(); //正常运行
return 0;
}
- 这段代码不会发生崩溃,编译报错的情况,它是会正常运行的
- p->Print如果对p进行了解引用,就会报错,但这里是正常运行的,可见类对象的存储方式是猜测三:只保存成员变量,成员函数存放在公共的代码段
6. 类的6个默认成员函数
#include<iostream>
using namespace std;
class A
{
};
int main()
{
cout << sizeof(A);
return 0;
}
- 如果一个类中什么成员都没有,简称为空类。
- 空类并不是真的什么都没有,任何类在什么都不写时,
编译器会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。
6.1构造函数
6.1.1构造函数的特点
- 函数名与类名相同。
- 无返回值的概念
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
- 不传参也可以调用
6.1.1 无参构造和带参构造
#include<iostream>
using namespace std;
typedef int DataType;
class Date
{
public:
Date()
{
_year = 2022;
_month = 7;
_day = 27;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date A;
A.Print();
return 0;
}
6.1.2 构造函数的冲突
#include<iostream>
using namespace std;
typedef int DataType;
class Date
{
public:
Date()
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
- 这里编译不会通过的,无参的构造函数和半缺省/全缺省的构造函数,默认构造函数都叫构造函数,它们只能出现其中的一个,
6.1.3 错误使用
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
- 这种就是错误的使用构造函数,没有合适的默认构造函数可用
6.1.4 构造函数内语句
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 0, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date A;
return 0;
}
-
构造函数体中的语句准确的来说应该叫:赋初值,而不能称作初始化
因为初始化只能初始化一次,而构造函数体内可以多次赋值
6.1.5 构造函数体内调用成员函数的构造函数
#include <iostream>
using namespace std;
class Time
{
public:
//Time(int hour = 0)
Time(int hour)
{
_hour = hour;
}
private:
int _hour;
};
class Date
{
public:
// 要初始化_t 对象,必须通过初始化列表
Date(int year, int hour)
{
// 函数体内初始化
_year = year;
Time t(hour);
_t = t;//拷贝构造
}
private:
int _year;
Time _t;
};
int main()
{
Date d(2022, 1);
return 0;
}
- 这里想初始化Date里面的_t成员,直接用构造函数,在函数体内赋值,是十分不好用的,
6.1.6 初始化列表
#include <iostream>
using namespace std;
class Time
{
public:
Time(int hour)
{
_hour = hour;
}
private:
int _hour;
};
class Date
{
public:
Date(int year, int hour, int& x)
:_year(year)
,_t(hour)
, _N(10)
, _ref(x)
{
// 函数体内初始化
_year = year;
_ref++;
}
private:
int _year;
Time _t;
const int _N;
int& _ref;
};
int main()
{
int y = 0;
Date d(2022, 1, y);
return 0;
}
-
类中包含以下成员,必须放在初始化列表位置进行初始
-
引用成员变量
-
const成员变量(const必须在定义的地方初始化,只有一次机会)
-
-
对于构造函数大部分情况推荐:用初始化列表初始化
6.1.6 初始化列表的初始顺序
#include <iostream>
using namespace std;
class A
{
public:
// 初始化应该按声明顺序初始化
A(int a)
// 成员变量定义
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
// 成员变量声明
int _a2;
int _a1;
};
int main() {
A aa1(1);
aa1.Print();
return 0;
}
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,
与其在初始化列表中的先后次序无关 - _a2先声明,就先定义,这时_a是随机值
- _a1后声明,后定义,这时_a1 = 1
6.1.7 编译器自动生成的默认成员函数
#include<iostream>
using namespace std;
typedef int DataType;
class Time
{
public:
/*Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}*/
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year;
int _month;
int _day;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
- 在C++中规定默认生成构造函数
- a:内置类型成员不做处理
- b:自定义类型成员会去调用他的默认构造函数
- C++中的这个设计就会导致一个问题,
- 比如说在这段代码中,生成d1的时候就会调用它的默认构造函数,而_year和_month和_day都是内置类型,只有_t是自定义类型,
- 对于_t这个对象又会去调用它的默认构造函数,_hour和 _minute和 _second都是内置类型,但是又会因为内置类型成员不做处理。导致最后调用了默认构造函数之后还是随机值
6.1.8 C++ 对内置类型的缺陷的补丁
- 给的默认值不是初始化,是给的缺省值
6.2 析构函数(清理空间)
6.2.1 析构函数特点
- 析构函数名是在类名前加上字符 ~
- 无参数无返回值类型。
- 一个类只能有一个构函数d且不能重载若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
#include<iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:
//构造函数
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
//析构函数
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
void TestStack()
{
Stack s;
s.Push(1);
s.Push(2);
}
int main()
{
TestStack();
return 0;
}
6.2.2 编译器自动生成的析构函数
#include<iostream>
using namespace std;
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
- 编译器生成的默认析构函数对内置类型不做处理,自定义类型会去调用它的析构函数
-
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数就可以,比如Date类;
-
有资源申请时,一定要写,否则会造成资源泄漏,比如 Stack 类。 malloc 开辟的空间
6.2.3 析构顺序
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)->" << _a << endl;
}
~A()
{
cout << "~A()->" << _a << endl;
}
private:
int _a;
};
A aa3(3);
void f()
{
static A aa0(0);
A aa1(1);
A aa2(2);
static A aa4(4);
}
// 构造顺序:3 0 1 2 4
// 析构顺序:~2 ~1 ~4 ~0 ~3
int main()
{
f();
//f();
return 0;
}
- 构造函数: 全局优先,依次构造
- 析构函数: 栈区优先,先进后出
如果对f()函数调用两次呢?
- 变量aa1和aa2是局部变量(存放在栈区中),出了函数的作用域就不存在了,生命周期也就没了
- 变量aa3和aa4被static修饰(存放在静态区),出了函数的的作用域依旧存在,全局变量aa3就不用多说了
6.3 拷贝构造函数
6.3.1 拷贝构造函数的特点
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,
- 使用传值方式编译器直接报错,因为会引发无穷递归调用。
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
- 如果使用的是传值传参会发生无穷递归,形参是实参的临时拷贝
6.3.2 深浅拷贝问题
#include<iostream>
using namespace std;
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
//构造函数
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
//析构函数
~Stack()
{
if (_array) {
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
- 这里会直接崩溃,这里虽然能把s1拷贝给s2,但是这是值拷贝,是属于浅拷贝,
- 这段空间会被析构两次,导致程序直接崩溃,
- 需要自己实现深拷贝解决这个问题
6.4 赋值运算符重载
6.4.1 方案一: 运算符重载到类外面
#include<iostream>
using namespace std;
// 全局的operator==
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
bool operator==(const Date& d1, const Date& d2) {
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
void Test()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
//cout << operator==(d1, d2) << endl;
cout << (d1 == d2) << endl;
}
int main()
{
Test();
return 0;
}
6.4.2 方案二:运算符重载到类里面
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator==(const Date& d2)
{
return this->_year == d2._year
&& this->_month == d2._month
&& this->_day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
//cout << d1.operator==(d2) << endl;
cout << (d1 == d2) << endl;
}
int main()
{
Test();
return 0;
}
- 将operator定义在类里面,就不用把类中的成员变量公开了,一定程度上解决了方案一的问题
6.4.3 相关注意实现
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ? : . 这5个运算符不能重载。这个经常在笔试选择题中出现。
6.4.4 赋值运算符重载格式
#include<iostream>
using namespace std;
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time& operator=(const Time& t)
{
cout << "Time& operator=(const Time& t)" << endl;
//防止自己跟自己赋值
if (this != &t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
return *this;
}
private:
int _hour;
int _minute;
int _second;
};
int main()
{
Time a1;
Time a2 = a1;
Time a3 = a2 = a1;
return 0;
}
- 赋值运算符重载需要支持连续赋值,所以返回*this,且*this出了函数operator的作用域还存在,所以这里是可以用传引用返回的
- 同样赋值运算符重载也会存在深拷贝问题
7. 操作符前置++和后置++的重载
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date& operator++()// 前置加加
{
_day += 1;
return *this;
}
Date operator++(int)// 后置加加
{
Date temp(*this);
_day += 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
Date d1(2022, 1, 13);
d = d1++;
d = ++d1;
return 0;
}
- C++中为了解决前置++和后置++中函数名是相同的问题,对后置++默认多传一个参数(编译器做的),用int接收
- 有了上面的解决方法,前置++和后置++就构造了函数重载
8. const成员函数
#include<iostream>
using namespace std;
class A
{
public:
//void Print(const A* const this)
void Print() const
{
//_year = 1;
cout << _year << "/" << _month << "/" << _day << endl;
}
//void Print(A* const this)
void Print()
{
_year = 1024;
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year = 1; // 年
int _month = 1; // 月
int _day = 1; // 日
};
int main()
{
A d1;
const A d2;
d1.Print();
d2.Print();
return 0;
}
-
被const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针 ,表明在该成员函数中 不能对类的任何成员进行修改
-
被const修饰会改变权限,权限的访问是不能放大的
9. 取地址及const取地址操作符重载
#include<iostream>
using namespace std;
class Date
{
public:
// 全缺省的构造函数
Date(int year = 1,int month = 1,int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date A;
const Date B;
cout << &A << endl;
cout << &B << endl;
return 0;
}
- 这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
-
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如 想让别人获取到指定的内容!
10. explicit关键字
#include <iostream>
using namespace std;
class Date
{
public:
explicit Date(int year)
//Date(int year)
:_year(year)
{
cout << "Date(int year)" << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
};
int main()
{
Date d1(2022); // 直接调用构造
Date d2 = 2022; // 隐式类型转换:构造 + 拷贝构造 + 编译器优化 ->直接调用构造
const Date& d3 = 2022;
return 0;
}
- explicit可以禁掉构造函数的类型转换
11. 匿名对象
#include <iostream>
using namespace std;
//explicit关键字 + 匿名对象
class Date
{
public:
explicit Date(int year)
//Date(int year)
:_year(year)
{
cout << "Date(int year)" << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
};
class Solution {
public:
int Sum_Solution(int n) {
// ...
return 0;
}
};
int main()
{
// 匿名对象 -- 声明周期只有这一行
Date(2000);
// 匿名对象 一些使用场景
Solution slt;
slt.Sum_Solution(10);
Solution().Sum_Solution(10);//匿名对象的应用
return 0;
}
- explicit对匿名对象没用,匿名对象 -- 声明周期只有这一行
12. static成员
静态成员变量一定要在类外进行初始化
12.1 特性一
#include <iostream>
using namespace std;
class Test
{
private:
static int _n;
};
int main()
{
cout << sizeof(Test) << endl;
return 0;
}
- 静态成员为所有类对象共享,不属于某个具体的对象
- 静态成员并不计入其总大小之和
12.2 特性二
class Test
{
private:
static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;
- 静态成员变量必须在类外定义,定义时不添加static关键字
- 注意:这里静态成员变量_n虽然是私有,但是我们在类外突破类域直接对其进行了访问。这是一个特例,不受访问限定符的限制,否则就没办法对静态成员变量进行定义和初始化了
12.3 特性三
class Test
{
public:
static void Fun()
{
cout << _a << endl; //error不能访问非静态成员
cout << _n << endl; //correct
}
private:
int _a; //非静态成员
static int _n; //静态成员
};
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 含有静态成员变量的类,一般含有一个静态成员函数,用于访问静态成员变量
12.4 访问静态成员变量的方法
12.4.1 当静态成员变量为公有时,有以下几种访问方式:
#include <iostream>
using namespace std;
class Test
{
public:
static int _n; //公有
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
Test test;
cout << test._n << endl; //1.通过类对象突破类域进行访问
cout << Test()._n << endl; //3.通过匿名对象突破类域进行访问
cout << Test::_n << endl; //2.通过类名突破类域进行访问
return 0;
}
- 类名::静态成员 或者 对象.静态成员 来访问
12.4.1当静态成员变量为私有时,有以下几种访问方式:
#include <iostream>
using namespace std;
class Test
{
public:
static int GetN()
{
return _n;
}
private:
static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
Test test;
cout << test.GetN() << endl; //1.通过对象调用成员函数进行访问
cout << Test().GetN() << endl; //2.通过匿名对象调用成员函数进行访问
cout << Test::GetN() << endl; //3.通过类名调用静态成员函数进行访问
return 0;
}
- 调用静态成员函数访问静态成员变量
12.5 经典问题
12.5.1 静态成员函数可以调用非静态成员函数吗
-
不可以。因为非静态成员函数的第一个形参默认为this指针,而静态成员函数中没有this指针,故静态成员函数不可调用非静态成员函数。
12.5.2 非静态成员函数可以调用静态成员函数吗
- 可以。因为静态成员函数和非静态成员函数都在类中,在类中不受访问限定符的限制
12.5.3 案例:实现一个类,计算程序中创建出了多少个类对象
#include <iostream>
using namespace std;
class A
{
public:
// 构造函数
A()
{
++_scount;
}
// 拷贝构造函数
A(const A& t) { ++_scount; }
// 静态成员函数 -- 没有this指针
static int GetCount()
{
//_a = 1;
return _scount;
}
private:
int _a;
// 静态成员变量,属于整个类,生命周期整个程序运行期间,存在静态区
static int _scount; // 声明
};
// 类外面定义初始化
int A::_scount = 0;
int main()
{
A a1;
A a2;
A a3(a2);
cout <<A::GetCount() << endl;
return 0;
}
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
13. 友元
- 友元函数可以访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
- 友元关系是单向的,不具有交换,不能传递
#include <iostream>
using namespace std;
class Date
{
// 友元: 使私有在类外面能够访问
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month; int _day;
};
ostream& operator<<(ostream& _cout, const Date& d) {
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d) {
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
int main()
{
Date d;
cin >> d;
cout << d << endl;
return 0;
}
-
this指针默认是第一个参数也就是左操作数
-
如果将operator<<重载成成员函数,那么使用的时候就会很尴尬,比如d >> cin,所以出现了友元来解决这个问题
- 注意: 友元没有传递性,如果B是A的友元,C是B的友元,则不能说明C时A的友元。
13.1 友元的缺陷
- 友元提供了一种突破封装的方式,虽然提供了便利,但是友元会增加耦合度,破坏了封装,所以友元不宜多用,
14. 内部类
#include <iostream>
using namespace std;
class A
{
public:
// B定义在A的里面
// 1、受A的类域限制,访问限定符
// 2、B天生是A的友元,即B可以访问A中的成员变量
class B
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a._h << endl;//OK -- 友元
}
private:
int _b;
};
private:
int _h;
static int k;
};
int A::k = 1;
int main()
{
cout << sizeof(A) << endl;
A a;
A::B b;
return 0;
}
-
里面的类是外面类的友元,即里面的类可以直接访问外面的成员变量
15. 编译器的优化
15.1 对连续表达式的优化
#include <iostream>
using namespace std;
class W
{
public:
W(int x = 0)
{
cout << "W()" << endl;
}
W(const W& w)
{
cout << "W(const W& w)" << endl;
}
W& operator=(const W& w)
{
cout << "W& operator=(const W& w)" << endl;
return *this;
}
~W()
{
cout << "~W()" << endl;
}
};
void f1(W w)
{
}
void f2(const W& w)
{
}
int main()
{
W w1;
f1(w1);
f2(w1);
cout << endl << endl;
f1(W()); // 构造+拷贝构造--编译器的优化--直接构造
return 0;
}
- 连续一个表达式步骤中,连续构造一般都会优化
- 构造+拷贝构造--编译器的优化--直接构造
15.2 对返回值的优化
#include <iostream>
using namespace std;
class W
{
public:
W(int x = 0)
{
cout << "W()" << endl;
}
W(const W& w)
{
cout << "W(const W& w)" << endl;
}
W& operator=(const W& w)
{
cout << "W& operator=(const W& w)" << endl;
return *this;
}
~W()
{
cout << "~W()" << endl;
}
};
W f(W u)
{
W v(u);
W w = v;
return w;
}
int main()
{
W x;
W y = f(x); // 1次构造 4次拷贝
return 0;
}
- 传值返回是需要借助寄存器的,编译器会这里做优化,不借助中间的寄存器,拷贝构造就会少一次,
- linux中的g++优化更大,比vs2019都少一次
16. 类和对象经典题目
16.1 题目一:求1+2+3+…+n
题目描述:
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)
直接new一堆类的数组
#include <iostream>
#include <string>
using namespace std;
class Add {
public:
Add() {
_num++;
_ret += _num;
}
static int _num;
static int _ret;
};
int Add::_num = 0;
int Add::_ret = 0;
class Solution {
public:
int Sum_Solution(int n) {
// 解决多个案例
Add::_num = 0;
Add::_ret = 0;
Add* p = new Add[n];
return Add::_ret;
}
};
int main()
{
cout << Solution().Sum_Solution(10) << endl;
return 0;
}
- 注意:多个测试案列的时候,记得清零(static)