在C++中,⽤户⾃定义数据类型的⽅式主要有两种:结构体类型和类类型。其中类类型是根据结构体类型演化过来的⼀种类型,可以说是结构体的增强版。类是⽤户⾃⼰指定的类型。如果程序中要⽤到类类型,必须⾃⼰根据需要进⾏声明,或者使⽤别⼈已设计好的类。C++标准本身并不提供现成的类的名称、结构和内容。在C++中声明⼀个类类型和声明⼀个结构体类型是相似的
类(class
)是C++有别于C语言的最重要的概念之一,正是因为有了类的概念,C++才拥有了所谓面向对象(OOP,即Object Original Programming)的编程模式,那么类究竟是什么呢?
单从语法层面讲,类其实就是增加了函数的结构体。而从逻辑层面讲,类是增加了数据行为的结构体。类跟结构体一样,先要自定义类类型,然后再使用类类型去定义变量(对象)。以下是示例代码:
#include
#include
using namespace std;
class stu
{
public:
string name;
int age;
void printf_info()
{
cout << name << age << endl;
}
};
int main(int argc, char const *argv[])
{
class stu s1;
s1.name = "dad";
s1.age = 20;
s1.printf_info();
return 0;
}
以上定义中可以看到,跟C语言的结构体定义是非常类似的,它们的唯二区别是:
public
,该关键字用来指明类成员可以在类外任意地方访问。void showInfo()
,这代表类不仅有数据,还有自身的行为。 在类中,如果对成员不加任何访问修饰,系统则默认该成员是私有的。在C++中,对成员的访问限制,系统提供了三个关键字:public
公共成员,protected
受保护成员,private
私有成员。访问修饰符的作用范围:是从修饰符位置开始到下一个修饰符结束或来类的结束符为止。在一个类中可以存在多个访问修饰符,也可以重复,一般建议先写public,再写protected,最后写私有的。
小贴士:
空类或只有普通函数成员的类一般占用1个字节内存,这个字节是用来区分该类创建的不同对象用的。
在一个类中,函数成员一般不占对象的内存,类中的函数成员在内存中只存一份,供该类创建的所有对象所共享。一个对象分配的内存主要是类中的数据成员,和普通函数没关系。如果类型中存在虚函数成员,一般会多占用对象的一个指针大小的内存。一个类中无论存在多少个虚成员函数,对于该类的对象来说,只会多占用一个指针大小的内存。
对于类中的静态数据成员,不占用对象的内存,因为静态数据成员属于类不属于对象
采用结构体初始化类中的数据成员是有局限性,对共公有成员是可以的,但对类中的非公有成员是失效的。在C++中初始化数据成员的方式:
- 1.采用类外逐成员进行初始化;
- 2.可以通过构造函数来初始化类中的数据成员(更通用)
类中普通成员和函数成员只能被该类的对象所调用
类中的函数成员有两种定义方式:
- 1.在类中定义;
- 2.在类内声明,在类外定义。函数成员类外定义的格式:
返回类型 类名::成员函数名(形参列表){}
类中的成员函数可以实现对当前类内的数据成员和函数成员进行访问,类内成员函数访问内部成员不受访问修饰符约束
受保护成员,可以在当前的类内和当前类的派生类内部调用。私有成员,外部不可以直接访问,但是可以简介访问:比如通过公共成员函数去获取私有成员进行返回
所谓类方法,就是类里面的函数接口,这是与结构体有重大区别的地方之一。类方法实际上表达的是一个对象的行为属性,比如小猫类 Kitty
应该要提供如下类方法,来表达一只小猫的正常的行为:
class Kitty
{
public:
void eat(){cout<< "进食" <
调用这些类方法的形式也非常简单,跟普通的函数调用没有什么区别,只不过类方法一般是通过类的对象来调用的(静态对象可以通过对象调用也可以通过类调用),例如:
int main(void)
{
Kitty myCat;
// 通过对象myCat调用各个类方法
myCat.eat(); // 进食
myCat.sleep(); // 睡觉
myCat.actCute();// 卖萌
}
class BMP
{
string fileName;
int width;
int height;
char *RGB;
public:
// 构造函数:
BMP(string fileName)
{
fileName = fileName; // 对象名字与参数名字相同了
}
};
上述代码显然有误,由于类成员 fileName
恰好与构造函数参数同名,因此其赋值语句发生了名字冲突,无法正常赋值,此时除了修改它们的名字外,通常也可以用 this
指针来指明变量的所属,比如:
// 构造函数
BMP(string fileName)
{
this->fileName = fileName;
}
重点:
this
指针是所有类方法(包括构造函数和析构函数)的隐藏参数this
指向调用了该类方法的类对象类与结构体有许多不同,其中之一是结构体中的所有数据默认都是公有的(public),而类中的成员(不管是数据还是方法)会被分成三种不同的访问权限,它们分别是:
一般而言,对于类成员的权限限定的基本原则是:
一个类中可以存在多个构造函数,意味着构造函数也可以支持函数重载。在函数重载中,要警惕带默认参数的函数对重载的影响。
以下代码展示了 BMP
的析构函数的实现:
class BMP
{
string fileName;
int width;
int height;
char *RGB;
public:
// 析构函数
~BMP()
{
// 释放各种资源等
}
};
⼀个对象从创建到释放为⽌的时间段称为对象的⽣存期,⼀个对象的⽣存期主要
包括:
1.给对象分配内存空间
2.由构造函数初始化
3.通过调⽤对象的成员函数实现对象的功能
4.由析构函数清理现场(⽐如new申请内容,delete释放)
5.销毁内部对象(释放对象内部创建的⼦对象或数据成员)
6.从内存中清除该对象(系统回收其分配的内存)
在C语言中,使用 malloc()
和 free()
来分配和释放堆内存,这两个函数在C++中也一样可用,但C++有更好的API来实现这一功能,它们是:
new
delete
以下是一个简单的示例代码:
int main(void)
{
// 申请一块int型数据大小的堆内存
int *p1 = new int;
// 申请一块可以存储100个int型数据大小的堆内存
int *p2 = new int[100];
// 申请一块35个字节大小的堆内存
char *p3 = new char[35];
// 释放所有的堆内存资源
delete p1;
delete [] p2;
delete [] p3;
}
以上代码,完全等价于:
int main(void)
{
// 申请一块int型数据大小的堆内存
int *p1 = (int *)malloc(sizeof(int));
// 申请一块可以存储100个int型数据大小的堆内存
int *p2 = (int *)malloc(sizeof(int) * 100);
// 申请一块35个字节大小的堆内存
char *p3 = (char *)malloc(35);
// 释放所有的堆内存资源
free(p1);
free(p2);
free(p3);
}
可以看到,在操作基本数据类型的时候,这两组堆内存操作函数是没有区别的,它们真正的区别在给类对象分配内存的时候,具体而言指的是: