1、面向过程和面向对象初步认识
C语言是面向过程的,关注的是过程
C++是基于面向对象的(不纯面向对象,可以面向对象和面向过程混编,兼容C语言),关注的是对象。
假设要设计简单学校外卖系统
面向过程:
关注:点餐、接单、送餐过程,关注流程函数的实现
面向对象:
关注:用户、商家、骑手。关注对象之间的关系。
类
//兼容c struct 语法
//c++同时将struct升级成了类:
//成员变量
//成员函数
struct Stack
{
void Init()
{
//...
}
...
int* a;
int top;
int capacity;
类的定义
1、声明和定义全部放在类体中,编译器可能会将其当成内联函数处理(对于小函数)
2、声明放在.h文件中,类的定义放在.cpp文件中
类域
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
//这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()//在类外定义成员,用::作用域解析符指明成员属于哪个类域
{
cout << _name << " "_gender << " " _age << endl;
}
访问限定符
pubilc(公有):可以在类外被访问
protected(保护)、private(私有):只能在类里面被访问
封装、继承、多态
//class 定义的类默认是private
//struct 定义的类默认是public
class Stack
{
public://到下一条限定符前都是pubilc类型
void Init()
{
a = 0;
top = capacity = 0;
}
void Push(int x)
{
//...
}
void Pop()
{
//...
}
int Top()
{
return a[top - 1];
}
private:
int* a;
int top;
int capacity;
};
int main()
{
Stack st;
st.Init();
st.Push(1);
st.Push(2);
st.Push(3);
//cout << st.a[st.top] << endl;
//cout << st.[st.top-1] << endl
cout << st.Top() << endl;
return 0;
}
生命周期和存储位置有关系
栈、堆(malloc)、静态区,常量区
声明和定义
int main()
{
//extern int age; //声明,未开辟内存空间
//int age: //定义,开辟了内存空间
//链接属性不一样
//int age; //所有文件可见
//static int size; //当前文件可见
return 0;
}
对象存储方式
设计一
A aa1;
A aa2;
aa1._a = 1;
aa2._a = 2;
aa1.PrintA();
aa2.PrintA();
实例化的每个A对象成员变量都是独立空间,是不同的变量,但是每个A对象,调用PrintA成员函数都是同一个,所以造成了浪费。
设计二和三
A aa1;
A aa2;
aa1._a = 1;
aa2._a = 2;
aa1.PrintA(); //编译链接时就根据函数名去公共代码区
aa2.PrintA(); //找到函数的地址,call函数地址。
//
A* ptr = nullptr;
ptr->func(); //所以可以正常运行。但是采用设计二就会崩溃。
类对象都存储设计一般都是第三种。
类内存对齐
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
void func()
{
cout << "void A ::func()" << endl;
}
private:
char _a; //声明,因为类并没有分配实际的内存空间。只有当用类创建对象,也就是类的实例化才会分配内存空间。
int _i;
};
int main()
{
cout << sizeof(A) << endl;
}
类的内存对齐规则与结构体内存对齐规则一致:
构造函数
this
我们写的代码和编译器处理的代码
1
这样编译器不报错。
2
this 指针存在哪里?
this指针存储在栈,因为他是一个形参。但是编译器优化后,比如vs下面传递this指针,是通国ecx寄存器传递的,这样this大量重复访问变量提高效率。
lea:把[aa]的地址存放到ecx中。
类的6个默认成员函数
如果一个类中什么成员都没有,简称空类,但是编译器会自动生成一下6个默认成员函数(用户没有显示实现,编译器生成的函数)
调用Init初始化,可能会忘记,导致崩溃出现随机值
能不能保住对象一定被初始化? --构造函数
写 一个更好的构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
typedef int DataType;
class Stack
{
public:
Stack(int capacity)
{
cout << "Stack(int capacity = 4)" << endl;
_array = (DataType*)malloc(sizeof(DataType)*capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
private:
DataType* _array;
int _capacity;
int _size;
};
// C++类型分类:
// 内置类型/基本类型:int/double/char/指针等等
// 自定义类型:struct/class
class MyQueue
{
private:
int _size;
Stack _st1;
Stack _st2;
};
默认构造函数:->特点:不传参数就可以调用的
1、我们不写,编译器自动生成那个
2、我们自己写的,全缺省构造函数
3、我们自己写的,无参构造函数
这里把自己写的Stack的构造函数注释掉了。
总结:
1、一般的类斗不会让编译器默认生成构造函数,都会自己显示写一个全缺省构造函数,非常好用,比如Date,可以自己显示初始化年月日。
2、特殊情况下才默认生成构造函数。
析构函数
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中自愿的清理工作。
特性
1、析构函数名是在类名前面加上字符~(按位取反)
2、无参数无返回值类型
3、一个类只能有一个析构函数。若未显示定义,系统自动生成默认的析构函数,注意:析构函数不能重载
4、对象生命周期结束时,C++编译系统自动调用析构函数
默认生成析构函数特点与构造函数类似:a、内置类型不处理 b、自定义成员类型会去调用它的析构。
对比C
拷贝构造函数
只有单个形参,该形参是对本类类对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
特征
1、拷贝构造函数是构造函数的一个重载形式
2、拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷地柜调用。
3、若未显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝(类似memcpy),这种拷贝叫浅拷贝,或者值拷贝。
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
浅拷贝的问题:
1、一个对象修改会影响另一个对象
2、会析构两次,程序崩溃
如何解决?自己实现深拷贝
运算符重载
c++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也有其返回值类型。函数名字以及参数列表,其返回值与参数类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号
函数原型:返回值类型operator操作符(参数列表)
注意:
1、不能通过链接其他符号来创建新的操作符,比如operator@
2、重载操作符必须有一个类类型参数
3、用于内置类型的运算符,其含义不能改变,例如:内置的整形+,不能改变其含义
4、作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5、.* 、:: 、 sizeof 、 ?: 、 . 。注意这5个运算符不能重载
class Date
{
public:
int GetMonthDay(int year, int month)
{
static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2
&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
return 29;
}
else
{
return days[month];
}
}
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//d1 + 100
Date operator+(int day)
{
Date ret(*this);
ret._day += day;
while (ret._day > GetMonthDay(ret._year, ret._month))
{
ret._day -= GetMonthDay(ret._year, ret._month);
ret._month++;
if (ret._month == 13)
{
ret._month = 1;
ret._year++;
}
}
return ret;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 7, 23);
Date ret = d1 + 50;
return 0;
}
小结
1、大部分的类都需要自己写构造函数
2、只有像Myqueue这样的类不需要显示写构造函数
3、每个类最好都要提供默认构造函数
1、一些类需要显示写析构函数,比如:Stack、Queue…
2、一些类不需要显示写析构函数:a、比如Date这样类,没有资源需要清理 b、比如MyQueue也可以不写,默认生成的就可以
1、一些类需要显示写拷贝和赋值,比如Stack、Queue…
2、一些类不需要显示写拷贝和赋值:
a、比如Date这样的类,默认生成就会完成值拷贝、浅拷贝
b、比如MyQueue这样的类,默认生成就会调用他的自定义类型成员Stack的赋值和拷贝
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)->" << _a << endl;
}
~A()
{
cout << "~A()->" << _a << endl;
}
private:
int _a;
};
int main()
{
A aa1(1);
A aa2(2);
return 0;
}
aa3(3);
void f()
{
static int i = 0;
static A aa0(0);
A aa1(1);
A aa2(2);
static A aa4(4);
}
//构造顺序: 3 0 1 2 4 1 2
//析构顺序: ~2 ~1 ~2 ~1 ~4 ~0 ~3
int main()
{
f();
f();
return 0;
}
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0) ->" << _a << endl;
}
A(const A& aa)
{
_a = aa._a;
cout << "A(const A& aa) ->" << _a << endl;
}
~A()
{
cout << "~A()->" << _a << endl;
}
private:
int _a;
};
A func3()
{
static A aa(3);
return aa;
}
A& func4()
{
static A aa(4);
return aa;
}
int main()
{
func3();
cout << endl << endl;
func4();
return 0;
}
赋值运算符重载
1、赋值运算符重载格式
·参数类型:const T&,传递引用可以提高传参效率
·返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
·检测是否自己给自己赋值
·返回*this:要符合连续赋值的含义
用户没有显示实现时,编译器会生成一个默认赋值运算符重载,以值得方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
// 任何一个类 > == 或者< == "重载, 剩下比较运算符重载复用即可。
#pragma once
#include<iostream>
using namespace std;
//到底可以重载哪些运算符?->看哪些运算符对这个类型有意义
class Date
{
public:
// 获取某年某月的天数
// 会频繁调用,所以直接放在类里面定义作为inline
int GetMonthDay(int year, int month)
{
static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = days[month];
if (month == 2
&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
day += 1;
}
return day;
}
// 构造会频繁调用,所以直接放在类里面定义作为inline
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print();
bool operator==(const Date& d);
bool operator!=(const Date& d);
bool operator>(const Date& d);
bool operator>=(const Date& d);
bool operator<(const Date& d);
bool operator<=(const Date& d);
Date operator+(int day);
Date& operator+=(int day);
//++d1;
//d1++;
//直接按特性重载,无法区分
//特殊处理,使用重载区分,后置++重载增加一个int参数
//跟前置构成函数重载进行区分
Date& operator++(); //前置
Date operator++(int); //后置
private:
int _year;
int _month;
int _day;
};
#include "Date.h"
void Date::Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
bool Date::operator==(const Date& d )
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool Date::operator>(const Date& d)
{
if (_year > d._year
|| _year == d._year && _month > d._month
|| _year == d._year && _month == d._month && _day > d._day
)
{
return true;
}
else
{
return false;
}
}
bool Date::operator != (const Date & d)
{
return !(*this == d);
}
bool Date::operator >= (const Date& d)
{
return (*this == d) || (*this > d);
}
bool Date::operator < (const Date& d)
{
return !(*this >= d);
}
bool Date::operator <= (const Date& d)
{
return !(*this > d);
}
Date Date::operator+(int day)
{
Date ret = *this;
ret += day;
return ret;
}
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
Date& Date::operator++()
{
return *this += 1;
}
Date Date::operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
#include"Date.h"
void TestDate1()
{
Date d1(2022, 7, 24);
Date d2(2022, 7, 25);
Date d3(2022, 7, 25);
cout << (d1 < d2) << endl;
cout << (d1 < d3) << endl;
cout << (d1 == d3) << endl;
cout << (d1 > d3) << endl;
}
void TestDate2()
{
Date d1(2020, 7, 24);
(d1 + 4).Print();
(d1 + 40).Print(); //跨月
(d1 + 400).Print(); //跨年
(d1 + 4000).Print(); //夸闰年
Date ret1 = ++d1;
Date ret2 = d1++;
}
int main()
{
TestDate1();
TestDate2();
}
日期减日期
Date .h:
int operator-(const Date& d);
Date.cpp:
int Date::operator-(const Date& d)
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++min;
n++;
}
return n * flag;
}
流插入重载
class Date
{
//友元函数 -- 这个函数内部可以使用Date对象访问私有保护成员
friend ostream& operator<<(ostream& out, const Date& d);
...
//流重载插入 定义在全局,这样不会因为定义在类里面有this指针
//导致出现d<<out的情况
inline ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日";
return out;
}
流重载提取
//流提取重载
inline istream& operator>>(istream& in, Date d)
{
in >> d._year >> d._month >> d._day;
assert(d.CheckDate());
return in;
}
日期菜单
void TestDate4()
{
const char* WeeDayToStr[] = { "周一","周二" ,"周三" ,"周四" ,"周五","周六" ,"周天" };
Date d1, d2;
int day = 0;
int option = 0;
do
{
cout << "*********************************" << endl;
cout << " 1、日期加/减天数 2、日期减日期" << endl;
cout << " 3、日期->周几 -1、退出" << endl;
cout << "*********************************" << endl;
cout << "请选择:" << endl;
cin >> option;
if (option == 1)
{
cout << "请依次输入日期和天数(减天数就输入负数):";
cin >> d1 >> day;
cout << "日期加减天数后的日期:" << d1 + day << endl;
}
else if (option == 2)
{
cout << "请依次输入两个日期:" << endl;
cin >> d1 >> day;
cout << "相差的天数" << d1 - d2 << endl;
}
else if (option == 3)
{
cout << "请输入日期:" << endl;
cin >> d1;
Date start(1, 1, 1);
int n = d1 - start;
int WeeDay = 0;
WeeDay += n;
cout << WeeDayToStr[WeeDay % 7 + 1] << endl;
}
else
{
cout << "无此选项,请重新输入:" << endl;
}
} while (option != -1);
}
const
初始化列表
以一个冒号开始,接着是一个逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。
1、每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2、类中包含一下成员,必须放在初始化列表为之进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有构造函数时)
class Time
{
public:
Time(int hour)
{
_hour = hour;
}
private:
int _hour;
};
class Date
{
public:
Date(int year, int hour)
:_t(hour)
{
//函数体内初始化,编译器会报错
/*_year = year;
Time t(hour);
_t = t;*/
}
private:
int _year;
Time _t;
};
class Time
{
public:
Time(int hour = 0)
{
_hour = hour;
}
private:
int _hour;
};
class Date
{
public:
//要初始化_t对象,可以在函数体内赋值,但是还是
//会走初始化列表调用Time的默认构造
/*Date(int year, int hour)
{
函数体内初始化
_year = year;
Time t(hour);
_t = t;
}*/
Date(int year, int hour, int& x)
:_t(hour)
, _N(10)
, _ref(x)
{
//函数体内初始化
_year = year;
_ref++;
}
private:
int _year;
Time _t;
const int _N;
int& _ref;
};
class Time
{
public:
Time(int hour = 0)
{
_hour = hour;
}
private:
int _hour;
};
class Date
{
public:
//要初始化_t对象,可以在函数体内赋值,但是还是
//会走初始化列表调用Time的默认构造
/*Date(int year, int hour)
{
函数体内初始化
_year = year;
Time t(hour);
_t = t;
}*/
Date(int year, int hour, int& x)
:_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);
//const int N; // const必须在定义的地方初始化,
// 所以写在这里会报错
return 0;
}
结论:自定义类型成员,推荐使用初始化列表初始化
初始化列表可以认为是成员变量定义的地方
class A
{
public:
/*A(int N)
:_a((int*)malloc(sizeof(int)* N))
,_N(N)
{
if (_a == NULL)
{
perror("malloc fail");
}
memset(_a, 0, sizeof(int) * N);
}*/
//有些初始化工作必须在函数体内完成
A(int N)
:_N(N)
{
_a = (int*)malloc(sizeof(int) * N);
if (_a == NULL)
{
perror("malloc fail");
}
memset(_a, 0, sizeof(int) * N);
}
private:
int* _a;
int _N;
};
int main()
{
A aa(10);
return 0;
}
class A
{
public:
// 初始化按声明顺序初始化
A(int a)
// 成员变量定义
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
// 成员变量声明
int _a2;
int _a1;
};
//A.输出1 1
//B.程序崩溃
//C.编译不通过
//D.输出1 随机值
int main() {
// D
// 对象定义
A aa1(1);
aa1.Print();
A aa2(2);
}
答案是D
因为成员变量在类中的声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
explicit关键字 + 匿名对象
//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;
};
//string(const char* str)
//{
//}
//
//void func(const string& s)
//{}
class Solution
{
public:
int Sum_Solution(int n)
{
//...
return 0;
}
};
int main()
{
Date d1(2022); // 直接调用构造
Date d2 = 2022; //隐式类型转换:构造 + 拷贝构造 + 编译器优化 -> 直接调用构造
//如果是加了explicit Date(int year),则会让隐式类型转换失效,报错。
const Date& d3 = 2022;
int i = 10;
const double& d = i;
//匿名对象--生命周期只有这一行
Date(2000);
//匿名对象一些使用场景
/*Solution slt;
slt.Sum_Solution(10);*/
Solution().Sum_Solution(10);
return 0;
}
静态成员函数
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量,用sTATIC修饰的成员函数称之为静态成员函数。静态成员变量一定要在类外进行初始化
特性
1、静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
2、静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
3、类静态成员即可用类名::静态成员或者对.静态成员来访问
4、静态成员函数没有隐藏this指针,不能访问任何非静态成员
5、静态成员也是类的成员;守public、protected,private访问限定符的限制
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 << a1._scount << endl;
cout << a2._scount << endl;
cout << A::_scount << endl;*/
cout <<A::GetCount() << endl;
return 0;
}
问题:
1、静态成员函数可以调用非静态成员函数吗?
X,因为静态成员函数没有this
2、非静态成员函数可以调用类的静态成员函数吗?
可以,因为静态成员函数属于整个类
设计一个只在栈上定义对象的类
class StackOnly
{
public:
//没有this指针
static StackOnly CreateObj()
{
StackOnly so;
return so;
}
private:
StackOnly(int x = 0, int y = 0)
:_x(x)
, _y(0)
{}
private:
int _x = 0;
int _y = 0;
};
int main()
{
StackOnly so3 = StackOnly::CreateObj();
}
友元函数
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为友元函数和友元类。
说明:
友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不收类访问限定符限制
一个函数可以是多个类的友元函数(void func(const A& a, const B& b, const C& c); func同时成为A B C的友元)
友元函数的调用与普通函数的调用原理相同
内部类(了解)
如果一个类定义在另一个类的内部,这个内部类就叫做内部类,内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
//内部类
class A {
private:
int _h;
static int k;
public:
//B定义在A的里面
//1、受A的类域限制,访问限定符
//2、B天生是A的友元
class B
{
public:
void foo(const A& a)
{
cout << k << endl; //OK
cout << a._h << endl; //OK-友元
}
private:
int _b;
};
};
题
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()); // 本来构造+拷贝构造--编译器的优化--直接构造
// // 结论:连续一个表达式步骤中,连续构造一般都会优化 -- 合二为一
//
// W w2 = 1;
//
// return 0;
//}
OJ题
小结
STL
C语言–自由编程
C++ – 封装 -> class ->构造函数+析构函数 ->
深浅拷贝
运算符重载