类
在C++中可以使用class或者struct来定义类,类中包含成员变量和成员函数,通过限定访问符,限制对成员的访问,限定访问符有:public(公有的)、private(私有的)、protected(保护的)。
#include<iostream>
using namespace std;
class ListNode
{
//1.成员变量
//2.成员函数
int _val;
ListNode* _next;
ListNode* _prev;
};
struct ListNode
{
int _val;
ListNode* _next;
ListNode* _prev;
};
int main()
{
return 0;
}
在C语言中struct是用于定义结构体的,因为C++兼容C语言,所以在C++中兼容struct定义结构体的方法,同时struct也可以用于定义类。
访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
struct 与class定义类的区别是,struct默认的限定访问符为public(公有的),class默认限定访问符为私有的。
类和结构体不同的是除了可以定义变量,还可以定义方法。
封装
- 数据和方法都封装到类里面
- 可以给你访问的定义成公有,不想让你访问的定义为私有或者保护。
类对象的大小
对象中有成员函数和成员变量,那计算类的大小时,计算的是哪一部分。
类对象的存储方式
类对象中 只保存成员变量,成员函数存储在公共的代码端段。
class Student
{
char _name[10];
int _age;
int _id;
};
class A
{
};
class B
{
public:
void Print()
{
cout<<"hello"<<endl;
}
};
int main()
{
Student s;
A a;
B b;
cout<<sizeof(s)<<endl;//20
cout<<sizeof(a)<<endl;//1
cout<<sizeof(b)<<endl;//1
return 0;
}
结论:一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比 较特殊,编译器给了空类一个字节来唯一标识这个类。
隐含的this指针
每个成员函数都有个指针形参,叫this指针,名字是固定的,是成员函数的第一个参数,它是隐试的编译器自动处理。在函数的实参处,编译器会将对象的地址传给成员函数的第一个形参this指针。
this指针不可以被修改。
#include<iostream>
using namespace std;
class Data
{
public:
void Init(int year, int month, int day)//void Init(Data* this ,int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()//void Print(Data* this)
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data a;
a.Init(2021, 8, 24);//a.Init(&a , 2021 , 8 , 24);
a.Print();
return 0;
}
this指针存储在哪里
this指针存储在栈中,在vs中this指针是通过寄存器ecx存储 的
#include<iostream>
using namespace std;
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
void Show()
{
cout << "show" << endl;
}
private:
int _a;
};
int main()
{
A* a = NULL;
a->PrintA();//程序崩溃
a->Show();//正常输出
return 0;
}
成员函数存储在公共代码段,所以不会到对象中去查找成员函数
PrintA函数中由于this指针会解引用,导致崩溃(NULL)
Show函数没有对this指针进行解引用,所以正常输出
类的默认成员函数
如果我们不实现,会使用默认的函数
- 构造函数
- 析构函数
- 拷贝构造函数(构造函数的重载)
- 赋值运算符重载
- 取地址运算符重载
构造函数
- 构造函数支持函数重载。
- 函数名与类名相同
- 函数没有返回值
- 在对象实例化的时候自动调用构造函数
- 在函数构造时调用的函数,这个函数完成初始化操作。(自动)
- 构造函数并不是开辟空间,而是初始化对象。
#include<iostream>
using namespace std;
class Date
{
public:
//构造函数-->支持函数重载
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date()
{
_year = 0;
_month = 1;
_day = 1;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2021, 8, 24);
d1.Print();
Date d2;
d2.Print();
return 0;
}
如果类中没有显示的定义构造函数,则C++编译器自动生成一个无参的默认构造函数。一但显示定义的构造函数,编译器将不在生成。
生成的默认构造函数就有以下特点
- 针对内置的类型的成员变量不做处理。
- 对于自定义类型的成员变量,调用它的构造函数初始化。
#include<iostream>
using namespace std;
class Time
{
public:
Time()
{
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
默认构造函数(不需要参数就可以调用的)有:全缺省的构造函数、编译器生成的、自定义的无参构造函数;(只能存在一个)
析构函数
析构函数与构造函数相反,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。
析构函数特征如下:
- 析构函数是在类名前加符号~
- 析构函数没有返回值
- 一个类中有且只有一个析构函数,若未显示定义,系统会自动生成默认的析构函数
- 对象生命周期结束时,C++编译器自动调用析构函数
- 默认生成的析构函数,对内置类型不做处理,自定义类型调用它的析构函数。
#include<iostream>
using namespace std;
class Stack
{
public:
//构造函数
Stack(int n = 10)
{
_a = (int*)malloc(sizeof(Stack));
_size = _capacity = n;
cout << "Stack" << endl;
}
//析构函数 当对象生命周期结束时,自动调用 完成清理工作
~Stack()
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
cout << "`Stack"<< endl;
}
private:
int* _a;
int _size;
int _capacity;
};
int main()
{
Stack s1;
return 0;
}
拷贝构造函数
- 拷贝构造函数是构造函数的一种重载形式。
- 拷贝构造函数的参数只有一个并且必须使用引用传参,使用传值会引发死递归。
- 若未显示定义,会自动生成默认的拷贝构造函数。
默认生成的拷贝构造:
- 内置类型成员,会按照字节序的拷贝(浅拷贝);
- 自定义类型调用它的拷贝构造函数。
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = 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;
};
int main()
{
Date d1(2021, 8, 26);
Date d2(d1);
d2.Print();
return 0;
}
赋值运算符重载函数
当一个类,没有显示定义时,编译器会自动生成默认的赋值运算符重载函数,完成对象按字节序的值拷贝(浅拷贝)。
内置类型成员,会完成按字节序的浅拷贝。自定义类型调用它的赋值运算符。
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
实现赋值运算符重载和拷贝构造函数的意义?
赋值运算符重载和拷贝构造函数默认生成的都是函数都是实现的浅拷贝,当拷贝指针时,会发生程序崩溃,原因是因为析构函数对同一块空间的多次释放导致。
以下代码使用编译器生成的拷贝构造和赋值运算符存在什么问题
#include<iostream>
using namespace std;
class Stack
{
public:
Stack(int n = 10)
{
_a = (int*)malloc(sizeof(int)*n);
}
~Stack()
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
private:
int*_a;
size_t _size;
size_t _capacity;
};
int main()
{
Stack st1;
Stack st2(20);
st1 = st2;
return 0;
}
运算符重载
运算符重载是具有特殊函数名的函数。函数名为operator+需要重载的运算符。函数原型为 返回值类型+operator+重载运算符(参数列表)。
注意参数列表有隐藏的this指针。
.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&&_day == d._day;
}
bool operator<(const Date& d)
{
if (this->_year < d._year)
return true;
else if (this->_year == d._year&&this->_month < d._month)
return true;
else if (this->_year == d._year&&this->_month == d._month&&this->_day < d._day)
return true;
return false;
}
const成员
const修饰类的成员函数
将const修饰的类的成员函数叫const成员函数,const修饰类成员函数实际上修饰的是隐含的this指针,表示该函数不能对成员变量进行修改;
对于隐含的this指针,直接在后面加const
class Date
{
public:
Date(int year = 0, int month = 1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{}
void Print()const //void Print(const Date* this)
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
const对象可以调用非const的成员函数吗
不可以
非const的对象可以调用const的成员函数吗?
可以
const成员函数可以调用非const的成员函数吗
不可以
class A
{
public:
void F2()
{
}
void F3()const
{
F2();//不可以-->权利可以缩小,但不可以放大。const-->非 const
}
};
非const的成员函数可以调用const的成员函数吗
可以
class A
{
public:
void F1()const
{};
void F2()
{
F1();//this.F1();
}
};
取地址操作符重载
取地址运算符重载一般不需要自己实现,使用编译器默认生成的就可以,只有特殊情况才需要重载,比如不想让获取到真实的地址。
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year;
int _month;
int _day;
};
友元
友元函数
当我们尝试去重载operator<<时,我们发现没办法将operator<<重载成成员函数,因为隐含的this指针和左操作数cout会抢占第一个参数的位置,但是只有当cout是第一个形参是才可以使用,所以我们要将operator<<重载成全局函数。但是这 样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。
#include<iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date(int year=0,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;
}
int main()
{
Date d1(2021, 5, 6);
cout << d1;
return 0;
}
友元函数可以直接访问类的私有成员,它是定义在类外面的普通函数,需要在类中进行声明,声明时加friend关键字。
- 友元函数可以访问保护和私有的成员,但不是类的成员函数。
- 友元函数不能用const修饰。
- 友元函数可以在定义类的任意地方声明,不受访问限定符限制。
- 一个函数可以是多个类的友元函数。
- 友元函数的调用和普通函数的调用原理相同。
友元类
class Time
{
friend class Date;
public:
Time(int hour = 0, int minute = 0, int second = 0)
:_hour(hour)
,_minute(minute)
,_second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year=0,int month=1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{}
void Print()
{
cout << _year << "/" << _month << "/" << _day << "-" << _t._hour << "-" << _t._minute << "-" << _t._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
- 友元类的所以成员函数都是另一个类的友元函数。
- 友元关系具有单向性,不能传递。
。
友元类
class Time
{
friend class Date;
public:
Time(int hour = 0, int minute = 0, int second = 0)
:_hour(hour)
,_minute(minute)
,_second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year=0,int month=1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{}
void Print()
{
cout << _year << "/" << _month << "/" << _day << "-" << _t._hour << "-" << _t._minute << "-" << _t._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
- 友元类的所以成员函数都是另一个类的友元函数。
- 友元关系具有单向性,不能传递。