C++类和对象(一)基础内容讲解
C++类和对象(一)基础内容讲解
- 1 类的定义
- 1.1 类定义的语法格式
- 1.2 类域
- 1.2.1 访问限定符
- 1.2.2 类域的概念与使用
- 2 实例化
- 2.1 实例化的概念
- 2.2 对象大小
- 3 this指针
- 3.1 this指针概念及使用
- 3.2 真题实战,夯实基础
1 类的定义
1.1 类定义的语法格式
class Stack{ };
其中class
是定义类的关键字,Stack
是类的名字(类名就是类型),{}
内是类的主体,类定义结束时末尾的;
不可省略。类的主体中的内容称作类的成员:类中的变量称作类的属性或成员变量;类中的函数称为类的方法或者成员函数。比如:
class Date
{//属性、成员变量int _year;int _month;int _day;//方法、成员函数void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
};
知晓语法格式后,我们仍需注意以下三点:
- 当类中成员变量较多易引起命名冲突时,为了区分成员变量,一般习惯在成员变量的前后加上
_
或者m
开头,虽然C++中并未对此做出强制要求,但在项目实践中却是常见的。 - C++中
struct
也可以定义类,C++兼容了C语言中struct
的用法,同时将struct
升级为了类,显著变化为现在struct
中已经可以定义函数。但一般情况下我们最好还是使用class
来定义类。 - 定义在类里面的成员函数默认是
inline
修饰的内联函数。
1.2 类域
1.2.1 访问限定符
用类将对象的属性和方法绑定在一块,并通过访问权限控制,选择性地暴露接口给外部用户,让对象更加完善。这是C++中一种实现封装的方式。
本小节所提及的访问限定符就是通过实现对访问权限的控制以达到数据隐藏目的而使用的关键字。
访问限定符有以下三种:
- public:成员可在类内外被直接访问
- private:成员只能在类内部访问
- protected:成员可在类内部或者派生类中访问
class
内定义的成员未被访问限定符修饰时权限默认为private
,在struct
中权限默认为public
。
注意:1、访问权限作⽤域是从该访问限定符出现的位置开始一直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到}
即类结束。
2、一般情况下成员变量都会被限制为private/protected
,需要给别人使用的成员函数权限会被放到public
中。如下:
class Date
{
public://方法、成员函数void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
private://属性、成员变量//这里只是声明,没有开空间int _year;int _month;int _day;
};
1.2.2 类域的概念与使用
类域是类所定义的一个新的作用域,类的所有成员都在类的作用域中。当要在类外面定义成员时,需要使用::
作用域解析运算符指明成员属于哪个类域,其格式为:<返回类型> 类名::成员函数名(参数列表) {函数体}
。
class Date
{
public://方法、成员函数void Init(int year, int month, int day);
private://属性、成员变量int _year;int _month;int _day;
};//函数声明和定义分离,需要指定类域
void Date::Init(int year, int month, int day)
{_year = year;_month = month;_day = day;
}
由于类域影响的是编译的查找规则,上面代码中Init如果不指定类域Date
,那么编译器就会把Init
当成全局函数,在编译时,编译器找不到_year
等成员的声明/定义在哪里,就会报错。指定类域Date
后,编译器就知道Date
是成员函数,当前局部域中找不到_year
等成员时,就会去类域中查找,在类域中也找不到时才会去全局域中查找。
2 实例化
2.1 实例化的概念
通过前面对类的讲解我们可以知道,类其实是一个抽象的模板,其定义的是对象的结构(属性和方法),但本身不占用内存。对象的创建就是类实例化分配内存的结果,系统会在物理内存中为该对象分配空间,存储其成员变量的值。用类类型在物理内存中创建对象的过程,称为类实例化出对象。
类与对象是一对多的关系,一个类可以实例化出多个对象,我们可以将类视为住房设计图,图中规划有房间个数和各个房间的功能,但是由于没有实体建筑的存在,所以无法住人和实现房间功能。然而一幅住房设计图可以生成许多套住房来完成上面的规划,这些住房就是类实例化出的对象。
+-------------------+| Person |+-------------------+| - name: String || - age: int |+-------------------+| + setName() || + getName():String|| + setAge() || + getAge():int |+-------------------+▲│ 实例化 箭头指向类,表示这两个对象是由类实例化来的│
+---------------------+ +---------------------+
| person1 | | person2 |
+---------------------+ +---------------------+
| - name: "Alice" | | - name: "Bob" |
| - age: 30 | | - age: 25 |
+---------------------+ +---------------------+
class Date
{
public://方法、成员函数void Init(int year, int month, int day);void Dprint(){cout << _year << "|" << _month << "|" << _day << endl;}
private://属性、成员变量int _year;int _month;int _day;
};//函数声明和定义分离,需要指定类域
void Date::Init(int year, int month, int day)
{_year = year;_month = month;_day = day;
}int main()
{//类名就是类型,实例化出两个对象Date ret1;Date ret2;ret1.Init(2025, 1, 1);ret1.Dprint();ret2.Init(2025, 1, 2);ret2.Dprint();return 0;
}
2.2 对象大小
计算对象的大小,我们首先要知道都有哪些成员是存储在对象之中的。类实例化出的每个对象的成员变量都有独立的存储空间,所以对象的存储肯定是包含成员变量的。但是对于成员函数,对象中却无法存储,主要有以下两方面原因:
- 函数被编译后是一段指令,对象中无法存储,这些指令会被存储到代码段当中,如果一定要存储相关函数,那么就只能存储成员函数的指针。但是对象中没有存储函数指针的必要。比如上面
Date
实例化出的ret1
和ret2
两个对象,二者中_year
等变量都是各自独立的,而ret1
和ret2
的成员函数函数Init/Dprint
的指针却是一样的,如果存储在对象中的话,当实例化的对象有几千个时,成员函数指针就会被重复存储几千次,如此就会非常浪费空间。 - 从底层上讲,函数指针就是一个地址,调用函数会被编译成汇编指令
[call地址]
,编译器在编译链接时,就需要找到函数地址,而非在运行时查找。(存储函数指针且在运行时查找只有在动态多态时才会出现)
由上可知,对象中只存储成员变量。C++中规定,类实例化出的对象也符合内存对齐原则。所以这里计算对象大小和C语言中计算结构体大小是相同的。
注意:空类和对象中不包含成员变量的类所实例化出的对象的大小是1个字节,目的是为了占位标识对象存在。
3 this指针
3.1 this指针概念及使用
Date
类中有两个成员函数Init
和Dprint
,但成员函数是如何识别是访问对象ret1
还是访问对象ret2
?这个问题的解决就涉及到C++中所提供的隐含的this
指针。
当一个对象调用其成员函数时,this指针会隐式传递给该函数,作为该函数的第一个参数(void Init(Date* const this, int year, int month, int day)
),this指针指向的是调用该函数的对象。
this
指针的核心作用:
- 区分不同对象:即使多个对象共享同一份成员函数代码(因为成员函数是放在类的代码段中),
this
指针确保函数能操作正确的对象数据。 - 关联成员变量和函数:成员函数内部访问成员变量或调用其他成员函数时,编译器会通过
this
指针找到对应对象的存储位置。访问对象内成员变量,比如Init
函数中的赋值,本质上是通过this->_year = year;
实现的。
使用this指针的注意事项:
- 静态函数中没有
this
指针,所以this
指针无法在静态函数中使用。 - C++规定不能在实参和形参的位置显式的写
this
指针(编译时编译器会处理),但是可以在函数体内部显式使用this指针(this->_year = year
)
this
指针的特性:
- 类型为
ClassType* const
:是一个指向对象的常量指针,不能修改其指向(如this = nullptr
;会报错)。 - 不占用对象内存:
sizeof(MyClass)
的结果不包含this
指针的大小,因为它是编译器隐式传递的,是成员函数的隐式参数,与对象无关,并不是对象的成员。 - 仅在非静态成员函数中可用:静态成员函数和普通函数中无法使用
this
。 - 传递方式:通常通过寄存器(如ECX)传递,具体依赖编译器优化。
3.2 真题实战,夯实基础
例题一:下面程序编译运行的结果是:
A:编译报错 B:运行崩溃 C:正常运行
#include<iostream>
using namespace std;
class A
{
public:void Print(){cout << "A::Print()" << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}
首先,A* p = nullptr
这是一种未定义行为,类A
并没有实例化出具体的对象,这个类A
类型的变量p
只是一个空指针,其值无效,并不指向任何类A
的对象。
其次,可见主函数中使用空指针p
调用了类A
当中的print
函数,成员函数print
的调用会隐式传递this
指针,指向调用对象(即p
所指向的对象),由于p
是nullptr
,所以this
也是nullptr
。
最后,因为成员函数print
当中未访问任何成员变量或this
指针指向的数据,所以不会触发对无效内存的访问。
综上,程序会正常运行,选C。
例题二:
下面程序编译运行的结果是:
A:编译报错 B:运行崩溃 C:正常运行
#include<iostream>
using namespace std;
class A
{
public:void Print(){cout << "A::Print()" << endl;cout << _a << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}
对比例题一我们可知例题二在成员函数print
内增加了一行输出成员变量值的代码,由例题一可知类A
并没有实例化出任何对象,当不访问this
指针指向的数据时,运行不会出错。但是此时成员函数内访问的是this
指针指向的数据_a
,触发了无效访问,导致运行崩溃。故答案为B。
例题三:this
指针存放于内存的哪块区域:
A:栈或寄存器 B:堆 C:静态区 D: 常量区 E:对象内部
this
指针位于函数的形参部分,自然会被当做形参对待,所以其被存放于栈或寄存器当中。注意this
指针的存储位置是灵活的,具体实现取决于编译器。在某些情况下,this
指针会被压入到栈区,但大多数编译器(如VC、GCC)会将this
指针优化到寄存器(如ECX)中,以提高访问效率。
全文至此结束!!!
写作不易,不知各位老板能否给个一键三连或是一个免费的赞呢(▽)(▽),这将是对我最大的肯定与支持!!!谢谢!!!(▽)(▽)