C++(10)之类语法分析(1)
Author: Once Day Date: 2024年8月17日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…
漫漫长路,有人对你微笑过嘛…
全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客
参考文章:
文章目录
1. 介绍
1.1 概述
在C++中,类(Class)是一种用户自定义的数据类型,它将数据成员(属性)和成员函数(方法)组合在一起,形成一个逻辑上相关的实体。通过使用类,我们可以创建出具有特定行为和状态的对象。
类的定义通常包含以下几个部分:
-
类名,类的名称。通常以大写字母开头,使用驼峰命名法。
-
访问修饰符,控制类的成员对外部的可见性,包括public(公有)、protected(保护)和private(私有)三种。
-
数据成员,类的属性,描述了类的状态。可以是基本数据类型、数组、指针、对象等。
-
成员函数,类的方法,定义了类的行为。包括构造函数、析构函数、普通成员函数等。
下面是一个简单的类定义示例:
class Student {
private:
string name;
int age;
public:
Student(string n, int a) {
name = n;
age = a;
}
void displayInfo() {
cout << "Name: " << name << endl;
cout << "Age: " << age << endl;
}
};
在这个例子中定义了一个名为Student
的类。它有两个私有数据成员name
和age
,分别表示学生的姓名和年龄。同时,它还有两个公有成员函数:构造函数Student()
用于初始化对象,displayInfo()
用于显示学生的信息。
创建类的对象很简单,只需要使用类名和构造函数即可:
Student stu("Alice", 20);
stu.displayInfo();
以上代码创建了一个名为stu
的Student
对象,并调用其displayInfo()
方法输出学生信息。
1.2 类基本概念
类(class):类是一个用户定义的数据类型,它包含数据成员(属性)和成员函数(方法)。
抽象(Abstraction):抽象是指将对象的共同特征抽取出来,形成一个类的过程。它强调了对象的本质属性和行为,而忽略了非本质的细节。通过抽象,我们可以创建出更加通用和灵活的类。
继承(Inheritance):继承是一种类与类之间的关系,它允许一个类(子类)继承另一个类(父类或基类)的属性和方法。通过继承,子类可以重用父类的代码,并可以添加或修改自己特有的功能。继承提高了代码的复用性和可维护性。
多态(Polymorphism):多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在C++中,多态分为静态多态(函数重载)和动态多态(虚函数)。多态提供了一种接口重用的方式,增强了程序的可扩展性。
成员(Member),成员是属于类的一部分,包括数据成员(属性)和成员函数(方法)。数据成员用于描述类的状态,成员函数用于定义类的行为。成员可以是静态的(属于类本身)或非静态的(属于类的对象)。
访问修饰符(Access Modifiers):访问修饰符用于控制类的成员对外部的可见性,包括以下三种:
- public(公有):公有成员可以被任何地方访问,包括类的内部和外部。
- protected(保护):保护成员可以被类自身及其子类访问,但不能被类的外部直接访问。
- private(私有):私有成员只能被类自身访问,不能被子类和类的外部访问。
1.3 类、结构体和联合的联系和区别
结构(struct)、类(class)和联合(union)是C++中三种重要的用户自定义数据类型,结构和类的主要区别在于默认的访问修饰符不同:结构默认是公有的,类默认是私有的。
结构和类都支持面向对象编程的特性,如封装、继承、多态等。但在实践中,结构更倾向于被用作简单的数据容器,而类更倾向于被用于定义复杂的数据类型和行为。
联合与结构和类的区别较大。联合强调了成员之间的互斥性,同一时间只能存储一个成员的值,常用于节省内存空间或者表示变体类型。
特性 | 结构(struct) | 类(class) | 联合(union) |
---|---|---|---|
默认访问修饰符 | public | private | 无(所有成员公有) |
访问修饰符支持 | 支持public、protected、private | 支持public、protected、private | 不支持 |
数据成员 | 支持 | 支持 | 支持 |
成员函数 | 支持 | 支持 | 不支持 |
构造函数/析构函数 | 支持 | 支持 | 不支持 |
继承 | 可以继承其他结构或类 | 可以继承其他类或结构,支持多重继承 | 不支持继承 |
多态 | 不直接支持,但可以通过继承实现 | 支持,可以定义虚函数 | 不支持 |
内存布局 | 每个成员独立占用内存空间 | 每个成员独立占用内存空间 | 所有成员共享同一块内存空间 |
常见用途 | 简单的数据容器 | 定义复杂的数据类型和行为 | 节省内存空间,表示变体类型 |
2. 类基础特性
2.1 类成员函数
类成员函数是类的重要组成部分,它们定义了类的行为和功能。
(1) 特殊成员函数是编译器自动生成或用户显式定义的一组函数,包括:
- 默认构造函数:无参数或所有参数都有默认值的构造函数。
- 析构函数:在对象销毁时自动调用,用于释放资源。
- 拷贝构造函数:使用另一个同类型对象初始化当前对象。
- 移动构造函数:使用另一个同类型的右值对象初始化当前对象。
- 拷贝赋值运算符:将另一个同类型对象赋值给当前对象。
- 移动赋值运算符:将另一个同类型的右值对象赋值给当前对象。
(2) 可变和静态数据成员:
- 可变数据成员,使用
mutable
关键字声明,即使在常量对象中也可以被修改。 - 静态数据成员,使用
static
关键字声明,属于类本身,而不是类的实例,静态数据成员在类外部初始化。
(3) 内置类型和用户定义类型,类的数据成员可以是内置类型(如int、double等)或其他用户定义的类型(如类、结构体等)。对于用户定义类型的数据成员,需要注意初始化的顺序和方式。
(4) 运算符,类可以重载运算符,以提供特定于类的操作。重载运算符的函数可以是类的成员函数或友元函数。
(5) 嵌套类声明,类可以在另一个类的内部声明,称为嵌套类。嵌套类可以访问外围类的私有成员。
(6) Unions,类可以包含联合(Union)类型的数据成员。联合允许在同一内存位置存储不同类型的对象。
(7) 枚举,类可以包含枚举(Enum)类型的数据成员。枚举用于定义一组命名的整型常量。
(8) 位域,类可以包含位域(Bit Field)类型的数据成员。位域允许在一个整型变量中指定位的数量,以节省内存空间。
(9) 友元,类可以将其他类、函数或模板声明为友元(Friend),以允许它们访问类的私有成员。
(10) 别名和typedef,类可以使用别名(如using
关键字)或typedef为类型定义新的名称,以提高代码的可读性。
在构造函数中,可以使用初始化列表来初始化数据成员,特别是const成员和引用成员。如果数据成员没有在构造函数中显式初始化,则会进行默认初始化。内置类型的默认初始化值取决于数据成员的存储位置。静态数据成员在类外部初始化,而不是在构造函数中初始化。
2.2 类成员内联函数
类成员函数和内联函数有着紧密的联系,合理地使用内联成员函数可以提高程序的执行效率。
-
内联函数是一种特殊的函数,它的函数体在编译时被直接插入到调用点,而不是像普通函数那样通过函数调用和返回的方式执行。这样可以避免函数调用的开销,提高程序的执行效率。内联函数通常适用于短小、频繁调用的函数。
-
类成员函数(Class Member Function),类成员函数是属于类的一部分,用于定义类的行为。成员函数可以访问类的私有成员,并且可以被类的对象调用。成员函数可以是普通函数,也可以是内联函数。
-
内联成员函数(Inline Member Function),内联成员函数是指在类内部定义且声明为内联的成员函数。内联成员函数的函数体会在编译时被直接插入到调用点,从而避免了函数调用的开销。内联成员函数通常用于实现短小、频繁调用的成员函数,如访问器(getter)和修改器(setter)等。
对于短小、频繁调用的成员函数,可以考虑将其定义为内联函数,以提高执行效率。但需要注意,过度使用内联函数可能会增加代码的体积和编译时间。将成员函数定义在类内部,将成员函数的定义放在类的内部,可以让编译器更容易进行内联优化。同时,这样也可以提高代码的可读性和封装性。
下面是一个简单的例子,展示了内联成员函数的使用:
class Rectangle {
private:
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
inline int getArea() const {
return width * height;
}
inline void setWidth(int w) {
width = w;
}
inline void setHeight(int h) {
height = h;
}
};
在这个例子中,getArea()
,setWidth()
和setHeight()
都是内联成员函数,它们的函数体会在编译时被直接插入到调用点,从而避免了函数调用的开销。
2.3 构造函数和析构函数
构造函数(Constructor),构造函数是一种特殊的成员函数,用于初始化对象的数据成员。它与类同名,没有返回类型,可以有参数。构造函数在对象创建时自动调用,用于确保对象在使用前处于一个有效的状态。
-
默认构造函数,如果没有显式定义构造函数,编译器会自动生成一个默认构造函数。默认构造函数没有参数,会对对象进行默认初始化。可以通过将隐式默认构造函数定义为已删除来阻止编译器生成它。
// Default constructor MyClass() = delete;
如果有任何类成员不是默认可构造,则编译器生成的默认构造函数会定义为已删除。 例如,类类型的所有成员及其类类型成员都必须具有可访问的默认构造函数和析构函数。 引用类型的所有数据成员和所有
const
成员都必须具有默认成员初始化表达式。 -
参数化构造函数,带有参数的构造函数,用于根据传入的参数初始化对象的数据成员。
构造函数的语法:
class MyClass {
public:
MyClass(); // 默认构造函数
MyClass(int value); // 参数化构造函数
};
析构函数(Destructor),析构函数是一种特殊的成员函数,用于在对象销毁时执行清理操作,如释放动态分配的内存、关闭文件、断开网络连接等。析构函数的名称与类名相同,前面加上~
符号,没有参数和返回值。析构函数在对象超出作用域、被显式删除或程序结束时自动调用。
- 默认析构函数,如果没有显式定义析构函数,编译器会自动生成一个默认析构函数,默认析构函数没有任何操作。
- 自定义析构函数,用户根据需要定义的析构函数,执行必要的清理操作。
下面是一个完整的例子,展示了构造函数和析构函数的使用:
class MyClass {
public:
MyClass() {
cout << "Default constructor called." << endl;
}
MyClass(int value) : data(value) {
cout << "Parameterized constructor called." << endl;
}
~MyClass() {
cout << "Destructor called." << endl;
}
private:
int data;
};
int main() {
MyClass obj1; // 调用默认构造函数
MyClass obj2(42); // 调用参数化构造函数
return 0; // 对象超出作用域,调用析构函数
}
输出结果:
Default constructor called.
Parameterized constructor called.
Destructor called.
Destructor called.
2.4 列表初始化(c11)
C++11引入了列表初始化(List Initialization)的概念,提供了一种更加通用、一致的方式来初始化对象,包括类的对象。列表初始化使用大括号{}
来指定初始值,可以用于各种类型的对象。
(1) 类成员的列表初始化,使用列表初始化可以直接在类的定义中为数据成员指定初始值,而不需要在构造函数中显式初始化。
class MyClass {
public:
MyClass() {}
private:
int value{42};
std::string name{"John"};
std::vector<int> numbers{1, 2, 3};
};
(2) 构造函数的列表初始化,在构造函数中,可以使用列表初始化来初始化数据成员,这提供了一种更加简洁、直观的方式。
class MyClass {
public:
MyClass() : value{42}, name{"John"}, numbers{1, 2, 3} {}
private:
int value;
std::string name;
std::vector<int> numbers;
};
(3) 对象的列表初始化,在创建类的对象时,可以使用列表初始化来直接指定对象的初始值。
MyClass obj1{}; // 使用默认构造函数
MyClass obj2{42, "John", {1, 2, 3}}; // 使用列表初始化
(4) 返回对象的列表初始化,在函数返回类的对象时,可以使用列表初始化来直接创建和返回一个临时对象。
MyClass createObject() {
return {42, "John", {1, 2, 3}}; // 使用列表初始化返回临时对象
}
(5) 聚合类的列表初始化,对于聚合类(Aggregate Class),即没有用户提供的构造函数、基类和虚函数的类,可以使用列表初始化来直接初始化其数据成员。
struct Point {
int x;
int y;
};
Point p1{1, 2}; // 使用列表初始化
2.5 const成员函数
const成员函数是一种特殊的类成员函数,用于保证在该函数中不会修改对象的状态。在成员函数的声明和定义中,在函数参数列表后面加上const
关键字,表示该函数是一个const成员函数。
class MyClass {
public:
int getValue() const; // const成员函数声明
};
int MyClass::getValue() const {
return value; // const成员函数定义
}
const成员函数不能修改对象的数据成员,也不能调用其他非const成员函数(因为它们可能会修改对象状态)。这提供了一种契约,保证了const成员函数的行为。
class MyClass {
public:
int getValue() const {
// value = 42; // 错误:不能修改数据成员
return value;
}
private:
int value;
};
可以同时提供一个成员函数的const和非const版本,以适应不同的需求,这称为const重载。
class MyClass {
public:
int& getValue() {
return value; // 非const版本,可以修改数据成员
}
const int& getValue() const {
return value; // const版本,不能修改数据成员
}
private:
int value;
};
常量对象(即声明为const的对象)只能调用const成员函数,不能调用非const成员函数,这确保了常量对象的不可变性。
const MyClass obj;
obj.getValue(); // 调用const版本的getValue
2.6 类作用域和静态成员
类作用域、作用域类枚举和静态常量成员是C++中与类相关的重要概念,它们定义了类成员的可见性、生命周期和访问方式。
(1) 类作用域(Class Scope),类作用域是指在类定义中声明的名称(如成员变量、成员函数、嵌套类型等)的可见范围。类作用域内的名称只能在类内部直接访问,在类外部需要通过类名或对象名限定。
class MyClass {
public:
void myFunction() {
// 在类作用域内直接访问成员变量
value = 42;
}
private:
int value;
};
int main() {
MyClass obj;
// obj.value = 42; // 错误:在类外部不能直接访问私有成员
obj.myFunction(); // 通过对象名访问公共成员函数
return 0;
}
(2) 作用域类枚举(Scoped Enumerations),作用域类枚举是C++11引入的一种强类型枚举,它具有类作用域,可以防止枚举值的命名冲突。作用域类枚举使用enum class
关键字定义,枚举值的名称仅在枚举类型的作用域内可见。
enum class Color { Red, Green, Blue };
int main() {
Color c = Color::Red; // 使用作用域类枚举
// int value = Red; // 错误:枚举值名称在枚举类型作用域外不可见
return 0;
}
(3) 静态常量成员(Static Const Members),静态常量成员是类的一种特殊成员,它具有静态存储期和常量特性。静态常量成员在类内部声明,在类外部定义和初始化。它们可以在类的任何成员函数中访问,也可以通过类名在类外部访问。
class MyClass {
public:
static const int VALUE = 42;
};
const int MyClass::VALUE; // 在类外部定义和初始化静态常量成员
int main() {
int value = MyClass::VALUE; // 通过类名访问静态常量成员
return 0;
}
静态数据成员不是给定的类类型的对象的一部分。 因此,静态数据成员的声明不被视为一个定义。 在类范围中声明数据成员,但在文件范围内执行定义。 这些静态类成员具有外部链接。
2.7 this指针
this指针是C++中的一个特殊指针,它是一个隐式参数,存在于类的每一个非静态成员函数中。this指针指向当前对象的地址,通过this指针可以访问当前对象的成员变量和成员函数。
this指针的类型是当前类的常量指针,即const ClassName* const
,其中ClassName
是当前类的名称。这意味着this指针指向的地址不能被修改,也不能用于修改当前对象的常量成员。
在成员函数中,可以使用this指针显式地访问当前对象的成员变量,以消除名称冲突或提高代码的清晰度。
class MyClass {
public:
void setValue(int value) {
this->value = value; // 使用this指针访问成员变量
}
private:
int value;
};
在某些成员函数中,可以返回*this
,即当前对象的引用,以支持链式调用或其他特殊用途。
class MyClass {
public:
MyClass& setValue(int value) {
this->value = value;
return *this; // 返回当前对象的引用
}
private:
int value;
};
int main() {
MyClass obj;
obj.setValue(42).setValue(24); // 链式调用
return 0;
}
在const成员函数中,this指针的类型是const ClassName* const
,这意味着不能通过this指针修改当前对象的成员变量。
class MyClass {
public:
int getValue() const {
return this->value; // 在const成员函数中使用this指针
}
private:
int value;
};
Once Day
也信美人终作土,不堪幽梦太匆匆......
如果这篇文章为您带来了帮助或启发,不妨点个赞👍和关注,再加上一个小小的收藏⭐!
(。◕‿◕。)感谢您的阅读与支持~~~