💓 博客主页:倔强的石头的CSDN主页
📝Gitee主页:倔强的石头的gitee主页
⏩ 文章专栏:《C++指南》
期待您的关注
目录
引言
在C++编程中,类和对象是面向对象编程(OOP)的基石。类是一种自定义的数据类型,它允许我们将数据(成员变量)和操作数据的方法(成员函数)封装在一起。对象则是类的实例,它根据类的定义被创建,并可以执行类中定义的操作。
本文将深入探讨C++中类和对象的基本概念。
文章思路
- 引言:简要介绍C++类和对象的概念,以及它们在面向对象编程中的重要性。
- 类的定义:详细解释如何定义类,包括成员变量和成员函数的声明。
- 访问限定符:介绍public、protected、private访问限定符的作用和使用方法。
- class与struct的使用区别:阐述class和struct在C++中的异同点。
- 对象的定义和使用:展示如何根据类定义创建对象,以及如何通过对象访问成员。
- 对象的大小:讨论对象在内存中的大小,以及成员变量对对象大小的影响。
- this指针:解释this指针的概念、用途以及它在成员函数中的隐式使用。
- 结论:总结类和对象的基本概念,以及它们在C++编程中的重要性。
一、类的定义
1.类的定义
- 类的定义是C++面向对象编程的核心。
- 一个类定义了一个新的数据类型,它包含了数据成员(也称为成员变量)和成员函数(也称为方法)。
- 类的定义以关键字
class
开始,后跟类名和类体。- 类体被包含在一对花括号
{}
中,并包含成员变量和成员函数的声明。- 成员变量用于存储对象的状态,而成员函数则用于实现对象的行为。
class MyClass
{
public:
int myNumber; //成员变量
void myFunction(); //成员函数
};
在上面的例子中,MyClass
是一个类,它有一个成员变量myNumber
和一个成员函数myFunction
。
注意事项
- 为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前⾯或者后⾯加_或者m 开头,注意C++中这个并不是强制的,只是⼀些惯例,具体看公司的要求。
- C++中struct也可以定义类,C++兼容C中struct的⽤法,同时struct升级成了类,明显的变化是 struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类。关于class和struct的更多细节会在稍后介绍
- 定义在类⾯的成员函数默认为inline。
以下是一个日期类的简单示例:
(关于public等关键字,可参考下文:类的访问限定符)
class Date
{
public:
void Init(int year, int month, int day)//类中定义默认为内联函数
{
_year = year;
_month = month;
_day = day;
}
private:
// 为了区分成员变量,⼀般习惯上成员变量
// 会加⼀个特殊标识,如_ 或者 m开头
int _year; // year_ m_year
int _month;
int _day;
};
2.类的访问限定符
C++⼀种实现封装的方式,用类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限 选择性的将其接⼝提供给外部的用户使用。
C++提供了三种访问限定符:public、protected和private。它们用于控制类成员的访问权限。
public
:成员在任何地方都是可访问的。protected
:成员在类内部和派生类中是可访问的。private
:成员只能在类内部访问。
- public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访 问,protected和private目前来说是⼀样的,后续会在继承的文章中讲解题目的区别
- 访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有 访问限定符,作⽤域就到 } 即类结束。
- class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
- ⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public。
3.class与struct的使用区别
在C++中,class
和struct
都可以用来定义自定义类型,但它们在使用上有一些关键的区别,主要涉及到成员的默认访问权限和继承的默认行为。
- 默认访问权限:
class
中的成员默认是private
的,这意味着在类的外部不能直接访问这些成员,需要通过公有成员函数(方法)来访问。struct
中的成员默认是public
的,这意味着在结构体外部可以直接访问这些成员。
- 继承的默认行为:(同样的,继承的相关内容会放在后续文章展开讲解,这里先简单了解即可)
- 当使用
class
进行继承时,默认是private
继承。这意味着派生类只能访问基类的公有和保护成员,且这些成员在派生类中变成了私有成员。 - 当使用
struct
进行继承时,默认是public
继承。这意味着派生类可以访问基类的公有和保护成员,且这些成员在派生类中保持原有的访问级别(公有或保护)。
- 当使用
4.类域
类域的定义
- 基本概念:类域是由类定义引入的一个作用域,它包含了类中声明的所有成员变量和成员函数。在类域内定义的标识符(如成员变量名、成员函数名)仅在类的范围内有效。
- 类的所有成员都在类的作⽤域中,在类体外定义成员时,需要使用::作用域操作符指明成员属于哪个类域
- 类域影响的是编译的查找规则。下⾯程序中Init如果不指定类域Stack,那么编译器就把Init当成全 局函数,那么编译时,找不到array等成员的声明/定义在哪⾥,就会报错。指定类域Stack,就是知 道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找
#include<iostream>
using namespace std;
class Stack
{
public:
// 成员函数
void Init(int n = 4);
private:
// 成员变量
int* array;
size_t capacity;
size_t top;
};
// 声明和定义分离,需要指定类域
void Stack::Init(int n)
{
array = (int*)malloc(sizeof(int) * n);
if (nullptr == array)
{
perror("malloc申请空间失败");
return;
}
capacity = n;
top = 0;
}
int main()
{
Stack st;
st.Init();
return 0;
}
类域与其他作用域的关系
- 全局作用域:在全局作用域中定义的变量、函数和类具有全局作用域,它们可以被程序中的任何其他作用域所访问。然而,全局作用域中的标识符不会与类域中的标识符冲突,因为它们是在两个不同的作用域中。
- 局部作用域:在函数内部定义的变量具有局部作用域,它们只能在定义它们的函数内部访问。局部作用域与类域也是相互独立的。
- 命名空间域:命名空间域是C++中用于解决全局命名冲突的一种机制。它相当于一个更加灵活的文件域(全局域),可以将全局作用域中的标识符组织成不同的命名空间。然而,命名空间域与类域是不同的概念,它们之间没有直接的包含关系。
二、对象的定义和使用
1.对象的定义
在C++中,定义对象的第一步是定义一个类。类定义了一组具有相同属性和方法的对象的蓝图。下面是一个简单的类的例子:(为了方便理解,将成员变量声明为了公有)
class Car {
public:
string brand; // 数据成员
string model;
int year;
void displayInfo() { // 成员函数
cout << "Brand: " << brand << ", Model: " << model << ", Year: " << year << endl;
}
};
2.对象的使用
一旦定义了类,就可以创建该类的对象了。创建对象的过程称为实例化。
- 类是对象进⾏⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只 是声明,没有分配空间,用类实例化出对象时,才会分配空间。
- ⼀个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。打个⽐比方:类实例化出对象就像现实中使⽤建筑设计图建造出房子,类就像是设计图,设计图规划了有多 少个房间,房间大小功能等,但是并没有实体的建筑存在,也不能住人,用设计图修建出房子,房房子才能住⼈。同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据
以下是如何创建Car
类的对象并使用它的示例:
int main() {
Car myCar; // 创建对象
myCar.brand = "Toyota"; // 访问数据成员并赋值
myCar.model = "Corolla";
myCar.year = 2020;
myCar.displayInfo(); // 调用成员函数
return 0;
}
在这个例子中,我们创建了一个Car
类的对象myCar
,然后通过点操作符(.
)访问它的数据成员并赋值,最后调用它的成员函数displayInfo
来显示汽车的信息。
3.对象的大小
对象在内存中的大小是由其成员变量决定的。成员函数不占用对象的空间,因为所有对象共享同一个成员函数实例。
在C++中,对象的大小是指该对象所占用的内存字节数。对象的大小可以通过
sizeof
运算符来计算。sizeof
运算符可以用于任何数据类型,包括类类型。
如何计算对象的大小
基本数据类型:对于基本数据类型(如
int
、float
、char
等),它们的大小是固定的,并且可以通过sizeof
运算符直接得到。类类型:对于类类型,
sizeof
运算符将返回该类的一个对象所占用的字节数。这个大小包括了类的所有非静态数据成员的大小,以及由于对齐而可能添加的任何填充字节。继承:如果一个类从另一个类继承,那么它的大小将包括基类的大小以及它自己的非静态数据成员的大小。
虚函数:如果一个类包含虚函数,那么它的大小将包括一个或多个虚指针的大小,这些虚指针用于实现多态性。(继承和虚函数的内容在后续文章讲解,这里暂不考虑)
对齐:编译器可能会在类的成员之间或类的末尾添加填充字节,以确保类的对象在内存中的对齐。这会影响对象的大小。
内存对齐规则
注意:C++中对象的内存对齐规则与C语言中结构体的对齐规则是完全一致的
- 第⼀个成员在与结构体偏移量为0的地址处
- 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处
- 注意:对⻬数=编译器默认的⼀个对⻬数与该成员大小的较小值。
- 以Visual Studio 为例,编译器默认的对齐数为8
- 结构体总大小为:最大对齐数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体大小就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍。
示例:
#include<iostream>
using namespace std;
// 计算⼀下A/B/C实例化的对象是多⼤?
class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
class B
{
public:
void Print()
{
//...
}
};
class C
{};
int main()
{
A a;
B b;
C c;
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
cout << sizeof(c) << endl;
return 0;
}
注意:
上⾯的程序运⾏后,我们看到没有成员变量的B和C类对象的大小是1,为什么没有成员变量还要给1个 字节呢?因为如果⼀个字节都不给,怎么表示对象存在过呢!所以这里给1字节,纯粹是为了占位标识 对象存在。
4. this指针
this
指针是 C++ 中一个非常重要的概念,它指向调用成员函数的对象。每个非静态成员函数都有一个 this
指针,它允许成员函数访问调用对象的成员变量和其他成员函数。
下面将详细讲解 this
指针的基本概念、使用规则和注意事项。
基本概念
- 定义:
this
是一个指针,它指向调用成员函数的对象,类型为 类的类型* const this。- 存在性:在类的非静态成员函数中,
this
指针是自动定义的,不需要用户显式声明也不可以。同时说明this
指针并不是对象内存空间的一部分,它不会影响对象的大小(即sizeof(对象)
的结果)- 用途:
this
指针主要用于访问调用对象的成员变量和成员函数。
使用规则
- C++规定不能在实参和形参的位置显式的写this指针(编译时编译器会处理),但是可以在函数体内显显式使用this指针。
- 访问成员变量:通过
this
指针,可以访问调用对象的成员变量。例如,this->member_variable
。- 访问成员函数:同样,通过
this
指针,可以调用调用对象的成员函数。例如,this->member_function()
。- 返回当前对象:成员函数可以返回
*this
,即返回调用对象本身。这常用于链式调用。- 区分成员变量和局部变量:当成员变量的名称与局部变量的名称相同时,可以使用
this
指针来区分它们。
注意事项
- 静态成员函数:静态成员函数没有
this
指针,因为它们不属于任何对象,而是属于整个类。- 在构造函数和析构函数中的使用:在构造函数中,
this
指针指向正在被构造的对象。在析构函数中,this
指针指向正在被析构的对象。- 返回
*this
的注意事项:当成员函数返回*this
时,需要注意返回值的类型应该是类的引用或指针,以避免不必要的对象复制。this
指针的类型:this
指针的类型是指向类类型的指针,具体类型取决于成员函数所属的类。this
指针的不可变性:在成员函数内部,不能改变this
指针的值,因为他被const修饰。它始终指向调用对象。
结语
本文介绍了C++中类和对象的基本概念,包括类的定义、访问限定符的作用、class和struct的使用区别、对象的定义和使用、对象的大小以及this指针的概念和用途。这些概念是C++面向对象编程的基础,掌握它们对于编写高效、可维护的C++代码至关重要。
本文完,更多相关知识请关注后续文章。