目录
一、对象数组
在C++中,可以使用对象数组来存储和操作一组相关的对象。下面是一个示例:
class Student {
private:
std::string name;
int age;
std::string gender;
public:
// 构造函数
Student(std::string n, int a, std::string g) {
name = n;
age = a;
gender = g;
}
// Getter 和 Setter 方法
std::string getName() {
return name;
}
void setName(std::string n) {
name = n;
}
int getAge() {
return age;
}
void setAge(int a) {
age = a;
}
std::string getGender() {
return gender;
}
void setGender(std::string g) {
gender = g;
}
};
int main() {
// 声明一个存储 Student 对象的数组
Student students[10];
// 初始化数组中的每个对象
students[0] = Student("张三", 18, "男");
students[1] = Student("李四", 19, "女");
// ...
// 访问对象数组中的元素
std::cout << "第一个学生的姓名是:" << students[0].getName() << std::endl;
std::cout << "第二个学生的年龄是:" << students[1].getAge() << std::endl;
// 修改对象数组中的元素
students[0].setName("王五");
students[1].setAge(20);
return 0;
}
在上面的示例中,Student
是一个自定义的类,有姓名、年龄和性别等属性。然后在main
函数中声明了一个名为students
的对象数组,用来存储多个学生对象。可以通过索引访问和操作每个学生对象。最后,通过对象数组可以方便地对一组学生对象进行管理和操作。
二、对象指针
对象指针是指指向对象的指针变量。在C++中,可以通过使用对象指针来操作对象的成员和调用成员函数。
通过对象指针可以访问对象的成员,使用 ->
运算符可以在指针上调用成员函数和访问成员变量。例如,studentPtr->getName()
会调用Student
对象的getName()
函数。
下面是一个示例:
class Student {
private:
std::string name;
int age;
std::string gender;
public:
// 构造函数
Student(std::string n, int a, std::string g) {
name = n;
age = a;
gender = g;
}
// Getter 和 Setter 方法
std::string getName() {
return name;
}
void setName(std::string n) {
name = n;
}
int getAge() {
return age;
}
void setAge(int a) {
age = a;
}
std::string getGender() {
return gender;
}
void setGender(std::string g) {
gender = g;
}
};
int main() {
// 声明一个指向 Student 对象的指针
Student* studentPtr;
// 创建一个 Student 对象,并将指针指向该对象
Student student("张三", 18, "男");
studentPtr = &student;
// 通过指针访问对象的成员
std::cout << "学生的姓名是:" << studentPtr->getName() << std::endl;
std::cout << "学生的年龄是:" << studentPtr->getAge() << std::endl;
// 通过指针调用对象的成员函数
studentPtr->setName("王五");
studentPtr->setAge(20);
return 0;
}
在上面的示例中,Student
是一个自定义的类,有姓名、年龄和性别等属性。然后在main
函数中声明了一个名为studentPtr
的指针变量,用来指向Student
对象。通过使用 &
操作符,我们将指针studentPtr
指向了一个实际的Student
对象 student
。
注意:
- 当使用对象指针时,必须确保指针指向的对象是有效的(未被销毁)。否则,在使用指针访问成员时可能会导致程序崩溃或未定义行为。
三、this 指针
this
指针是一个指向当前对象的指针,它可以在类的成员函数中使用。在C++中,每个非静态成员函数都有一个隐含的this
指针,它指向调用该函数的对象。通过this
指针,可以在成员函数中访问和操作对象的成员变量和成员函数。
下面是一个示例:
class MyClass {
private:
int value;
public:
// 构造函数
MyClass(int v) {
value = v;
}
// 成员函数
void printValue() {
std::cout << "对象的值是:" << value << std::endl;
}
void setValue(int v) {
this->value = v;
}
void printAddress() {
std::cout << "对象的地址是:" << this << std::endl;
}
};
int main() {
MyClass obj(10);
obj.printValue();
obj.setValue(20);
obj.printValue();
obj.printAddress();
return 0;
}
在上面的示例中,MyClass
是一个包含一个私有成员变量value
和一些成员函数的类。在printValue
函数中,我们使用this
指针来访问对象的成员变量value
并打印出来。在setValue
函数中,我们使用this
指针来设置对象的成员变量value
的值。
this
指针还可以用于获取对象的地址,如printAddress
函数所示。通过打印this
指针,我们可以看到它指向的是创建的对象的地址。
注意:
this
指针是一个常量指针,不能被修改和赋值。所以不能将this
指针作为参数传递给其他函数或赋值给其他指针变量。它只能被用于访问当前对象的成员。
四、类类型作为参数类型的三种形式
4.1 对象本身作为参数
在C++中,可以将一个类类型的对象本身作为参数传递给函数。这意味着函数可以直接操作和修改对象的成员变量和成员函数。
示例如下:
#include <iostream>
class MyClass {
public:
int x;
MyClass(int a) {
x = a;
}
void func() {
std::cout << "x的值为: " << x << std::endl;
}
};
void modifyObject(MyClass obj) {
obj.x = 10;
}
int main() {
MyClass obj(5);
obj.func(); // 输出: x的值为: 5
modifyObject(obj);
obj.func(); // 输出: x的值为: 5(对象本身不会被修改)
return 0;
}
在上面的示例中,我们定义了一个名为MyClass
的类,它包含一个整型成员变量x
和一个成员函数func()
。然后,我们定义了一个函数modifyObject()
,它接受一个MyClass
对象作为参数。在modifyObject()
函数中,我们试图修改传递进来的对象的 x
成员变量的值,但实际上,这个修改只在modifyObject()
函数内部生效,并不会影响到调用函数时传递的原始对象。所以,在主函数main()
中,我们调用modifyObject(obj)
后,再次输出obj.x
的值时,它的值仍然是调用前的原始值。
注意:
- 当将一个对象作为参数传递给函数时,C++会调用对象的拷贝构造函数来创建一个新的对象。这意味着函数内部对该对象的修改不会影响到原始对象。如果希望在函数内部修改原始对象的值,可以将对象作为引用或指针来传递。
4.2 对象指针作为参数
在C++中,我们可以将对象指针作为函数的参数来传递对象。使用对象指针作为参数可以使函数对对象进行原地修改,而不必复制对象或返回新对象。
下面是一个简单的示例,展示了如何将对象指针作为参数传递:
#include <iostream>
class MyClass {
public:
int num;
void printNum() {
std::cout << "The number is: " << num << std::endl;
}
};
// 接受对象指针作为参数的函数
void updateNum(MyClass* obj, int newNum) {
obj->num = newNum;
}
int main() {
MyClass obj;
obj.num = 10;
obj.printNum(); // 输出: The number is: 10
updateNum(&obj, 20);
obj.printNum(); // 输出: The number is: 20
return 0;
}
在上面的示例中,我们定义了一个MyClass
类,它包含一个整数成员变量num
和一个打印成员变量的函数printNum()
。我们还定义了一个名为updateNum()
的函数,它接受一个MyClass
对象指针和一个新的整数值,并将对象的num
变量更新为新值。
在main()
函数中,我们创建一个MyClass
对象obj
,并将其变量设置为10。我们调用updateNum()
函数,将obj
的指针和新值20作为参数传递。这样,updateNum()
函数就可以直接操作obj
的num
变量,并将其值更新为20。最后,我们再次调用printNum()
函数来验证num
变量是否已经更新为20。
注意:
- 在使用对象指针时要确保对象是有效的,并在不再需要时及时释放内存。
4.3 对象引用作为参数
在C++中,我们可以将对象引用作为函数的参数来传递对象。使用对象引用作为参数可以使函数对对象进行原地修改,而不必复制对象或返回新对象。与对象指针相比,不需要使用额外的操作符来访问成员变量。同时,对象引用也自动处理了对象是否为有效的情况,因此不需要在使用之前进行额外的检查。
下面是一个简单的示例,展示了如何将对象引用作为参数传递:
#include <iostream>
class MyClass {
public:
int num;
void printNum() {
std::cout << "The number is: " << num << std::endl;
}
};
// 接受对象引用作为参数的函数
void updateNum(MyClass& obj, int newNum) {
obj.num = newNum;
}
int main() {
MyClass obj;
obj.num = 10;
obj.printNum(); // 输出: The number is: 10
updateNum(obj, 20);
obj.printNum(); // 输出: The number is: 20
return 0;
}
在上面的示例中,我们定义了一个MyClass
类,它包含一个整数成员变量num
和一个打印成员变量的函数printNum()
。我们还定义了一个名为updateNum()
的函数,它接受一个MyClass
对象引用和一个新的整数值,并将对象的num
变量更新为新值。
在main()
函数中,我们创建一个MyClass
对象obj
,并将其num
变量设置为10。然后,我们调用updateNum()
函数,将obj
和新值20作为参数传递。这样,updateNum()
函数就可以直接操作obj
的num
变量,并将其值更新为20。最后,我们再次调用printNum()
函数来验证num
变量是否已经更新为20。
注意:
- 一旦对象引用作为参数传递给函数,函数内部对对象的修改将会影响到原始对象。因此,在使用对象引用作为函数参数时,要格外小心,确保不会意外修改对象的状态。
五、静态成员
静态成员是类的成员,而不是对象的成员。它们与类相关联,而不是与特定的对象相关联。静态成员在类的所有对象之间共享,并且可以通过类名来访问,而不需要创建类的实例。
静态成员可以是静态数据成员或静态成员函数。静态数据成员在类中只有一个实例,并且可以在所有对象之间共享。静态成员函数不依赖于任何特定对象的实例,可以直接通过类名来调用。
5.1 静态数据成员
在C++中,静态数据成员是类的特殊成员,它被该类的所有对象所共享,而不是每个对象独立拥有。静态数据成员的值在类的所有对象中是相同的。
静态数据成员具有以下特点:
- 静态数据成员属于整个类,而不是类的某个具体对象。它在内存中只有一份拷贝,而不是每个对象都有一份。
- 静态数据成员可以在类的内部声明,在类的外部进行定义和初始化。在定义时,需要在成员名称前加上
static
关键字,同时需要在类的外部进行变量的定义和初始化。 - 静态数据成员的作用域限定在所属的类内部,可以通过类名和作用域解析操作符
::
来直接访问。 - 静态数据成员可以在类的对象之间共享数据,可以用于在对象之间传递共享的数据。
以下是一个示例:
class MyClass {
public:
static int myStaticVar; // 静态数据成员的声明
static int getStaticVar() { // 静态成员函数可以直接访问静态数据成员
return myStaticVar;
}
};
int MyClass::myStaticVar = 0; // 静态数据成员的定义和初始化
int main() {
MyClass obj1;
MyClass obj2;
obj1.myStaticVar = 5;
cout << obj1.myStaticVar << endl; // 输出 5
cout << obj2.myStaticVar << endl; // 输出 5,静态数据成员在所有对象中共享
cout << MyClass::myStaticVar << endl; // 通过类名访问静态数据成员
return 0;
}
在以上示例中,myStaticVar
是一个静态成员变量,通过类名来访问。通过对象obj1
和obj2
来访问myStaticVar
,它们都共享相同的值。静态成员函数getStaticVar()
可以直接访问静态数据成员。
5.2 静态成员函数
在C++中,静态成员函数是属于整个类的,而不是类的某个对象,它不依赖于特定的对象实例。静态成员函数可以直接通过类名来调用,而不需要创建对象。
静态成员函数具有以下特点:
- 静态成员函数没有this指针,因此不能直接访问非静态成员变量和非静态成员函数。它只能访问静态成员变量和静态成员函数。
- 静态成员函数可以在类的内部声明,在类的外部进行定义。
- 静态成员函数的调用方式是通过类名和作用域解析操作符
::
来调用。
以下是一个示例:
class MyClass {
public:
static int myStaticVar; // 静态数据成员的声明
static int getStaticVar() { // 静态成员函数的定义
return myStaticVar;
}
static void setStaticVar(int value) {
myStaticVar = value;
}
};
int MyClass::myStaticVar = 0; // 静态数据成员的定义和初始化
int main() {
MyClass::setStaticVar(5); // 通过类名调用静态成员函数
cout << MyClass::getStaticVar() << endl; // 通过类名调用静态成员函数
return 0;
}
在以上示例中,getStaticVar()
和setStaticVar()
都是静态成员函数,可以直接通过类名来调用。静态成员函数可以用于访问和操作静态数据成员,而不依赖于具体的对象实例。
六、友元机制
友元机制是C++中一种特殊的访问权限控制机制,它允许一个类或函数访问另一个类的私有成员。
在C++中,可以使用friend
关键字将一个函数或类声明为友元。被声明为友元的函数或类可以直接访问其它类的私有和保护成员,而不受访问权限的限制。
6.1 友元函数
友元函数是指被声明为某个类的友元的函数,可以访问该类的私有成员和保护成员。通过友元函数,可以实现一些与该类有关的操作,即使这些操作不属于该类的成员函数。
以下是一个友元函数的示例:
class MyClass {
private:
int privateData;
public:
MyClass(int data) : privateData(data) {}
friend int friendFunction(MyClass& obj);
};
int friendFunction(MyClass& obj) {
// 友元函数可以访问私有成员
return obj.privateData;
}
int main() {
MyClass myObj(5);
int data = friendFunction(myObj);
cout << "Private data: " << data << endl;
return 0;
}
在上述示例中,MyClass
类声明了一个友元函数friendFunction()
,并将其声明为友元。friendFunction()
函数可以访问MyClass
的私有成员privateData
。
在main()
函数中,我们创建了一个MyClass
对象myObj
,并将其初始化为5。然后调用friendFunction()
函数,该函数访问了myObj
的私有成员privateData
并返回它。最后,我们将返回的私有数据打印到控制台上。
通过友元函数,我们可以方便地访问类的私有成员,但同样需要谨慎使用,以保护数据的封装性。
6.2 友元类
友元类是指一个类被声明为另一个类的友元,可以访问该类的私有成员和保护成员。这样,友元类可以在其成员函数中访问被友元类的私有成员。
以下是一个友元类的示例:
class FriendClass {
private:
int privateData;
public:
FriendClass(int data) : privateData(data) {}
friend class MyClass;
};
class MyClass {
public:
void accessPrivateData(FriendClass& obj) {
// 友元类可以访问私有成员
int data = obj.privateData;
cout << "Private data: " << data << endl;
}
};
int main() {
FriendClass friendObj(5);
MyClass myObj;
myObj.accessPrivateData(friendObj);
return 0;
}
在上述示例中,FriendClass
类被声明为MyClass
类的友元类。因此,在MyClass
类中的成员函数accessPrivateData()
中,可以访问FriendClass
类的私有成员privateData
。
在main()
函数中,我们创建了一个FriendClass
对象friendObj
,并将其初始化为5。然后,我们创建了一个MyClass
对象myObj
,并调用其成员函数accessPrivateData()
,将friendObj
作为参数传递给它。accessPrivateData()
函数可以访问friendObj
的私有成员privateData
,并将其打印到控制台上。
通过友元类,类之间的访问限制可以得到打破,但同样需要谨慎使用,以保护数据的封装性。
七、类的组合
在C++中,类的组合是指一个类包含另一个类的对象作为其成员。通过组合,一个类可以使用另一个类的功能,并将其封装在自己的实现中。
组合是一种常用的实现方式,它允许类之间建立关联关系,并共享彼此的功能。通过组合,我们可以构建更加复杂和灵活的类结构。
下面是一个示例,展示了如何在C++中使用类的组合:
// 定义一个矩形类
class Rectangle {
private:
int width;
int height;
public:
Rectangle(int w, int h) {
width = w;
height = h;
}
int calculateArea() {
return width * height;
}
};
// 定义一个房间类,使用矩形类的对象作为成员
class Room {
private:
Rectangle area;
int numOfWindows;
public:
Room(int w, int h, int windows) : area(w, h) {
numOfWindows = windows;
}
int calculateTotalArea() {
return area.calculateArea() * numOfWindows;
}
};
int main() {
// 创建一个房间对象
Room myRoom(10, 12, 4);
// 计算房间的总面积
int totalArea = myRoom.calculateTotalArea();
// 输出结果
cout << "Total area of the room: " << totalArea << endl;
return 0;
}
在上面的示例中,Rectangle
类表示一个矩形,它有一个calculateArea
方法来计算矩形的面积。Room
类表示一个房间,它有一个calculateTotalArea
方法来计算房间的总面积,通过将Rectangle
类的对象作为成员变量area
来实现矩形的组合。通过创建一个Room
对象,我们可以调用其方法来计算房间的总面积。
八、数据成员的初始化和释放顺序
在C++中,类的数据成员的初始化和释放顺序是按照它们在类中的声明顺序确定的。初始化顺序是在进入构造函数的初始化列表之前确定的,而释放顺序是在析构函数中完成的;通过使用初始化列表,在构造函数中可以确保数据成员正确初始化。而在析构函数中,数据成员的释放顺序与初始化顺序相反。
下面是一个示例,展示了数据成员初始化和释放顺序的规则:
class MyClass {
private:
int x;
int y;
int z;
public:
MyClass(int a, int b, int c) : x(a), y(b), z(c) {
// 构造函数
}
~MyClass() {
// 析构函数
}
};
在上面的示例中,MyClass
类有三个整型数据成员:x
、y
和z
。在构造函数中,我们使用初始化列表来初始化这些成员变量,按照它们在类中的声明顺序:先初始化x
,然后是y
,最后是z
。在析构函数中,成员变量的释放顺序与初始化顺序相反,先释放z
,然后是y
,最后是x
。
注意:
- 如果一个数据成员是一个对象或者指向一个对象的指针,在构造函数中如果需要使用它,那么它必须在初始化列表中进行初始化。
九、常对象与常成员
9.1 常对象
常对象是指被声明为const的对象,即对象的值在创建后就不能被修改。
常对象的定义方法:
- 在对象声明时直接加上const关键字。
const ClassName obj; // 声明一个常对象
- 使用类型别名或引用来声明常对象。
typedef const ClassName CObj; // 声明一个常对象的类型别名CObj
CObj obj; // 声明一个常对象
常对象的特点如下:
- 常对象的状态是只读的,无法通过任何途径修改其成员变量的值。
- 常对象只能调用其类中被声明为const的成员函数,不能调用非const成员函数。
- 常对象可以调用常成员函数,因为常成员函数不会修改任何成员变量的值。
常对象在实际编程中有很多用途,例如:
- 在函数中传递常对象作为参数,以防止函数修改对象的值。
- 在类的成员函数中,常对象可以被用作参数或返回值,以保证函数的纯粹性。
- 常对象可以用于实现单例模式中的只读实例。
通过使用常对象,可以提高程序的安全性和可维护性,避免对象值的意外修改。
9.2 常成员
9.2.1 常成员函数
常成员函数指的是在类中声明的成员函数加上const关键字,表示该函数不会修改对象的状态。常成员函数可以被常对象调用,确保对象在调用该函数时不会被修改。
常成员函数的定义方式:
class ClassName {
public:
void functionName() const;
};
在函数声明后加上const关键字,表示该函数是一个常成员函数。常成员函数可以在类的内部或外部进行定义。
常成员函数的特点如下:
- 常成员函数不能修改对象的非常量数据成员。它只能读取数据成员的值,而不能修改它们。
- 常成员函数只能调用其他常成员函数,而不能调用非常成员函数。这是为了保证在常成员函数中不会修改对象的状态。
9.2.2 常成员数据
常成员数据指的是在类中声明的成员变量加上const关键字,表示该变量的值在对象创建后就不能被修改。常成员数据必须在类的构造函数初始化列表中进行初始化。
常成员数据的定义方式为:
class ClassName {
private:
const int variableName;
public:
ClassName(int value) : variableName(value) {}
};
在成员变量的类型前加上const关键字,表示该变量是一个常成员数据。在构造函数初始化列表中对常成员数据进行初始化。
常成员数据的特点如下:
- 常成员数据的值在对象创建后就不能被修改,它们被视为对象的常量。
- 常成员数据只能在构造函数的初始化列表中进行初始化,而不能在构造函数内部或其他成员函数中进行修改。