C++中的类和对象详解
目录
一、类(Class)的概念
类是C++中的一种用户自定义数据类型,它封装了数据(成员变量)和操作这些数据的函数(成员函数)。类可以看作是一个模板,用于创建具有相同属性和行为的对象集合。通过类,我们可以将数据和操作数据的函数组织在一起,从而实现面向对象编程的核心思想:封装、继承和多态。
在C++中,类的定义通常使用class关键字,其基本语法如下:
class 类名
{
// 成员变量和成员函数的声明
};
类中的成员可以分为三类,它们的访问权限不同:
-
私有(private):这是默认的访问权限。私有成员只能被类的成员函数访问,不能被类的对象直接访问。这有助于保护类的内部实现细节,防止外部直接修改成员变量。
-
公有(public):公有成员可以被类的对象直接访问。通常,类的成员函数会被声明为公有,以便外部可以通过对象调用这些函数。
-
保护(protected):保护成员可以被类的成员函数和派生类访问,但不能被类的对象直接访问。这种访问权限主要用于类的继承机制中,允许派生类访问基类的某些成员,同时隐藏这些成员的实现细节。
二、对象(Object)的概念
对象是类的实例,是类的具体实现。通过对象,我们可以访问类的公有成员变量和成员函数。对象是类的实体,它在内存中分配空间,存储类的成员变量的值。
创建对象的基本语法如下:
类名 对象名;
例如,如果我们定义了一个名为Person的类,我们可以创建一个Person对象如下:
Person p1;
三、类的使用示例
定义一个简单的类
以下是一个简单的类的定义和使用示例:
#include <iostream>
using namespace std;
// 定义一个类
class Person
{
private:
string name; // 成员变量
int age; // 成员变量
public:
// 构造函数
Person(string n, int a) : name(n), age(a) {}
// 成员函数
void display()
{
cout << "Name: " << name << ", Age: " << age << endl;
}
// 析构函数
~Person()
{
cout << "Person object destroyed." << endl;
}
};
int main()
{
// 创建对象
Person p1("John", 30);
// 调用成员函数
p1.display();
return 0;
}
输出结果
Name: John, Age: 30
Person object destroyed.
四、类的成员变量和成员函数
(一)成员变量
成员变量用于存储对象的状态信息。它们可以是基本数据类型(如int、float、char等)、数组、指针,甚至是其他类的对象。成员变量通常被声明为私有,以保护对象的内部状态。
(二)成员函数
成员函数用于定义对象的行为。它们可以访问类的成员变量,并可以被对象调用。成员函数通常被声明为公有,以便外部可以通过对象调用这些函数。
五、八大默认成员函数
在C++中,类默认提供以下八个特殊成员函数,除非用户显式定义或删除它们。这些默认成员函数在类的生命周期管理和对象操作中起着重要作用。
(一)默认构造函数
默认构造函数是一个没有参数的构造函数。如果类中没有定义任何构造函数,编译器会自动生成一个默认构造函数。默认构造函数的主要作用是初始化对象的成员变量。如果类中没有定义任何构造函数,编译器生成的默认构造函数不会对成员变量进行初始化,成员变量的值将是未定义的(对于基本数据类型)或默认构造(对于类类型成员变量)。
类名::类名() {} // 默认构造函数
(二)默认析构函数
默认析构函数用于释放对象占用的资源。如果类中没有定义析构函数,编译器会自动生成一个默认析构函数。默认析构函数的主要作用是销毁对象,释放对象占用的资源。如果类中没有动态分配的内存或其他需要手动释放的资源,编译器生成的默认析构函数通常不会执行任何操作。
类名:: ~类名() {} // 默认析构函数
(三)拷贝构造函数
拷贝构造函数用于创建对象的新副本。它的参数是一个同类型的对象的引用。拷贝构造函数的主要作用是创建一个与原对象具有相同状态的新对象。如果类中没有定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数,该函数会逐成员地复制对象的成员变量。
类名::类名(const 类名& other)
(四)赋值运算符重载
在C++中,赋值运算符(=)可以被重载,将一个存在的对象的值赋给另外一个存在的对象,以实现自定义的赋值逻辑。赋值运算符重载通常用于处理类中的动态内存分配或其他资源管理任务。
类名& 类名::operator=(const 类名& other)
{
// 赋值运算符重载函数体
return *this; // 返回当前对象的引用
}
为了区分前置++和后置++,C++规定,在重载后置++时多增加了一个int类型的参数,但是调用时不用传参
后置++:Data& opertor++(int)
前置++:Data opertor++( )
(五)普通对象取地址运算符重载
在C++中,取地址运算符(&)通常用于获取变量的地址。然而,C++允许我们重载这个运算符,以便为类的对象提供自定义的行为。这种重载通常用于实现智能指针或其他需要自定义地址获取逻辑的场景。
类名* 类名::operator&()const
{
// 返回当前对象的地址
return this;
}
(六)const对象取地址运算符重载
在C++中,取地址运算符(&)可以被重载,但需要特别注意的是,取地址运算符的重载与普通运算符重载有所不同。取地址运算符的重载主要用于返回对象的地址,通常用于实现一些特殊的资源管理或代理模式。
const 类名* 类名::operator&() const
{
// 返回当前对象的地址
return this;
}
(七)移动构造函数(C++11引入)
移动构造函数用于将一个对象的资源“移动”到另一个对象中。它的参数是一个同类型的对象的右值引用(类名&&)。移动构造函数的主要作用是实现资源的高效转移,避免不必要的拷贝。如果类中没有定义移动构造函数,编译器会自动生成一个默认的移动构造函数,该函数会逐成员地移动对象的成员变量。
类名::类名(类名&& other); // 移动构造函数
(八)移动赋值运算符(C++11引入)
移动赋值运算符用于将一个对象的资源“移动”到另一个对象中。它的参数是一个同类型的对象的右值引用(类名&&),返回类型是类名的引用(类名&)。移动赋值运算符的主要作用是实现资源的高效转移,避免不必要的拷贝。如果类中没有定义移动赋值运算符,编译器会自动生成一个默认的移动赋值运算符,该运算符会逐成员地移动对象的成员变量。
类名::类名& operator=(类名&& other); // 移动赋值运算符
六、this指针
定义
this指针是一个隐式的指针参数,它在类的每个非静态成员函数中自动存在。this指针指向调用该成员函数的对象。由于this指针指向当前对象,因此它允许成员函数访问和修改调用它的对象的成员变量和成员函数。
用途
区分同名变量:在成员函数中,参数名可能与成员变量名相同。此时,this指针可以用来区分它们。
返回当前对象的引用:在某些情况下,成员函数需要返回当前对象的引用,以便支持链式调用。
传递当前对象:可以将this指针传递给其他函数,或者将其存储在其他地方。
this指针的特性
this指针的类型
this指针的类型是类名* const,即指向当前类的常量指针。这意味着this指针本身不能被修改,但它指向的对象的成员可以被修改。
this指针的作用域
this指针只在类的非静态成员函数中有效。静态成员函数不能使用this指针,因为静态成员函数不属于任何特定的对象。
this指针的隐式传递
当调用一个对象的成员函数时,this指针会隐式地传递给该成员函数。
示例:
Person p1("John", 30);
p1.display();
七、类的大小计算
定义
类的大小是指一个类的对象在内存中占用的字节数。类的大小由其成员变量的大小和对齐方式决定。空类的大小为1,用于占位。
影响因素
-
成员变量的大小:类的大小至少等于其所有成员变量大小之和。
-
对齐方式:编译器会根据对齐规则对成员变量进行对齐,可能会在成员变量之间插入填充字节。
-
虚函数表(vtable):如果类中包含虚函数,编译器会为每个对象添加一个虚函数表指针,通常占用4或8字节(取决于平台)。
示例
#include <iostream>
using namespace std;
class A
{
int a;
char b;
};
class B
{
int a;
char b;
virtual void func() {}
};
int main()
{
cout << "Size of A: " << sizeof(A) << endl; // 输出:Size of A: 8
cout << "Size of B: " << sizeof(B) << endl; // 输出:Size of B: 12 或 16(取决于平台)
return 0;
}
八、初始化列表
定义
初始化列表用于在构造函数中初始化成员变量。它比在构造函数体内赋值更高效,尤其是对于类类型的成员变量。初始化列表可以确保成员变量在对象创建时被正确初始化,而不是先默认构造再赋值,初始化的顺序按照声明的顺序。
语法
类名(参数列表)
: 成员变量1(初始化值1), 成员变量2(初始化值2)
{ /* 构造函数体 */ }
示例
#include <iostream>
using namespace std;
class Person
{
string name;
int age;
public:
Person(string n, int a) : name(n), age(a) // 初始化列表
{
cout << "Name: " << name << ", Age: " << age << endl;
}
};
int main()
{
Person p1("John", 30);
return 0;
}
输出结果
Name: John, Age: 30
九、静态成员函数和静态成员变量
(一)静态成员变量
-
定义:静态成员变量属于类,而不是类的某个对象。它在类的所有对象之间共享。
-
声明:在类中使用
static关键字声明。 -
初始化:在类外初始化,通常在定义时初始化。
-
访问:通过类名访问,而不是通过对象名。
(二)静态成员函数
-
定义:静态成员函数属于类,而不是类的某个对象。它不能访问非静态成员变量和非静态成员函数。
-
声明:在类中使用
static关键字声明。 -
调用:通过类名调用,而不是通过对象名。
示例
#include <iostream>
using namespace std;
class Person
{
string name;
static int count; // 静态成员变量
public:
Person(string n) : name(n)
{
count++; // 修改静态成员变量
}
static void displayCount()
{
cout << "Total number of persons: " << count << endl;
}
};
// 初始化静态成员变量
int Person::count = 0;
int main()
{
Person p1("John");
Person p2("Jane");
Person::displayCount(); // 调用静态成员函数
return 0;
}
输出结果
Total number of persons: 2
十、友元
(一)友元的基本概念
定义
友元可以是一个函数、一个类或一个类的成员函数。友元不是类的成员,但它可以访问类的私有和保护成员。友元关系是单向的,不会被继承。
作用
访问私有成员:友元可以访问类的私有和保护成员,这在实现某些特殊功能时非常有用。
实现运算符重载:友元函数常用于实现类的运算符重载,特别是二元运算符。
实现联合类:友元可以用于实现联合类,即多个类共享某些成员函数或数据。
(二)友元的类型
友元函数
友元函数是一个非成员函数,但它可以访问类的私有和保护成员。
友元类
友元类是一个类,它的所有成员函数都可以访问另一个类的私有和保护成员。
友元成员函数
友元成员函数是一个类的成员函数,但它可以访问另一个类的私有和保护成员。
(三)友元的语法形式
友元函数
在类中使用friend关键字声明友元函数。
class 类名
{
friend 返回类型 函数名(参数列表);
};
友元类
在类中使用friend关键字声明友元类。
class 类名
{
friend class 友元类名;
};
友元成员函数
在类中使用friend关键字声明友元成员函数。
class 类名
{
friend 返回类型 友元类名::函数名(参数列表);
};
十一、总结
类和对象是C++面向对象编程的核心概念。类封装了数据和行为,对象是类的实例。通过类的继承和多态,可以实现代码的复用性和灵活性。掌握类和对象的使用,以及八大默认成员函数、类的大小计算、初始化列表、静态成员函数和静态成员变量等知识点,是学习C++面向对象编程的关键。在实际开发中,合理使用类和对象可以提高代码的可读性、可维护性和复用性。同时,理解并正确使用八大默认成员函数、类的大小计算、初始化列表、静态成员函数和静态成员变量等特性,可以帮助我们更好地管理类的生命周期和资源,避免常见的错误和陷阱。
更多推荐


所有评论(0)