引言
前面我们学了C++与C的关键区别(引用、重载、命名空间等),但这些还只是"语法糖",C++真正的核心是面向对象编程(OOP) ,而OOP的基石就是**类(class)和对象(object)。**本篇为类和对象入门篇,包括定义、访问限定符、实例化、this指针等。
如果想详细深入学习C++类和对象以及其他内容,可以关注我,后面还会更加深入的去为大家剖析C++类和对象的内容,并且全部免费。
从C的 struct 到C++的 class
首先,学过C语言的同学们对于 struct 结构体 肯定不陌生,它可以定义一个新的自定义类型,还能存放成员变量,但是呢 struct 又太过于简陋,只能存放数据,不能存放函数方法;并且没办法控制权限,程序员可以任意访问结构体中的数据。
因此,C++就将我们C语言中的结构体(Struct) 升级成了类(Class)。
比如我们想去手动实现一个栈,在C语言中,我们需要先用结构体 定义一个栈(只能存放数据),再去另外写一些方法实现对栈的各种操作;但在C++当中,我们可以用一个定义一个类 类型 Stack,存放数据以及各种函数,将整个栈有关的内容封装 到一个类中。这样的编程方式也符合我们面向对象编程思想。
cpp
// C语言
struct Student {
char name[20];
int age;
};
void PrintStudent(const struct Student* s) {
printf("%s, %d\n", s->name, s->age);
}
// 数据和操作是分离的
cpp
// C++风格
class Student {
// 默认private
char name[20];
int age;
public:
void Print() {
cout << name << ", " << age << endl;
}
};
// 将数据和方法打包起来
类的定义(strcut、class)
类的定义 方式有两种,一种是用class ,一种是struct。 这时候就有人会问了:struct 不是用来定义结构体的吗?
是的,在C++中 struct 兼容了C中的 struct 用法,在C的基础上还可以包含成员函数。但我们更常用的是 class,因为class 默认是private ,而struct 默认是 public。(这个下面会讲)
既然C++中的 class 是在C的 struct 基础上进行升级的,那么定义方式自然也和C中的 struct 类似。
cpp
//定义一个类
class classname // classname为类名
{
// 类的成员
public:
void func()
{
cout << "成员函数" << endl;
}
private:
char* _c = "成员变量";
};
- 首先,定义类要用class关键字,后面跟着的就是类名
- 我们发现,类的成员中除了有成员变量 ,还有成员函数 。成员变量也叫类的属性 ,成员函数也叫做类的方法
- 为了区分成员变量和成员函数的参数,我们通常会在成员变量命名时前面加上一个 _ 或 m_,表示 member
- 类中的成员函数默认时 inline 内联函数
- 也可以用 struct 去定义一个类,它与 class 的区别就是,一个默认为 private,一个默认为 public
那么说了半天,到底private 和public是什么?
访问限定符
我们通过名字也能大概猜出这个符号的作用了,那就是去限制外部访问成员的权限 。我们通过访问限定符,可以选择性地将接口提供给外部。
-
实现封装:隐藏内部实现细节,只暴露必要接口
-
保护数据:防止外部随意修改,可在成员函数中加入检查逻辑
三种访问限定符:
- public:修饰的成员可以在类外直接访问
- private/protect :修饰的成员不能在类外访问。private 和 protect 还有一些不同,这里我们先不提,在后面学习继承 时会细说,这里我们只需要知道他们都表示私有就可以了。
一般的成员变量 都会被限制为private,防止外部随意访问。
我们在C语言中,只能简陋地将数据放在struct中,再另外实现一堆方法,这样程序员可以随意访问数据,随意调用函数。而在C++当中有了访问限定符 ,我们的对象也更加安全、完善。
类域
类定义了一个新的作用域,类内部的成员只能在类内访问,类外部访问必须通过对象 或作用域限定符::
比如我们在类外定义成员函数:
cpp
class Person {
public:
void Show(); // 声明
private:
string _name;
};
// 类外定义需要加 Person::
void Person::Show() {
cout << _name << endl; // 仍能访问私有成员,因为处于类域中
}
注:类域不影响生命周期
实例化
-
类的定义实际上只是定义了一种数据类型,它本身是没有大小的,与内置类型 int、char 一样。
-
对象是类的一个具体实例,占用实际内存。对象与我们用内置类型创建出的变量一样。
-
我们用类这个类型,创建出一个具体的实例对象的过程,就叫实例化对象
对象的大小
- 类实例化出的对象在计算大小时也遵循内存对齐规则 (忘记的同学回看我们C语言结构体那一篇文章)
- 由于成员函数都是相同的,因此在存储时只会存储成员变量 ,不会存储成员函数
- 如果一个对象没有成员变量,只有成员函数,那么默认分配一个字节用于占位
到这里,就产生了一个问题:如果每个对象都不存储成员函数,都调用同一个成员函数,那么如何传递成员变量呢?成员函数是怎么知道被哪个对象调用的呢?
给大家举个例子
cpp
class A
{
public:
void func()
{
cout << _a << endl;
}
private:
int _a;
}
int main()
{
A a1;
A a2;
a1.func();
}
这里我们用 A 实例化两个对象 a1,a2 ,然后调用 func 成员函数,那么此时 func 函数是怎么知道自己是被a1调用还是被a2调用的呢?
这时候应该会有很多人注意到我们func函数前面的a1. ,没错,确实由于前面加上了 a1. 之后 func 才知道自己被谁调用了。那么 func 函数中的 _a成员是怎么传递进去的?
这里就引出了我们下面要说的 this 指针。
this 指针
在我们调用成员函数时,编译器 会在第一个参数的位置为我们加上一个形参,this指针 ,类型为当前类类型。
cpp
a1.func(A* const this);
这是上面的函数调用被编译器处理之后的样子,我们的this指针 其实就相当于 &a1 , 函数内部的 _a 实际上就变为了 this->_a,这样就确保了函数中的成员变量就是调用成员函数对象的成员变量。
同时我们注意到 this 指针是被 const 修饰 的,因此不能修改。
那么这个 this 指针我们能不能用呢?可以的。
- C++规定,不能在实参和形参显式地写 this 指针,但可以在函数内部显式地使用 this 指针。
意思就是,这个 this 指针不需要我们在参数的位置写上,编译器会自动帮我们加上,我们在函数内部可以直接使用这个 this 指针。
下面我们来看一道题
cpp
class A
{
public:
void Print()
{
cout << "A" << endl;
}
private:
int _a;
}
int main()
{
A* p = nullptr;
p->Print();
}
这个程序能否正常运行?思考一下
答案是可以正常运行。
前面我们说过,对象不会存储成员函数,成员函数单独存储,然后用传入的 this 指针区分是哪个对象调用的。因此这个函数调用实际是这样的
cpp
Print(A* const this);
这里的 this 指针就是 p,他们的值为 nullptr,所以程序能不能正常运行取决于this指针在函数内部 有没有被解引用 。成员函数的调用过程 实际上是不涉及对 p 的解引用的。
本篇内容至此也就结束了,但对于类和对象来说,这仅仅只是一小步。