学习目标:
自学c++面向对象知识点。
第五章 数据的共享与保护
1.静态数据成员:
在C++中,静态数据成员是属于类而不是类的实例的成员。静态数据成员在所有类的实例之间共享,而不是每个实例都有自己的拷贝。它在类的整个生命周期内存在,而不是依赖于特定实例的创建和销毁。
静态数据成员的声明和定义方式与普通数据成员有些不同,它需要使用 static
关键字来标识。通常,**静态数据成员的初始化和声明是在类的定义文件中进行的,而在类的实现文件中进行定义。**以下是一个简单的例子:
#include <iostream>
class MyClass {
public:
// 普通成员函数
void setNumber(int num) {
number = num;
staticNumber = num; // 静态数据成员可以在普通成员函数中使用
}
// 静态成员函数
static void printStaticNumber() {
// 不能在静态成员函数中直接访问非静态数据成员
// std::cout << "Number: " << number << std::endl; // 错误
// 可以直接访问静态数据成员
std::cout << "Static Number: " << staticNumber << std::endl;
}
private:
int number; // 普通数据成员
static int staticNumber; // 静态数据成员
};
// 初始化静态数据成员
int MyClass::staticNumber = 0;
int main() {
MyClass obj1, obj2;
obj1.setNumber(42);
obj2.setNumber(123);
MyClass::printStaticNumber(); // 通过类名调用静态成员函数
return 0;
}
在这个例子中,number
是普通的数据成员,而 staticNumber
是静态数据成员。注意如何初始化静态数据成员,它通常在类的实现文件中进行,而不是在类的声明文件中。
静态数据成员的特性:
- 所有类的实例共享同一份静态数据成员。
- 静态数据成员可以通过类名直接访问,而不需要创建类的实例。
- 静态数据成员的生命周期与程序的生命周期相同,它们在程序启动时被初始化,在程序结束时被销毁。
- 静态数据成员是私有类型也可以直接初始化。
2.静态函数成员
(1)不依赖于类的实例,因此可以直接通过类名调用。
(2)不能直接访问类的非静态数据成员和非静态成员函数,必须通过对象名。静态成员函数只能直接访问类的静态数据成员和静态成员函数。
(3)静态成员函数不能使用 this 指针,因为它没有特定的对象实例。
3.友元函数
在C++中,友元函数(Friend Functions)是与类相关联的非成员函数,它被允许访问类的私有成员。友元函数通常用于提供某些与类关联的操作,但这些操作不是类的成员函数。
友元函数的声明和定义通常在类的声明中,但它不是类的成员函数。它可以访问类的私有成员,就像它是类的朋友一样。
以下是一个简单的例子:
#include <iostream>
class MyClass {
private:
int privateData;
public:
MyClass(int data) : privateData(data) {}
// 友元函数的声明
friend void friendFunction(const MyClass& obj);
};
// 友元函数的定义
void friendFunction(const MyClass& obj) {
std::cout << "Friend Function: Accessing private data - " << obj.privateData << std::endl;
}
int main() {
MyClass myObject(42);
// 调用友元函数
friendFunction(myObject);
return 0;
}
在这个例子中,friendFunction
是一个友元函数,它可以访问 MyClass
类的私有成员 privateData
,即使它不是 MyClass
的成员函数。
友元函数的特性:
- 友元函数声明通常在类的声明中,但它的定义在类的外部。
- 友元函数可以访问类的所有成员,包括私有成员。
- 友元函数不是类的成员函数,但它可以被类友好地访问。
- 友元函数可以是另外一个类的成员函数,使用时要通过相应的类或对象名访问。
需要注意的是,友元函数的使用应该谨慎,因为它打破了封装的原则,增加了类的耦合性。友元函数应该在确实需要对类的私有成员进行访问时使用,而不是滥用。
4.友元类
在C++中,友元类(Friend Class)是一种机制,允许一个类将另一个类声明为其友元,从而使得友元类能够访问声明它为友元的类的私有成员。这可以用于提供更灵活的访问权限,但同样需要慎重使用,以避免破坏封装性。
以下是一个简单的例子:
#include <iostream>
// 前向声明
class FriendClass;
class MyClass {
private:
int privateData;
public:
MyClass(int data) : privateData(data) {}
// 友元类的声明
friend class FriendClass;
};
// 友元类的定义
class FriendClass {
public:
// 友元类的成员函数可以访问 MyClass 的私有成员
void accessPrivateData(const MyClass& obj) {
std::cout << "FriendClass accessing private data: " << obj.privateData << std::endl;
}
};
int main() {
MyClass myObject(42);
FriendClass friendObject;
friendObject.accessPrivateData(myObject);
return 0;
}
在这个例子中,FriendClass
被声明为 MyClass
的友元类。因此,FriendClass
的成员函数 accessPrivateData
可以访问 MyClass
的私有成员 privateData
。
友元类的特性:
- 友元类的声明通常出现在类的声明中,而友元类的定义出现在类的外部。
- 友元类的所有成员函数都可以访问声明它为友元的类的私有成员。
- 友元关系是单向的,如果类 A 声明类 B 为友元,不一定意味着类 B 也声明了类 A 为友元。
- 友元关系也是不可以传递的。
需要注意的是,过度使用友元类可能导致类之间的耦合性增加,降低代码的封装性。友元关系应该在确实需要对私有成员进行访问的情况下使用,并谨慎选择何时使用友元关系。
5.const:常对象必须进行初始化,且不可以被更新。
(1)如果将一个对象声明为常对象,则只能调用常成员函数(这也是常对象唯一的对外接口方式),而不能调用其他成员函数。
(2)常成员函数不能更新目的对象的数据成员,也不能调用没有const修饰的成员函数。
(3)const关键字可以用于重载。
void print();
void print() const;
(4)常数据成员:通过构造函数进行初始化,只能通过初始化列表。
//类中定义
const int a;
//类外初始化列表
A::A(int i):a(i){};
(5)常引用:其所引用的对象不能被更新。非const的引用只能绑定到普通的对象,不能绑定到常对象。而常引用可以绑定到普通的对象,也可以绑定到常对象。
6.外部变量与外部函数:
在编程中,"外部变量"和"外部函数"通常指的是在一个文件中定义的变量和函数,然后在其他文件中通过声明来引用它们。
外部变量:
外部变量是在一个源文件中定义的变量,然后通过 extern
关键字在其他源文件中进行声明以便引用。外部变量的生命周期跨越整个程序,可以在多个文件之间共享。
在一个文件中定义外部变量:
// file1.c
int globalVariable = 42;
在另一个文件中通过 extern
关键字声明并引用外部变量:
// file2.c
extern int globalVariable;
外部函数:
外部函数是在一个源文件中定义的函数,然后通过函数原型(函数声明)在其他文件中进行声明以便引用。外部函数允许在一个文件中定义函数的实现,而在其他文件中调用该函数。
在一个文件中定义外部函数:
// file1.c
#include <stdio.h>
void externalFunction() {
printf("This is an external function.\n");
}
在另一个文件中通过函数原型声明并引用外部函数:
// file2.c
extern void externalFunction();
int main() {
externalFunction(); // 调用外部函数
return 0;
}
第六章 数组与指针
1.数组作为参数时,传递的是地址;若在被调函数中对形参数组元素值进行改变,主调函数中实参数组的相应元素值也会改变。
2.this指针:
在C++中,this
指针是一个特殊的指针,指向当前对象的地址。它是一个隐含的参数(隐含于每一个类的非静态成员函数中的特殊指针,包括构造函数和析构函数),用于指向被调用的成员函数所操作的对象。当成员函数被调用时,this
指针被隐式传递给成员函数,指向调用该函数的对象。
以下是关于 this
指针的一些重要概念:
成员函数内部使用:
在成员函数内部,可以通过 this
指针来访问对象的成员变量和成员函数。这对于区分成员变量和函数参数同名的情况很有帮助。
class MyClass {
public:
void printAddress() {
std::cout << "Address of the object: " << this << std::endl;
}
};
隐式传递:
当对象调用其成员函数时,this
指针被隐式传递给成员函数。例如:
MyClass obj1, obj2;
obj1.printAddress(); // this 指向 obj1
obj2.printAddress(); // this 指向 obj2
用于返回当前对象:
在成员函数中,this
指针可以用于返回当前对象的引用,从而支持链式调用。
class MyClass {
public:
MyClass& setX(int x) {
this->x = x;
return *this;
}
private:
int x;
};
MyClass obj;
obj.setX(42); // 链式调用
在静态成员函数中不可用:
静态成员函数是与类关联而不与对象关联的函数,因此在静态成员函数中无法使用 this
指针。
class MyClass {
public:
static void staticFunction() {
// 无法使用 this 指针
}
};
this
指针的使用使得在成员函数中能够访问对象的成员变量和成员函数,同时也帮助解决了成员变量和函数参数同名的问题。在大多数情况下,程序员无需显式地使用 this
指针,因为它在成员函数内部自动被引入。
3.指向类的非静态成员的指针
指向类的非静态成员的指针是指针,它可以用来指向类的非静态成员变量或非静态成员函数。这种指针通常使用类类型和成员类型进行声明。以下是一些例子:
(1) 指向非静态成员变量的指针:
格式:类型说明符 类名::*指针名;
#include <iostream>
class MyClass {
public:
int myVariable;
};
int main() {
MyClass obj;
obj.myVariable = 42;
// 声明指向非静态成员变量的指针
int MyClass::*ptr = &MyClass::myVariable;
// 使用指针访问成员变量
std::cout << "Value of myVariable: " << obj.*ptr << std::endl;
return 0;
}
在这个例子中,int MyClass::*ptr
是一个指向 MyClass
类的非静态成员变量 myVariable
的指针。通过 obj.*ptr
,我们可以访问 obj
对象的 myVariable
成员变量。
(2)指向非静态成员函数的指针:
格式:类型说明符 (类名::*指针名)(参数表);
#include <iostream>
class MyClass {
public:
void printMessage() {
std::cout << "Hello, World!" << std::endl;
}
};
int main() {
MyClass obj;
// 声明指向非静态成员函数的指针
void (MyClass::*funcPtr)() = &MyClass::printMessage;
// 使用指针调用成员函数
(obj.*funcPtr)();
return 0;
}
在这个例子中,void (MyClass::*funcPtr)()
是一个指向 MyClass
类的非静态成员函数 printMessage
的指针。通过 (obj.*funcPtr)()
,我们可以调用 obj
对象的 printMessage
成员函数。
注:声明了指针后,还需赋值。赋值的格式为:指针名 = &类名::数据/函数成员名;
调用的格式:对象名.*指针名 或 对象指针名->类成员指针名;函数的话,在后面加一个(参数表)即可。
4.指向类的静态成员的指针
对于类的静态成员,不需要创建类的实例就可以访问它们。因此,指向类的静态成员的指针与指向非静态成员的指针有所不同。以下是指向类的静态成员的指针的例子:
#include <iostream>
class MyClass {
public:
static int staticVariable;
static void staticFunction() {
std::cout << "Static function called." << std::endl;
}
};
// 初始化静态成员变量
int MyClass::staticVariable = 42;
int main() {
// 声明指向静态成员变量的指针
int* ptrStaticVariable = &MyClass::staticVariable;
// 使用指针访问静态成员变量
std::cout << "Value of staticVariable: " << *ptrStaticVariable << std::endl;
// 声明指向静态成员函数的指针
void (*ptrStaticFunction)() = &MyClass::staticFunction;
// 使用指针调用静态成员函数
ptrStaticFunction();
return 0;
}
在这个例子中,int* ptrStaticVariable = &MyClass::staticVariable;
是一个指向 MyClass
类的静态成员变量 staticVariable
的指针。通过 *ptrStaticVariable
我们可以访问 staticVariable
的值。
void (*ptrStaticFunction)() = &MyClass::staticFunction;
是一个指向 MyClass
类的静态成员函数 staticFunction
的指针。通过 ptrStaticFunction()
我们可以调用 staticFunction
。
需要注意的是,静态成员函数和静态成员变量是与类关联而不与对象关联的,因此在声明和使用指针时不需要实例化对象。
5.深层复制与浅层复制:
深层复制(Deep Copy)和浅层复制(Shallow Copy)是在编程中常用于描述复制对象或数据结构时的两种不同方式。
深层复制(Deep Copy):
深层复制是指在复制对象时,会复制对象所包含的所有数据,包括对象内部的所有子对象。这意味着新创建的对象与原始对象是完全独立的,对其中一个对象的修改不会影响到另一个对象。
在深层复制中,每个对象及其所有相关的对象都会被递归地复制。这通常涉及到对象的拷贝构造函数或者自定义的复制逻辑。深层复制通常用于确保对象之间的独立性,防止共享相同的内存引用。
#include <iostream>
#include <string>
class DeepCopyExample {
public:
DeepCopyExample(const std::string& str) : data(new std::string(str)) {}
// 深层复制的拷贝构造函数
DeepCopyExample(const DeepCopyExample& other) : data(new std::string(*(other.data))) {}
void printData() const {
std::cout << *data << std::endl;
}
private:
std::string* data;
};
int main() {
DeepCopyExample obj1("Hello");
DeepCopyExample obj2 = obj1; // 深层复制
obj2.printData(); // 输出 "Hello"
// 修改 obj1 中的数据不会影响 obj2
obj1.printData(); // 输出 "Hello"
obj1.printData(); // 输出 "Hello"
return 0;
}
浅层复制(Shallow Copy):
浅层复制是指在复制对象时,只复制对象本身,而不复制对象内部包含的引用或指针。这意味着新创建的对象与原始对象共享相同的子对象,对其中一个对象的修改会影响到另一个对象。
在浅层复制中,只有对象本身被复制,而对象内部的指针或引用则被直接复制,两个对象共享同一块内存。因此该空间被两次释放,于是导致运行错误。
#include <iostream>
#include <string>
class ShallowCopyExample {
public:
ShallowCopyExample(const std::string& str) : data(new std::string(str)) {}
// 浅层复制的拷贝构造函数
ShallowCopyExample(const ShallowCopyExample& other) : data(other.data) {}
void printData() const {
std::cout << *data << std::endl;
}
private:
std::string* data;
};
int main() {
ShallowCopyExample obj1("Hello");
ShallowCopyExample obj2 = obj1; // 浅层复制
obj2.printData(); // 输出 "Hello"
// 修改 obj1 中的数据会影响 obj2
obj1.printData(); // 输出 "Hello"
obj1.printData(); // 输出 "Hello"
return 0;
}
在选择深层复制或浅层复制时,需要根据程序的需求和数据结构的特性来决定。深层复制通常比较安全,但可能会导致额外的内存开销。浅层复制可能更高效,但需要注意共享的对象可能会导致不希望的副作用。