1.类的定义
目前所知有两种类定义的方法,Class 和 Struct,相比于c,c++的struct可以定义成员函数了。我们常用的类就是class。
1.1访问限定符
![](https://i-blog.csdnimg.cn/direct/8e5debd8b0fa475aa63dd3e9820cc0d6.png)
1.2类域
2.实例化
2.1对象大小
内存对齐的目的:编译器是不能在任意位置开始读取数据,而是从整数倍位置开始读,这样才能方便读取
后定义先析构
3.this指针
4.类的默认成员函数
默认成员函数在没有显式实现的情况下,编译器会自主实现,一个类主要有6个默认成员函数,这次我们主要讲前四个
4.1构造函数
就是用来实例化时初始化对象
#include<iostream>
using namespace std;
class Date
{
public:
// 1.⽆参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// 3.全缺省构造函数
/*Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}*/
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
4.2析构函数
在需要销毁空间或栈帧的时候起作用
#include<iostream>
using namespace std;
typedef int STDataType;
class Stack
{
public:
Stack(int n = 4)
{
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (nullptr == _a)
{
perror("malloc申请空间失败");
return;
}
_capacity = n;
_top = 0;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
后构造的先析构
4.3拷贝构造函数
注意:拷贝构造函数拷贝都是浅拷贝(一个一个字节的拷贝,如果是地址则拷贝地址,而不是内容),如果遇到stack类等,就会出现很多错误
比如下列代码的_a,如果是编译器自主实现的拷贝构造,则会将_a的地址拷贝到新的类中,这样在结束时,就会出现多次析构;并且在修改_a的内容时,由于是同一个地址,而不是同样的内容,修改其中之一就会导致另外一边不需要修改的内容被修改,非常麻烦
以下得显式实现相应代码
Stack(const Stack& st)
{
// 需要对_a指向资源创建同样⼤的资源再拷⻉值
_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败!!!");
return;
}
memcpy(_a, st._a, sizeof(STDataType) * st._top);
_top = st._top;
_capacity = st._capacity;
}
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 编译报错:error C2652: “Date”: ⾮法的复制构造函数: 第⼀个参数不应是“Date”
//Date(Date d)
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date(Date* d)
{
_year = d->_year;
_month = d->_month;
_day = d->_day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
4.4赋值运算符重载
4.4.1 运算符重载
以operator+加减乘除等符号进行重载,但不能对.* :: sizeof ?: .这5个运算符重载
且重载运算符函数必须要有一个是类类型的形参
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
以上这种是不是成员函数时的例子
调用:d1 == d2 或 operator==(d1,d2);
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
以上就是作为成员函数省略的一个形参
加上const防止被修改,加上&可以省去拷贝构造函数的调用(因为传值或传地址了)
4.4.2赋值运算符重载
// 传引⽤返回减少拷⻉
// d1 = d2;
Date& operator=(const Date& d)
{
// 不要检查⾃⼰给⾃⼰赋值的情况
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// d1 = d2表达式的返回对象应该为d1,也就是*this
return *this;
}
返回值建议写为当前类的引用。return 可以返回*this
5.取地址运算符重载
5.1const成员函数
// void Print(const Date* const this) const
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
如果一个函数里没有什么值需要被修改,或者需要被维护,就用const保护起来
5.2取地址运算符重载
Date* operator&()
{
return this;
// return nullptr;
}
const Date* operator&()const
{
return this;
// return nullptr;
}
6.初始化列表
初始化列表是以跟在函数构造参数后:,为格式的初始化类成员的方式,后跟括号(括号内跟值或者表达式)初始化的顺序不依照初始化列表的顺序,而按照声明的顺序进行初始化
其中引用和const成员是必须在初始化列表初始化的
以下就是初始化列表常见问题
public:
Date(int& x, int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
,_t(12)
,_ref(x)
,_n(1)
{
// error C2512: “Time”: 没有合适的默认构造函数可⽤
// error C2530 : “Date::_ref” : 必须初始化引⽤
// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象
}
private:
int _year;
int _month;
int _day;
Time _t; // 没有默认构造
int& _ref; // 引⽤
const int _n; // const
7.类型转化
C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数
其实就是方便一点
!构造函数前⾯加explicit就不再⽀持隐式类型转换
class A
{
public:
// 构造函数explicit就不再⽀持隐式类型转换
// explicit A(int a1)
A(int a1)
:_a1(a1)
{}
//explicit A(int a1, int a2)
A(int a1, int a2)
:_a1(a1)
,_a2(a2)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
int main()
{
A aa = 1;
aa1.Print();
return 0;
}
8.static成员
#include<iostream>
using namespace std;
class A
{
public:
A()
{
++_scount;
}
A(const A& t)
{
++_scount;
}
~A()
{
--_scount;
}
static int GetACount()
{
return _scount;
}
private:
// 类⾥⾯声明
static int _scount;
};
// 类外⾯初始化
int A::_scount = 0;
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
cout << a1.GetACount() << endl;
// 编译报错:error C2248: “A::_scount”: ⽆法访问 private 成员(在“A”类中声明)
//cout << A::_scount << endl;
return 0;
}
9.友元
class A
{
// 友元声明
friend void func(const A& aa, const B& bb);
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
// 友元声明
friend void func(const A& aa, const B& bb);
private:
int _b1 = 3;
int _b2 = 4;
};
void func(const A& aa, const B& bb)
{
cout << aa._a1 << endl;
cout << bb._b1 << endl;
}
10.内部类
class A
{
private:
static int _k;
int _h = 1;
public:
class B // B默认就是A的友元
{
public:
void foo(const A& a)
{
cout << _k << endl; //OK
cout << a._h << endl; //OK
}
};
};
int A::_k = 1;
11.匿名对象
顾名思义,就是⽤类型(实参) 定义出来的对象叫做匿名对象,简单来说就是不需要定义类名
匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象
class A
{
public:
A(int n = 0):
a(n)
{}
void Print()
{
cout<<a<<endl;
}
private:
int a;
};
int main()
{
//只有一行代码生命周期
A(3);
cout<<A().Print();
return 0;
}