类和对象
面向过程和面向对象的初步认识
我们用军事为例,要完成一次作战,需要侦察、后勤保障、战略部署、战术部署...等等
-
面向过程: 更加关注过程,关注如何侦察(无人机侦察、火力侦察、侦察小组侦察),如何后倾保障、如何战略部署、如何战术部署 -
面向对象:更关注对象之间的关系,我们现在就可以找侦察参谋、后勤参谋、战术参谋、战略参谋,把他们都聚在指挥部里,而不是更关注过程
类
前言
在c语言中我们学习过struct,而在c++中,struct升级成了类。现在struct中不止能定义变量,还能定义函数。
类的定义
class 类名
{
// 类体:由成员函数和成员变量组成
};//分号不能漏
类定义的两种方式
-
声明和定义全部放在类体中, 成员函数在类中定义的话可能会被当做内联函数
class a
{
public:
int add(int x,int y)
{
return x+y
}
int b;
int c;
}
-
声明和定义分离
//声明放在.h文件中
class a
{
public:
int add(int x,int y);
int b;
int c;
}
//定义放在.cpp中
int a::add(int x,int y)
{
return x+y;
}
推荐使用第二种定义方法
类的访问限定符及封装
访问限定符
访问限定符的种类
-
public(公有):可在类外直接被访问 -
protected(保护):不可在类外直接被访问 -
private(私有):不可在类外直接被访问
访问限定符的作用域
从该访问限定符出现的位置到下一访问限定符或者到类结束的位置
c++默认的访问权限
-
class的默认权限是private -
struct的默认权限是public
class和struct的区别
-
默认的访问权限不同 -
在继承和模板参数列表位置有区别(现在还没学到,抱歉QAQ)
封装
-
将数据和操作数据的方法有机结合,隐藏对象的属性和实现的细节,仅对外公开接口来和对象进行交互 -
本质上是一种管理,让用户更方便使用类
类作用域
-
类的所有成员都在类的作用域中,在类外定义成员时需要使用::作用于操作符指明成员属于那个类的作用域
类的存储
-
成员变量存储在类中 -
成员函数存储在公共代码区 因为每个成员变量都对应着一个对象,而函数只需要形参便能够实现其功能,不是哪个对象独有的,为了节省空间便存放在公共代码区(毕竟a0对象的加法函数和a1对象的加法函数一模一样,只是参数不同)
类的实例化
-
用类类型创建对象的过程称作实例化。 -
类是对对象进行描述用的,类似于设计图,定义一个类时并不会分配实际的内存空间来存储它,当实例化的时候才会。 -
一个类可以实例化多个对象(根据一个设计图能够造出很多房子)
类对象模型
-
一个类的大小就是其“成员变量”(不是“成员函数”)之和, 遵守内存对齐原则 -
空类的大小是1字节,用来表示有这个类
this指针
-
用来防止要修改a1对象的值却设置成了a2对象的值 -
C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成
this指针的特性
-
this指针的类型: 类类型* const 指针名,说明不能修改this指针 -
只能在成员函数内部使用 -
this指针本质是成员函数的形参,而形参存储在栈区,所以this指针不存储于对象中 -
this指针是成员函数一个隐含的指针形参,一般由编译器通过ecx寄存器自动传递,不用用户传递(用户也没这资格)
类的六个默认成员函数
默认成员函数:用户没有显示实现,编译器会自动生成的成员函数。
-
构造函数 -
析构函数 -
拷贝构造函数 -
负值重载函数 -
普通对象取地址 -
const对象取地址
构造函数
-
函数名与类名相同 -
构造函数支持函数重载 -
构造函数不带返回值 -
类实例化时自动调用(防止忘记初始化) -
在声明周期内只调用一次 -
主要任务是初始化对象而不是开空间创建对象 -
以Date类举例
Date(int year, int month, int day)//日期类的构造函数
{
if (month > 12 || month < 1 || (day > GetMonthDay(year,month) || day < 1))
{
cout<<"非法日期"<<endl;
}
cout << "Date(int year, int month, int day)" << endl;
_year = year;
_month = month;
_day = day;
}
-
如果类中没有显示构造函数,编译器会自动生成一个无参的默认构造函数,该构造函数对内置类型成员不会处理(有的编译器会处理,但不是标准行为),对于自定义类型会自动调用该自定义类型的构造函数。 -
c++11标准中,可以通过在声明内置成员变量时给缺省值来达到使用 编译器生成的默认构造函数对内置类型进行初始化。
默认构造函数的分类
-
无参构造函数 -
全缺省构造函数 -
编译器默认生成的构造函数
初始化列表
-
构造函数体调用完之后,对象中有了个初始值,但实际上不能成为对对象中的成员变量初始化,构造函数体内的语句只能将其称为赋初值,因为初始化只能初始化一次,而构造函数体内可以多次赋值。 -
初始化列表:以冒号开头,跟着逗号分隔数据成员列表,每个成员变量后面跟一个放在 括号中的初始值或表达式 -
初始化列表还是成员函数定义的地方 -
是一种构造函数 -
以日期类举例
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{
//函数体内容(可以不填)
}
}
关于初始化列表注意事项
-
每个成员变量在初始化列表中只能出现一次(因为初始化只能初始化一次) -
类中包含引用成员变量、const成员变量、自定义类型成员(且该类没有默认构造函数) -
尽量使用初始化列表初始化,因为对于自定义类型成员变量,一定会先使用初始化列表初始化 -
成员变量的初始化顺序是由声明顺序决定的, 与在初始化列表中的出现顺序无关 -
声明中的缺省值就是给初始化列表用的 -
初始化列表可以和函数体结合起来用(比如malloc是否开辟成功的检查)
explicit关键字
-
作用:用于修饰构造函数,防止单个参数或 除了第一个参数外其他参数都有默认值的构造函数发生隐式转换 -
如果不用explicit修饰构造函数的话,Date d1 = 1为合法操作(1被隐式类型转换成Date的匿名对象,再调用d1的拷贝构造),加上则为非法操作了
class Date
{
public:
Date(int year, int month = 0, int day = 0)
: _year(year)
, _month(month)
, _day(day)
{
//函数体内容(可以不填)
}
}
析构函数
-
函数名为:~类名 -
无参数 -
无返回值 -
在对象生命周期结束的时候自动调用(防止我们忘记销毁对象) -
若类中无显式定义编译器会自动生成 -
以Date类举例
~Date()//析构函数
{
cout << "~Date()" << endl;
_year = 0;
_month = 0;
_day = 0;
}
-
同编译器生成的构造函数一样,只对自定义类型操作(调用该自定义类型的析构函数) -
当类中有申请内存这一行为时一定要写析构函数,此外就看心情了
拷贝构造函数
-
只有一个形参(且该形参是本类类型对象的引用,通常会使用const修饰) -
在使用已有的对象创建新对象的时候自动调用 -
是构造函数的一个重载形式 -
对自定义类型的拷贝一定会调用拷贝构造 -
参数一定要使用引用否则会引发无穷递归(原因就是上一点) -
若没有显示定义,编译器会默认生成拷贝构造。但该拷贝构造会对所有类型进行 浅拷贝,不只针对自定义类型, 浅拷贝十分容易造成野指针的问题(因为两个指针指向同一个空间) -
以日期类举例
Date(const Date& d)//拷贝构造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
拷贝构造的应用场景
-
使用已存在对象创建新对象 -
函数参数类型是类类型对象 -
函数的返回值类型是类类型对象
赋值运算符的重载
运算符类型的重载
-
为了增加代码的可读性 -
更为方便地操作自定义类型(比较,赋值,输入输出...等) -
语法:返回值类型 operator操作符(参数列表) -
“.*”、“::”、“sizeof”、“?:”、“.” 这五个操作符不能重载 -
不能通过连接其他符号创建新的操作符 -
用于内置类型的运算符其含义不会改变 -
在写运算负值重载函数的时候设计的参数要比实际少一个(因为this指针被隐藏了) -
以日期类举例
//
Date& Date:: operator+=(int day)
{
int maxDay = GetMonthDay(_year, _month);
_day += day;
while (_day > maxDay)
{
_month++;
_day -= maxDay;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
//
Date Date::operator+(int day)
{
Date tmp = *this;
tmp += day;
return tmp;
}
//
Date& Date::operator-= (int day)
{
while (day)
{
if (_day - day < 1)
{
day -= _day;
_month--;
if (_month < 1)
{
_month = 12;
_year--;
}
_day = GetMonthDay(_year, _month);
}
else
{
_day -= day;
break;
}
}
return *this;
}
//
Date Date::operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
//
Date& Date:: operator++()//前置++
{
_day++;
if (_day > GetMonthDay(_year, _month))
{
_day = 1;
_month++;
if (_month > 12)
{
_month = 1;
_year++;
}
}
return *this;
}
Date Date::operator++(int)//后置++
{
Date tmp = *this;
++(* this);
return tmp;
}
Date& Date::operator--()//前置--
{
_day--;
if (_day < 1)
{
_month--;
if (_month < 1)
{
_month = 12;
_year--;
}
_day = GetMonthDay(_year, _month);
}
return *this;
}
//
Date Date::operator--(int)
{
Date tmp = *this;
--(*this);
return tmp;
}
//
bool Date::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 Date::operator==(const Date& d)
{
return (_year == d._year && _month == d._month && _day == d._day);
}
//
bool Date::operator>=(const Date& d)
{
return ((*this) > d || (*this) == d);
}
//
bool Date::operator<(const Date& d)
{
return !((*this) > d || (*this) == d);
}
//
bool Date::operator<=(const Date& d)
{
return (*this) < d || (*this) == d;
}
//
bool Date::operator!=(const Date& d)
{
return!((*this) == d);
}
//
int Date::operator-(const Date& d)
{
Date max = *this;
Date min = *this;
if (max < d)
{
max = d;
}
if (min > d)
{
min = d;
}
for (int i = 0; 1; i++)
{
if ((min += 1) == max)
{
return i+1;
}
}
}
赋值运算符的重载
-
赋值运算符的重载格式: -
-
参数类型为const T&
-
-
-
返回值类型是 T&
-
-
-
检测是否给自己复制
-
-
-
返回*this(否则不能实现连续赋值)
-
-
赋值运算符只能重载成类的成员函数而不能重载成全局函数( 因为如果用户未显式定义,编译器会自动生成一个默认赋值运算符重载执行的是浅拷贝,如果定义成全局就会撞到) -
涉及到资源管理问题,赋值运算符重载 一定要写
赋值和拷贝构造的概念
-
赋值:两个已经存在的对象进行拷贝 -
拷贝构造: 通过一个 已经存在的对象去初始化一个 新的对象
const成员
-
将const修饰的成员函数称为const成员函数 -
const实际上修饰的是 隐含的this指针,使得类中的任何成员都不能被修改 -
用法:添加到函数的后面
void Print() const
-
重载问题
1. void Print() const
2. void Print()
这两个函数构成重载,当我们将this展开,答案显而易见
1. void Print(const Date* this)
2. void Print(Date* this)
取地址以及const取地址操作符的重载
-
这两个成员函数一般不需要定义,编译器会默认生成(使用的场景不多) -
想让别人获得指定内容的时候使用 -
对于 只读函数,最好都加上const
static成员
-
用static修饰的成员函数或成员变量被称为静态成员函数、静态成员变量 -
静态成员变量、函数一定要在类外初始化(因为它们不独属于某一个对象,是公用的),应用案例:统计程序中累计创建了多少个类对象,使用了多少个类对象 -
存放在静态区 -
使用类名::静态成员 或者 对象.静态成员来访问 -
静态成员函数没有隐藏的this指针 -
静态成员受访问限定符的限制 -
定义时不添加static关键字 -
定义的例子
class Solution
{
private:
static int _ret;
static int _i;
};
int Solution::_ret = 0;
int Solution::_i = 1;
友元
-
友元分为:友元函数和友元类 -
友元是一种突破封装的方法,使用时需慎重(谨慎交友)
友元函数
-
是定义在 类外的 普通函数,不属于任何类,但需要在类中声明一下(给好友发通行证) -
如果A是B类的友元函数,那么A能够访问B中的所有成员 -
不能使用const修饰友元函数 -
一个函数可以是多个类的友元函数 -
友元函数的调用原理和普通函数一样 -
友元函数可以在类的定义中的任何地方声明,不受访问限定符的限制
友元类
-
友元关系是单向的(你是我的朋友,我却不是你的朋友(悲)) -
友元关系不能传递(朋友的朋友不是你的朋友) -
友元关系不能继承
class Solution
{
friend A(int a);//友元的声明
private:
int _ret;
int _i;
};
Solution s;
void A(int a)//友元函数
{
printf(“%d”,s._i);
}
内部类
-
定义在一个类里面的类就是内部类 -
内部类天生是外部类的友元 -
内部类可以直接访问外部类的static成员,不需要外部类的 对象/类名 -
sizeof(外部类)= 外部类,和内部类没有关系
class Solution
{
public:
class Sum
{
public:
Sum()
{
_ret += _i;
_i++;
}
};
int Sum_Solution(int n)
{
Sum a[n];
return _ret;
}
private:
static int _ret;
static int _i;
};
匿名对象
-
和临时对象一样,匿名对象 具有常性 -
匿名对象的声明周期只有一行 -
const引用会延长匿名对象的生命周期
class Solution
{
Solution()
{
cout << "Solution" << endl;
}
private:
int _ret;
int _i;
};
Solution()//匿名对象,声明周期就只有定义的这一行