类和对象
类的引入
C语言中,结构体中只能定义变量,C++中不仅可以定义变量,还可以定义函数
类里面可以定义:1.成员变量 2.成员方法
class Person
{
void Print()
{
;
}
char _name[10];
int _age;
//....
};
类的定义
声明和定义放到类体中
class Person]
{
public:
void showInfo()
{
cout<<_name<<"-"<<_sex<<"-"<<_age<<endl;
}
public:
char*_name;
char*_sex;
int _age;
}
声明放.h文件中,类的定义放.cpp文件中
类的访问符及封装
访问限定符
- public(共有)
- protected(保护)
- private(私有)
class Student
{
public:
void ShowInfo()
{
cout<<_name<<endl;
cout<<_age<<endl;
cout<<_stuid<<endl;
}
int GetAge()
{
return _age;
}
private://一般情况下成员变量都是比较隐私的,都会定义成私有或者保护
char _name[20];
int _age;
int _stuid;
}
int main()
{
Student s1;
Student s2;
//s1.age=10;//私有,访问不了
return 0;
}
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- class的默认访问权限为private,struct为public(因为struct要兼容C)
问题:C++中struct和class的区别是什么?
解答:C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。
和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是的成员默认访问方式是
private。
C语言与C++中struct的区别
- C语言中struct用来定义结构体
- C++中兼容C语言定义结构体的用法,同时也可以定义类
类的实例化
实例化---->就是用自己定义的类型定义出对象
- 内置类型,基本类型 int /char/double
- 自定义类型class/struct
声明和定义的区别
- 声明是一种承诺,承诺要干嘛但还没做
- 定义就是把事落地了
class Stack\
{
public:
void Push(int x); //两种定义方式1.在类里面定义 2. 在类里面声明在类外面定义
void Pop();
bool Empty();//这里是声明
private:
int *_a;
int _size;
int _capacity;//这里也是声明
};
void Stack::Pop()
{
;
}
int main()
{
// 类实例化出对象,相当于定义出了类的成员变量
//类是审计图,类的实例化就是拿着设计图建造房子
Stack s1;
Stack s2;
Stack s3;
cout<<sizeof(s1)<<endl;
return 0;
}
类对象模型
对象中只存储成员变量,不存储成员函数?为什么?
- 一个类实例化出N个对象,每个对象的成员变量都可以存储不同的值,但调用的函数却是同一个
- 如果每个对象都放成员函数,那么会浪费空间。
如何计算一个类实例化成员变量的大小?
- 计算成员变量的大小之和,考虑内存对齐
- 没有成员变量的类的大小是1
为什么是1,不是0?
- 开一个字节不是为了存数据,而是为了占位,表示对象存在
this指针
#include <iostream>
using namespace std;
class Date
{
public:
void Init(int year,int month,int day)//void Init(Date*this,int year,int month,int day)
{
_year=year;
_day=day;
_month=month;
}
void Print()// void Print(Date*this)
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _day;
int _month;
}
int main()
{
Data d1;
d1.Init(2020,4,7);//d1.Init(&d1,2020,4,7);
d1.Print(); // d1.Print(&d1);
return 0;
}
隐含的this指针
- 谁调用这个成员函数,this就指向谁
问题:this指针存在哪里?(存在进程空间的哪个区域)
答:存在栈上的,因为他是一个形参(ps:VS下是存在ecx这个寄存器里)
- 成员函数存在于公共的代码段,所以p->Show()这里不会去p指的对象上去找
- 访问成员函数,才回去找
类的六个默认成员函数
#include <iostream>
using namespace std;
class Date
{
public:
void Init(int year,int month,int day)
{
_year=year;
_day=day;
_month=month;
}
void Print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _day;
int _month;
}
int main()
{
Data d1;
Data d2;
d1.Init(2020,4,7);
d1.Print();
d2.Init(2020,4,7);
d2.Print();
return 0;
}
构造函数
—>在对象构造的时候调用的函数,这个函数完成初始化操作
- 没有返回类型
- 函数名与类名相同
- 对象实例化的时候自动调用
- 构造函数可以重载
- 如果类中没有显式定义(自定义构造函数,则C++编译器会自动生成一个无参的默认构造函数 ,一旦用户显式定义,编译器将不再生成
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year,int month,int day)
{
_year=year;
_day=day;
_month=month;
}
Date()
{
_year=0;
_day=1;
_month=1;
}
void Print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _day;
int _month;
}
int main()
{
Data d1(2020,4,7);//构造函数
d1.Print();
Data d2;//构造函数+函数重载 Data d2()不能加括号
d2.Print();
return 0;
}
#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 _day;
int _month;
Time _t;
}
int main()
{
Data d1;//调用无参的编译器生成的构造函数
d1.Print();
return 0;
}
默认生成无参构造函数(语法坑:双标)
- 针对内置类型的成员变量没有做处理
- 针对自定义类型的成员变量,调用它的构造函数
#include <iostream>
using namespace std;
class Date
{
public:
/*Date(int year,int month,int day)
{
_year=year;
_day=day;
_month=month;
}
Date()
{
_year=0;
_day=1;
_month=1;
}*/
//更好的方式
Date(int year=0,int month=1,int day=1)
{
_year=year;
_day=day;
_month=month;
}
void Print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _day;
int _month;
}
int main()
{
Data d1;
d1.Print();
Data d2(2020,4,7);
d2.Print();
return 0;
}
调用默认构造函数
:对象生命周期到了之后完成清理工作(动态开辟的,比如malloc)
- 自己实现无参的构造函数
- 自己实现的全缺省的构造函数
- 自己不写,编译器自动生成的
特点:不用传参数
析构函数
- 析构函数是在类名前加上字符 ~
- 无返回值无参数
- 一个类有且只有一个析构函数,若未显式定义,系统会自动生成默认的析构函数
- 对象生命周期结束时,C++系统自动调用析构函数
class Date
{
public:
Date(int year=0,int month=1,int day=1)
{
_year=year;
_day=day;
_month=month;
cout<<Date()<<endl;
}
//构造函数,对象定义的时候自动调用
~Date()
{
cout<<~Date()<<endl;
}
//析构函数,对象生命周期到了以后自动调用,完成对象里面的资源清理工作,不是完成的d1,d2的销毁
void Print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _day;
int _month;
};
class Stack
{
public:
Stack(int n=10)
{
_a=(int*)malloc(sizeof(int)*n);
_size=0;
_capacity=n;
}
~Stack()
{
if(_a)
{
free(_a);
_a=nullptr;
_size=_capacity=0;
}
}
private:
int *_a;
int _size;
int _capacity;
}
int main()
{
Date d1;
Date d2;
Stack s1;
Stack s2;
return 0;
}
- 内置类型/基本类型 int/char 不会处理
- 自定义类型 调用它的构造函数初始化/析构函数
拷贝构造
- 拷贝构造函数是构造函数的一个重载形式
- 拷贝构造函数的参数只有一个且必须引用传参,使用传参方式会引发无穷递归调用
class Date
{
public:
Date(int year=0,int month=1,int day=1)
{
_year=year;
_month=month;
_day=day;
}
//Date d2(d1);--->Date(Date d);
//调用之前要先传参
//语义上形成无穷递归
//传参就是一个拷贝构造----->记住就行 没有理解
Date(const Date& d)//引用 传参的过程相当于 Date d=d1;
{
_year=d._year;
_month=d._month;
_day=d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024,1,27);
Date d2(2024,1,27);//自行修改
Date d2(d1);//拷贝构造 //对象初始化时自动调用构造函数(拷贝构造)
//也可以这样写
Date d3=d1;
return 0;
}
运算符重载
C++为了增加代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数
关键字 operator后面接需要重载的运算符符
返回值类型 operator操作符
- 不能通过链接其他符号来创造新的操作符,比如operator@
- 重载操作符必须有一个类类型或枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变,例如:内置的整形+,不能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少一成员函数的操作符有一个默认的形参this,限定为第一个形参
- .* , :: , sizeof , ?: , .注意以上五个操作不能重载
class Date
{
public:
Date(int year=0,int month=1,int day=1)
{
_year=year;
_month=month;
_day=day;
}
bool operator==(const Date& d)//隐含的this指针,d1传给this指针,d2传给d bool operator(Date*this ,const Date& d )
{
return _year == d._year
&&_month == d._month
&&_day == d._day;
}
private:
int _year;
int _month;
int _day;
};
bool IsDateEqual(const Date&d1,const Date& d2)
{
//....
}
//如果类为共有的,可以使用以下代码
/*bool operator==(const Date&d1,const Date& d2)
{
return d1._year == d2._year
&&d1._month == d2._month
&&d1._day == d2._day;
}*/
int main()
{
Date d1(2024,1,17);
Date d2(2024,1,18);
d1 == d2;///编译器如何调用->这里编译会转换成operator==(d1,d2);或者d1.operator==(d2)这里就是一个函数调用不推荐可读性差
return 0;
}
- 运算符有几个操作符,operator重载的函数就有几个参数
- 自定义类型是不能用运算符的,要用就得实现重载函数,自定义用的时候相当与调用这个重载函数
实现一个完善的日期类
#include<iostream>
using namespace std;
class Date
{
public:
bool operator>(const Date& d)
{
if (_year > d._year)
return true;
else if (_year == d._year && _month > d._month)
return true;
else if (_year == d._year && _month == d._month && _day > d._day)
return true;
return false;
}
bool operator==(const Date& d)
{
if (_year == d._year && _day == d._day && _month == d._month)
return true;
return false;
}
bool operator>=(const Date& d)
{
return *this > d || *this == d;
}
bool operator<(const Date& d)
{
return !(*this >= d);
}
int GetMonthDay(int year, int month)
{
static int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
//2月闰年返回29
if (month == 2 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
return 29;
return monthDays[month];
}
Date(int year = 0, int month = 1, int day = 1)
{
if (year >= 0 && month >=1 && month <=12 && day >=1&& day<=GetMonthDay(year,month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期"<< endl;
}
}
Date(const 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;
d1.Print();
Date d2(2024,1,27);
d2.Print();
Date d3(2023,2,29);
d3.Print();
Date d4(d2);
d4.Print();
//日期类方便练习运算符重载
cout << (d2 > d3) << endl;
cout << (d2 == d3) << endl;
cout << (d2 >= d3) << endl;
cout << (d2 < d3) << endl;
//是否要重载一个运算符,要看是否有意义
return 0;
}
高内聚 低耦合
现在 类内部成员间的关系 类和类之间的关系
以后 更多指的时模块间的关系
现实中一个部门,部门的凝聚力越高,内聚度越高越好,战斗力越强,部门与部门耦合度越低越好
赋值重载
- 运算符重载是为了让自定义类型可以像内置类型一样使用运算符
- 自定义类型传参数和返回值时,在可以的情况下,尽量使用引用返回,减少拷贝
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year=0,int month=1,int day=1)
{
_day=day;
_year=year;
_month=month;
}
Date (const Date& d)
{
_year=d._year;
_month=d._month;
_day=d._day;
}
void Print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
//d3=d1的表达式的返回值是d3才能支持连等
Date& operator=(const Date& d)
{
if(this!=&d)//针对自己给自己 的判断检查
{
_year=d._year;
_month=d._month;
_day=d._day;
}
return *this;//this就是地址*this就是d3返回的就是date类型
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023,1,1);
Date d2;
Date d3=d1;
d1.Print() ;
d2.Print() ;
d3.Print() ;
return 0;
}
默认成员函数
- 我们不实现时,编译器生成的构造函数和析构函数会针对成员变量:内置类型不处理,自定义类型会调用他的构造函数和析构函数初始
- 我们不实现时,编译器生成拷贝构造和operator=,会完成按字节的值拷贝(也叫浅拷贝(值拷贝):将对象按字节一个一个拷贝过去),也就是说有些类,我们不需要实现拷贝构造和operator=d的因为编译器自动生成的足够我们用了
- ==不会自动生成,因为不是默认成员函数
问题
-
我们自己写拷贝构造的意义是什么?
其他类比如栈如果只按浅拷贝,就不能完全实现其功能
-
浅拷贝问题
指针拷贝过去
st2析构释放一次空间,st1析构又释放一次空间,同一块空间不能释放两次,会引发崩溃
-
如何解决
去自己实现深拷贝的拷贝构造和operator=(顺序表,链表,等等都存在深浅拷贝的问题)
const成员
对象调用const成员
#include<iostream>
using namespace std;
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)
// 成员变量无法修改,const保护*this
{
cout<<_year<<" "<<_month<<" "<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
void f(const Date &d)
{
d.Print();
}
int main()
{
Date d(2024,3,20);
f(d);
return 0;
}
void Print()
{
;
}
void f(const &Date d)
{
d.Print();
}
- 为什么d调不动Print( )
- d.Print()->d.Print(&d) &需要传到Date*this
- 权限放大,需要在this指针前加const
- this指针隐含无法加const
- 这样写void Print() const但修饰完之后成员变量就无法更改
const
- const Date*p1 修饰 *p1指向的对象
- Date const*p2 修饰 *p2指向的对象
- Date* const p3 修饰 p3指针本身
- const在*之前就是修饰对象,之后就是修饰指针本身
- 1和2 没区别 ,一般写p1规范
成员变量调用成员函数
只要调用成员函数都涉及this指针
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year=0,int month=1,int day=1)
{
_year=year;
_month=month;
_day=day;
}
void f1()//void f1(Date* this)
{
f2();//this->f2(this)
//自己是可读可写的,传进去后只可以读,可以调用,权限缩小,反过来不可取
}
void f2() const
{
}
private:
int _year;
int _month;
int _day;
};
void f(const Date &d)
{
d.Print();
}
int main()
{
Date d(2024,3,20);
f(d);
d.Print();
return 0;
}
- 自己是可读可写的,传进去后只可以读,可以调用,权限缩小,反过来不可取
取地址及const取地址操作符重载
#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 *operator&()
{
cout<<"Date *operator&()"<<endl;
return this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
const Date d3;
cout<<&d1<<endl;!
cout<<&d2<<endl;
cout<<&d3<<endl;//调用编译器自用生成的
return 0;
}
- 实际当中没什么用,没什么意义
- 什么时候会给成员函数加上const,只要成员函数中不需要修改,成员变量最好都加上const,这样const变量可以调用,非const成员变量也可以调用
再谈构造函数
初始化列表
#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(int year=0,int month=1,int day=1)
:_year(year)
,_month(month)
,_day(day)
{
//初始化列表
}
private:
int _year;
int _month;
int _day;
};
class B
{
public:
B(int a,int ref)
:_aobj(1)
,_ref(ref)
,_n(1)
{}
private:
A _aobj;//没有默认构造函数
int &ref;//引用
const int _n;//const
}
int main()
{
return 0;
}
-
为什么有初始化列表?
必须在初始化列表初始化 1. 没有默认构造函数 2. 引用成员 3. const修饰
必须在定义的时候初始化
-
尽量使用初始化列表初始化
-
谁先声明,先初始化谁(成员变量声明的顺序就是初始化的顺序)
explicit
单参数
#include<iostream>
using namespace std;
class Date
{
public:
explicit Date(int year)//加explicit关键词可以防止隐式类型转换
:_year(year)
{
//初始化列表
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1);//构造
Date d2=2;//隐式类型的转换 构造出临时的tem(2),再用tem拷贝构造d2(tem),然后优化成直接构造
const Date& d4=2//引用的就是产生的临时变量
Date d3=d1;//拷贝构造
return 0;
}
多参数
- c++11支持
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year,int month,int day)//加explicit关键词可以防止隐式类型转换
:_year(year)
,_month(month)
,_day(day)
{
//初始化列表
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1,2,3);
Date d2={1,2,3};
return 0;
}
static成员
- 对象一定有构造函数或者拷贝构造产生的
#include<iostream>
using namespace std;
//设计一个类A,计算这个类总计产生多少对象
int n=0;//没有对n进行封装,n会被修改
class A
{
public:
A()
{
++n;
}
A(const A&a)
{
++n;
}
};
A f1(A a)
{
return a;
}
int main()
{
A a1;
A a2;
f1(a1);
f1(a2);
cout<<n<<endl;
return 0;
}
没有对n进行封装,将修改为以下代码
static
- 声明 不是属于某个对象,是属于所有对象,属于这个类
#include<iostream>
using namespace std;
//设计一个类A,计算这个类总计产生多少对象
class A
{
public:
A()
{
++n;
}
A(const A&a)
{
++n;
}
static int GetN()//成员函数也可以有static修饰
//没有this指针,函数中不能访问非静态成员
{
return n;
}
private:
static int n;//声明 不是属于某个对象,是属于所有对象,属于这个类
//n不在对象中, n在静态区
};
int A::n=0;//定义
A f1(A a)
{
return a;
}
int main()
{
A a1
A a2;
f1(a1);
f1(a2);
return 0;
}
- static成员变量不存在对象中,存在静态区,属于这个类的所有对象,也是属于这个类
- static成员函数,没有this指针,不使用对象就可以调用->类名::func( )
- static成员函数中,不能访问非静态的成员,没有this指针
- 非静态成员函数可以调用类的静态成员函数
#include<iostream>
using namespace std;
class Date
{
public:
Date()
{
}
void Print()
{
cout<<_year<<" "<<_month<<" "<<_day<<endl;
}
private:
//C++11
//声明时给缺省值 ,不是声明
int _year=0;
int _month=1;
int _day=1;
};
int main()
{
Date d;
d.Print() ;
return 0;
}
友元
友元函数
#include<iostream>
using namespace std;
class Date
{
friend void f(Date &d);
friend ostream operator<<(ostream& out,const Date &d);
public:
void Print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year=0;
int _month=1;
int _day=1;
};
void f(Date &d)
{
d._year=10;
cout<<d._year<<endl;
}
ostream& operator<<(ostream& out,const Date &d)
{
out<<d._year<<"/"<<d._month<<"/"<<d._day<<endl;
return ostream;
}
int main()
{
Date d;
d.Print();
cout<<d;
return 0;
}
-
cin cout 为什么可以自动识别类型
因为运算符重载
友元类
class Time
{
friend class Date;
};
class Date
{
//Date类型可以直接访问Time的成员变量和成员函数
};
内部类
把B类定义到A的里面
那么B天生就是A类的友元
匿名对象
- 只有一行会使用这个创建对象那个,别人不需要使用
#include<iostream>
using namespace std;
class Solution
{
public:
int Sum_Solution(int n)
{
return n;
}
};
int main()
{
Solution s1;//s1生命周期main()函数
s1.Sum_Solution(10);
Solution(); // 匿名对象,会调用构造函数
Solution().Sum_Solution(10);//生命周期在这一行
return 0;
}