举个例子:
淘宝这个软件,我要买一个东西,就应该有以下几步:
上架商品-》下单-》送快递-》收快递-》评价
C语言关注的就是这个过程
而C++关注的更多是对象:
平台-》快递-》用户
这就是面向对象,将上面繁琐的过程总结为三个对象
这样再来看解释是不是会更好理解:
C 语言是 面向过程 的, 关注 的是 过程 ,分析出求解问题的步骤,通过函数调用逐步解决问题。 C++ 是 基于面向对象 的, 关注 的是 对象 ,将一件事情拆分成不同的对象,靠对象之间的交互完成。类的引用
既然C++是靠对象交互实现的功能,那类,就是必须要掌握的知识,在C语言里面,结构体一般是这样的:
里面定义的都是变量,但是在C++里面就不是这样的,C++不仅可以定义变量还可以定义函数,就好像下面:
这个结构体不仅有对象,还有函数,那它的使用就是这样:
先定义一个对象,随后调用这个对象函数。
类的定义这个结构体也可以用class来定义,区别下面会提到。
类的定义跟结构体很像,它的格式是这样的:
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
类有两种定义,第一种是先在头文件里面声明,再在.c文件里面实现,另一种是全部写上,这种全部写在类里面的函数会被当成内联函数,要注意
第一种:
在头文件里面声明show函数
在.c里面定义函数,这里要注意,在函数名前面一定要加上::表面它属于这个类,否则它会找不到参数。
下面就是第二种全部写上:
那这里多出来一个public,这是什么意思?
类的访问限定符及封装 访问限定符这里要说一个概念就是访问限定符,当定义出来一个类之后,它会默认有三种权限:
public(公有)
protecte(保护)
private(隐私)
---
公有的意思就是在类的外面也可以使用,保护和隐私相反。
访问限定符它会持续到下一个访问限定符的出现或者是类结束。
class默认是protected,而struct则是public。
这些了解一下就可以了,不用想太深。
这里还有个问题:
C++ 中 struct 和 class 的区别是什么?
因为C++兼容C,所以struct在C++中同样可以创建类,class也可以,而struct的默认权限是public,而class则是protected。
---
封装面向对象的三大特性:封装、继承、多态
但是在这个阶段,先认识一下封装,封装的本质,是将一些对象的实现方式和参数隐藏,只对外面实现接口调用。
在创建类的时候那个class就包含这一点,因为class创建的类它里面成员的默认属性是私有,因为是私有所以在类外面是访问不到的,而struct则是公开。
类的作用域当写C++的时候,总会看到这么一句话:
假设是这样,我在一个头文件里面新建了一个类,这个类里面有一个函数,这个函数代码很长,可能有十几行,那我就可以这么写:
我在头文件里面声明,但是在.c里面实现,不过要在实现的函数名前面带上作用域,表示这个函数实现的是这个对象里面的
类的实例化、计算大小、存储方式在说之前,先看一下正式的解释:
1. 类只是 一个 模型 一样的东西,限定了类有哪些成员,定义出一个类 并没有分配实际的内存空间 来存储它 2. 一个类可以实例化出多个对象, 实例化出的对象 占用实际的物理空间,存储类成员变量 3. 做个比方。 类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图 ,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间
举个例子,我这里创建了一个日期类,里面有这样的几个参数:
那这里提一个问,这三个对象有没有占用空间?
没有,因为它只是声明有这样的三个参数,还没有真的给初始化出来,所以不占用内存空间。
但如果我们要计算一个类要占用的内存空间,我们该如何计算?
就好像在C语言里面的,算一个结构体的大小一样,别忘了内存对齐,这里就是
4*3 = 12个字节。
类里面的函数占不占空间?
不占,举这样的一个例子,我这里创建了两个对象,一个d1,一个d2:
它们肯定是两个不同的对象,里面的参数也不一样,但是它们调用的函数,是不是这个类里面的同一个函数?
是的,所以为了优化,类里面的函数都会放到单独的一块空间,这块空间是公用的,所以计算的时候不算这一块。
假设这个类里面什么都没有,占用多少个字节?
一个字节,虽然这个类里面什么都没有 ,但编译器在运行的过程中还是会象征的给它一个字节表示这是一个类。
this指针这个,其实不是很好讲,这里我用个例子来说:
首先,我定义了一个对象,这个对象很简单,一个构造函数一个打印函数,随后,我在主函数里面初始化了a并将其赋值为2,随后我调用打印函数,在屏幕上打印了一个2.
为什么这里编译器会打印a的2,而不是一些随机值?
这里就要说到this指针,当写出如上代码的时候,看上其PrintA里面什么参数也没有,实际上,它隐藏的传了一个指针过去,这个指针称为this,*this就是a的地址,所以当我们看到上面的代码的时候,实际上它是这样的:
这样它在找的时候就能知道该打印哪个,我们还可以通过别的方法看到this指针:
可以看到,我在类里面打印了this指针的地址,然后在外面打印了a的地址,发现它们两个的地址一样,可以说明确实是传了过去。
--------
默认成员函数如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6 个默认成员函数。
以我要写的一个对象为例:
class Data
{
};
这个里面看起来是空的,但实际上,它里面会包含以下几个成员函数:
负责初始化和清理的:构造函数、析构函数
负责拷贝赋值的:拷贝构造、赋值重载
负责取地址重载的:普通对象和const对象取地址,但这两个很少用,这里就不考虑
一个一个来,从构造函数开始:
构造函数假设我现在写一个简单的对象,日期类:
#includeusing namespace std; class Date { public: void InitDate(int year, int month, int day) { _year = year; _month = month; _day = day; } void Display() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; };
这是很简单的一个对象,里面有三个隐私变量的声明,而在变量之上有两个函数。
那根据上面的对象,我们定义对象应该是这样的:
不太好用,对吧?
我们知道整形可以 int a = 10;这样定义的时候就给赋值,那Date这样的类能不能也这样呢?
当然可以,这里就要用到构造函数,这里先看以下构造函数的定义:
构造函数 是一个 特殊的成员函数,名字与类名相同 , 创建类类型对象时由编译器自动调用 ,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次 。
再看一下构造函数的特性:
构造函数 是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象 。 其特征如下: 1. 函数名与类名相同。 2. 无返回值。 3. 对象实例化时编译器 自动调用 对应的构造函数。 4. 构造函数可以重载。
看起来有点麻烦,但实际用起来还是比较简单的,首先,它的函数名跟类名一样,不需要返回值,类里面可以进行初始化的赋值。
这里就示范一个:
可以看到,这个跟函数有点像的就是构造,它有三个参数,分别对应我们类里面的三个参数,我们在定义类的时候编译器会自己调用这个构造:
这里再运行看看:
对象就可以这么写了,初始化的赋值也没问题。
除此之外,还有两种:
不给参数,无参的
编译器自己生成的
顺着来看看,先看无参的:
它就可以这么写:
不给参数,直接赋值,如果需要直接赋值的话,那还是不要用这种:
再来看看编译器自己生成的,如果我们不写:
直接就随机值了,这里要说的就是,编译器不会对内置类型处理,比方说int,double这种,它默认构造之后还是一个随机值。
还是别用,这里我们主要了解是是第一个,给参数的,但是我们可以这么写:
调用的时候不给参数,它就会在这里的缺省值。
注意一点,构造函数优先写全缺省的,别写了一个缺省之后再写一个不带参的,会报错
如果遇到了自定义类型,编译器会调用自定义类型的改造函数,有点类似于套娃,这里看下面的代码:
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year;
int _month;
int _day;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
Date里面有一个自定义类型Time,而当我们定义d这个对象的时候,编译器去找默认构造,发现Date里面的前三个是内置类型,所以它不会做处理,而_t则是自定义类型,那它就会在Time里面去找默认构造,看上面的代码是可以看到Time的默认构造是这个:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
在默认构造里面,打印了一行话,然后我们运行:
它也确实调用了这个默认构造 。
------
析构函数这么说呢,这个其实理解起来跟构造函数是很像的,同样,我们先来看看它的盖帘和特性:
概念 与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。 而 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作 。
特征
其 特征 如下: 1. 析构函数名是在类名前加上字符 ~ 。 2. 无参数无返回值。 3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。 4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数。
这个理解起来可以理解为是默认构造的翻版,它在这个对象生命周期到了的时候会自动调用,这里要注意的是它跟默认构造一样,它的析构同样不会对内置类型处理,而对自定义类型调用对应的析构函数。
如果,在这个对象里面有指针,那编译器也是不会处理的,所以如果写了指针,这个析构函数也是需要自己写的。
同时,如果一个类里面要自己写析构函数,那拷贝构造和拷贝赋值也是需要自己写的,原因之后再说,这里了解一下就好了。
------
拷贝构造函数当我们用构造函数的时候,有没有可能,我们有的时候需要拿一个对象初始化另一个对象,那我们就需要用到拷贝构造函数。
构造函数 : 只有单个形参 ,该形参是对本 类类型对象的引用 ( 一般常用 const 修饰 ) ,在用 已存在的类类型对象 创建新对象时由编译器自动调用。
拷贝构造函数也是特殊的成员函数,其 特征 如下: 1. 拷贝构造函数 是构造函数的一个重载形式 。 2. 拷贝构造函数的 参数只有一个 且 必须使用引用传参 ,使用 传值方式会引发无穷递归调用 。
这个说来也很简单,看看下面的例子:
这看是一个很简单的拷贝构造,需要的参数是一个同类型的对象,同样不需要返回值,只需要很简答的把对方的值给自己就好了。
不过注意的是特征的第二条,至于为什么会无穷递归,是因为形参是实参的临时拷贝,拷贝就要用到拷贝构造,拷贝构造又传过去参数...
死循环,注意一下就好了。
拷贝同样有两种,第一种:
浅拷贝,一个字节一个字节的拷贝,对于一些简单的int或者是double,很简单,但是如果有指针类型,浅拷贝也会把这个地址拷过去,也就是说,这两个指针指向的是同一块空间,都学到这个地步了,这个后果我就不多说了。
深拷贝:为了解决指针的这种类似问题,就出现了深拷贝,但我们现在也用不到。
这里还有一点要注意:
如果没有写拷贝函数,那编译器自动生成的拷贝构造是浅拷贝。
-----------运算符的重载
我们知道,同一种类型的对象是可以赋值的,那类也不例外,但是我们总不可能直接写:
int a = 10;
Data.a = a;
首先,类的成员我们是私有的,其次这两类型都不一样,所以要写也是这么写:
Data a(xxx);
Data b;
b = a;
为了实现这样的操作(指直接用等于),我们就需要这个,运算符的重载:
我先写一个示范:
operator就是关键,其后面跟着的=号是你要实现的运算符,然后后面是需要的参数,单目操作符就不需要传参,双目就要一个参数...
其次,还要考虑的是有连续赋值的可能,所以返回值可以考虑用类型的引用。
其他的类似实现的操作符在下面的日期类会依次实现。
---------
const成员这个说起来比较麻烦,所以先看代码:
class Date
{
public :
void Display ()
{
cout<<"Display ()" <
d1调用d1.Display肯定是没有问题的,但是d2呢?
如果运行程序,就会出现这样的错误:
因为d2是被const修饰的对象,但如果调用Dsiplay的话,函数隐藏的接受的对象类型是不会附带const的,这就设计到之前C++的基础知识。
const的对象只有const对象才能接收,如果是普通类型就会设计到一个权限放大的错误。
为了解决这个问题,所以C++就会有这样的规定:
在函数的括号后面加一个const:
这样不管是普通对象类型还是被const修饰的都可以调用这个函数了。
---
取地址及const取地址操作符重载
这么说呢,这个操作也是类似的,这里简单写一下:
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
大概就是这样类似的操作...
-----------
日期类的实现
【C++】日期类_ClaudineKnous的博客-CSDN博客
看完了这个就可以去看看这个了,日期类的实现几乎概括了上面的所有知识。



