文章目录
- 一、前言
- 二、类
- 1. 基本概念
- 2. 类的封装
- 3. 使用习惯
- 成员函数定义习惯
- 成员变量命名习惯
- 三、对象
- 1. 基本概念
- 2. 类对象的存储规则
- 四、this 指针
- 1. 基本概念
- 2. 注意事项
- 3. 经典习题
- 4. 常见面试题
一、前言
在 C 语言中,我们用结构体来描述一个事物的多种属性。
struct person
{int age;char name[10];
};
而 C++ 则引入了类,相比结构体内只能定义变量,类还可以定义函数。
下面声明一个类,类名为person
:
//class关键字
class person
{void init(...){...}int age;char name[10];
};
上面代码只是类的声明,没有占用实际的空间。
用类创建对象,称作类的实例化。
实例化的对象才会占用实际的物理空间。
创建对象:类名 + 对象名
例子:
person a;
上述代码创建了一个person
类的对象a
,对象才占有实际空间。
而 C 语言中的struct
关键字,在 C++ 中也升级成了类,不过 C++ 中的struct
关键字依然兼容 C语言的结构体写法。
//C++ 中的类
struct person
{void init(...){...}int age;char name[10];
};
//创建一个person类的对象b
person b;//C 语言中的结构体
struct person
{int age;char name[10];
};
//创建一个结构体变量c
struct person c;
//也可以这样写,此时person被看作一个类
person c;
二、类
1. 基本概念
类中的内容称为成员。
类中的变量称为属性或成员变量,类中的函数称为方法或成员函数。
2. 类的封装
C++ 中的类是用于实现封装的,封装是面向对象的三大特性之一,简单来说,封装就是将对象的属性和方法有机结合,隐藏内部实现细节,仅对外提供接口用于交互。就像电脑主机内部封装了各种硬件的实现细节,仅提供开机按钮、鼠标和键盘等让用户和计算机交互。
如何隐藏内部实现细节呢,C++ 提供了三个访问限定符,public
、private
、protected
。
public
修饰的成员可以在类外访问,private
和protected
修饰的成员不能在类外访问。
class
的默认访问权限为private
,struct
为public
。
3. 使用习惯
成员函数定义习惯
第一种是成员函数的声明和定义都在类中。
class person
{
public:void print(){...}
private:int age;char name[10];
};
需要注意的是,这种方式的成员函数定义在类中,编译器可能会将其当作内联函数处理。
另一种则是成员函数的声明在类中(类在头文件声明),定义体在类外(cpp文件)。一般建议使用这种方式。
//person.h
class person
{
public:void print();private:int age;char name[10];
};//person.cpp
void person::print()//注意要加 person::
{...
}
一个类就是一个新的作用域(事实上,C++ 中一对大括号就是一个域),类的所有成员都在类的域中,因此在类外定义类的成员函数时,函数名前要加类名::
。
成员变量命名习惯
一般会给变量名加个前缀或后缀,用于区分成员变量和成员函数形参。
例子:
class person
{
public:void init(int age, char name[]){...}
private:int _age; //或 mAgechar _name[10]; //或 mName
};
三、对象
1. 基本概念
简单来说,类是对一类事物的抽象描述,只是一个声明,不占物理空间。而类的对象就是类的一个实例化,是真实存在在内存空间的。
2. 类对象的存储规则
一个类对象在内存中只会存储它的成员变量,成员函数则是放在公共代码区,供类的所有对象使用。
那么如何计算sizeof
类对象的大小呢?
事实上,类对象的大小等价于类的大小,因为sizeof
是根据类型确定大小的。
而类对象中只存储它的成员变量,因此只需计算类的所有成员变量所占空间的大小即可。
与 C 语言中计算结构体的大小相同,计算类的大小也要考虑内存对齐的规则,具体可以参考这篇文章:传送门
另外,空类比较特殊(包括没有成员变量的类,因为成员函数是存在公共代码区的,不参与类大小的计算),空类的大小不是 0,编译器会给空类 1 个字节来唯一标识这个类的对象,因此空类的大小是 1 。
四、this 指针
1. 基本概念
在对象调用成员函数的时候,编译器会自动给函数传递对象的地址,当作被调函数的一个指针形参,这个指针就叫做this
指针。该指针用于在成员函数中访问对象的成员变量。(由上文可知,对象只会存储自己的成员变量,成员函数时放在公共代码区的)
事实上,编译器给每个非静态的成员函数都隐藏了一个this
指针参数。相比于 C 语言,调用成员函数时,我们不用自己传递对象的地址,编译器会帮我们完成。
2. 注意事项
this
指针本质是一个常量指针,不能修改指向。
由于this
指针是对象调用成员函数时,成员函数隐藏的一个指针形参,所以this
指针的作用域是成员函数内部。
对象调用成员函数时,对象的地址作为实参传递给this
形参。而作为一个形参,this
指针应当存在函数栈帧中,有的编译器为了提高效率也会把this
指针存在寄存器。
3. 经典习题
//1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void Print(){cout << "Print()" << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}
本题结果为正常运行。
在本题中,对象地址为空,所以对象调用成员函数的时候,传给成员函数this
形参的是空指针。由于成员函数是存在公共代码区的,而不是存在对象中,所以对象地址为空并不影响调用成员函数。本题的成员函数内部,并没有对this
形参的解引用,因此不存在对空指针解引用的问题,所以程序正常运行。
tips:程序里有空指针,编译是不会报错的,最多就是警告。所以我们写代码如果不小心对空指针解引用,编译是不会报错的,但是运行程序的时候就会崩溃。
//2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:void PrintA(){cout << _a << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->PrintA();return 0;
}
本题结果为运行崩溃。
本题与上题的唯一区别在于,本题的成员函数内部访问了对象的成员变量,因此存在对this
指针的解引用。而对象的地址为空,所以传给成员函数的this
形参是空指针,因此出现了对空指针解引用的行为,程序运行崩溃。
4. 常见面试题
综上,我们就能回答两个常见的面试题。
this
指针存在哪里:
this
指针作为成员函数隐藏的形参,存在函数栈帧中,也有可能存在寄存器。
this
指针可以为空吗:
可以,但是成员函数内不能访问对象的成员变量,即不能对this
指针解引用。