友元函数(Friend Function)是C++中的一种特殊函数,它能够访问类的私有(private)和保护(protected)成员,即使它不是该类的成员函数。友元函数通过在类内部声明并在类外部定义来实现。
friend
关键字声明,但定义在类外部。class ClassName {
private:
// 私有成员
public:
friend ReturnType FunctionName(Parameters); // 友元函数声明
};
#include
using namespace std;
class Box {
private:
int length;
public:
Box(int l) : length(l) {}
friend void printLength(Box box); // 声明友元函数
};
// 定义友元函数
void printLength(Box box) {
cout << "Length of box: " << box.length << endl; // 访问私有成员
}
int main() {
Box b(10);
printLength(b); // 调用友元函数
return 0;
}
友元函数通常用于需要访问多个类的私有成员的场景,或用于运算符重载等特殊情况。
友元类(Friend Class)是 C++ 中的一种特殊机制,允许一个类访问另一个类的私有(private
)和保护(protected
)成员。通过将一个类声明为另一个类的友元类,可以突破封装性的限制,实现更灵活的成员访问控制。
class A {
private:
int privateData;
// 声明类 B 为友元类
friend class B;
};
class B {
public:
void accessA(A& obj) {
obj.privateData = 10; // 可以访问 A 的私有成员
}
};
A
中,使用 friend class B;
声明类 B
为友元类。B
可以访问 A
的私有成员,但 A
不能访问 B
的私有成员(除非 A
也被声明为 B
的友元类)。A
是 B
的友元类,B
不一定是 A
的友元类。A
是 B
的友元类,B
是 C
的友元类,A
并不会自动成为 C
的友元类。A
是 B
的友元类,A
的子类不会自动成为 B
的友元类。友元成员函数(Friend Member Function)是C++中一种特殊的函数声明方式,它允许一个类的成员函数访问另一个类的私有(private)和保护(protected)成员。
在类的定义中,使用 friend
关键字声明友元成员函数。语法如下:
class A {
private:
int privateData;
friend void B::accessPrivateData(A& a); // 声明B类的成员函数为友元
};
class B {
public:
void accessPrivateData(A& a) {
a.privateData = 10; // 可以访问A类的私有成员
}
};
B::accessPrivateData
是 A
的友元,B
可以访问 A
的私有成员,但 A
不能访问 B
的私有成员。B
),否则编译器会报错。B
),而不是友元声明所在的类(如 A
)。class A; // 前向声明
class B {
public:
void accessPrivateData(A& a);
};
class A {
private:
int privateData = 0;
friend void B::accessPrivateData(A& a); // 声明B的成员函数为友元
};
void B::accessPrivateData(A& a) {
a.privateData = 42; // 合法访问
}
友元(Friend)是C++中一种特殊的机制,它允许一个函数或类访问另一个类的私有(private)和保护(protected)成员。友元关系打破了类的封装性,但提供了更高的灵活性。
friend
。<<
和>>
)需要访问类的私有成员,但通常以全局函数的形式重载。这时可以将这些函数声明为友元。友元函数:
class MyClass {
private:
int privateData;
public:
friend void friendFunction(MyClass& obj); // 声明友元函数
};
void friendFunction(MyClass& obj) {
obj.privateData = 10; // 可以直接访问私有成员
}
友元类:
class FriendClass; // 前向声明
class MyClass {
private:
int privateData;
public:
friend class FriendClass; // 声明友元类
};
class FriendClass {
public:
void accessPrivate(MyClass& obj) {
obj.privateData = 20; // 可以直接访问MyClass的私有成员
}
};
友元(Friend)是C++中一种特殊的机制,它允许一个类或函数访问另一个类的私有(private)或保护(protected)成员。友元关系通过在类中声明friend
关键字来建立。友元可以是:
友元函数:
class MyClass {
private:
int privateData;
public:
friend void friendFunction(MyClass &obj);
};
void friendFunction(MyClass &obj) {
obj.privateData = 10; // 可以直接访问私有成员
}
友元类:
class FriendClass;
class MyClass {
private:
int privateData;
public:
friend class FriendClass;
};
class FriendClass {
public:
void accessPrivate(MyClass &obj) {
obj.privateData = 20; // 可以直接访问私有成员
}
};
友元成员函数:
class FriendClass;
class MyClass {
private:
int privateData;
public:
friend void FriendClass::accessPrivate(MyClass &obj);
};
class FriendClass {
public:
void accessPrivate(MyClass &obj) {
obj.privateData = 30; // 可以直接访问私有成员
}
};
封装性是面向对象编程的三大特性之一(封装、继承、多态),它通过将数据(成员变量)和操作(成员函数)绑定在一起,并对外隐藏实现细节,仅暴露必要的接口来实现数据保护。
友元机制在一定程度上破坏了封装性,因为它允许外部函数或类直接访问类的私有和保护成员。然而,友元的使用通常是出于以下合理原因:
<<
和>>
)时,可能需要访问私有成员。友元是C++中一种强大的工具,能够在特定场景下提供灵活的访问控制,但它也会对封装性造成一定破坏。合理使用友元可以提高代码的灵活性和效率,但需谨慎权衡其带来的耦合性。
异常(Exception)是C++中处理程序运行时错误的一种机制。当程序在执行过程中遇到无法正常处理的情况时(如除数为零、数组越界、内存不足等),可以通过抛出(throw)异常来中断当前的执行流程,转而寻找能够处理该异常的代码块(catch块)。
抛出异常:
throw expression; // expression可以是任意类型的对象
捕获异常:
try {
// 可能抛出异常的代码
} catch (exceptionType1& e) {
// 处理exceptionType1类型的异常
} catch (exceptionType2& e) {
// 处理exceptionType2类型的异常
} catch (...) {
// 捕获所有未被前面处理的异常
}
throw
语句时,立即停止当前函数的执行。catch
块。catch
块,执行其中的代码。catch
块,调用std::terminate()
终止程序。C++标准库提供了一系列异常类(定义在
头文件中),都继承自std::exception
基类:
logic_error
:程序逻辑错误
invalid_argument
domain_error
length_error
out_of_range
runtime_error
:运行时错误
range_error
overflow_error
underflow_error
catch (const std::exception& e)
)以避免对象切片和多一次拷贝。C++11引入了noexcept
说明符,表示函数不会抛出异常:
void func() noexcept; // 承诺不抛出异常
如果noexcept
函数抛出了异常,程序会调用std::terminate()
终止。
异常抛出与捕获是C++中处理运行时错误的一种机制。当程序遇到无法正常处理的情况时,可以通过抛出异常来中断当前执行流程,并由专门的异常处理代码来捕获和处理这个异常。
throw
关键字抛出异常std::exception
的异常类对象throw 42; // 抛出int类型异常
throw std::runtime_error("Error occurred"); // 抛出标准异常
try-catch
块捕获异常try
块包含可能抛出异常的代码catch
块处理特定类型的异常catch
块处理不同类型的异常try {
// 可能抛出异常的代码
throw std::runtime_error("Error");
} catch (const std::runtime_error& e) {
// 处理runtime_error
std::cerr << e.what() << std::endl;
} catch (...) {
// 捕获所有其他类型的异常
std::cerr << "Unknown exception" << std::endl;
}
std::terminate
)noexcept
关键字声明不抛出异常的函数C++标准库提供了一系列异常类(定义在
中):
std::exception
(所有标准异常的基类)std::runtime_error
(运行时错误)std::logic_error
(逻辑错误)每个标准异常类都提供what()
成员函数返回错误描述信息。
try-catch
块是C++中用于异常处理的基本结构,允许程序在运行时捕获和处理异常情况。
try {
// 可能抛出异常的代码
} catch (exceptionType1& e) {
// 处理exceptionType1类型的异常
} catch (exceptionType2& e) {
// 处理exceptionType2类型的异常
} catch (...) {
// 处理所有其他类型的异常
}
try块:
catch块:
...
)exceptionType& e
),以避免对象切片catch(...)
可以捕获所有异常,但无法访问异常对象throw;
)#include
#include
int main() {
try {
int age = -5;
if (age < 0) {
throw std::invalid_argument("Age cannot be negative");
}
} catch (const std::invalid_argument& e) {
std::cerr << "Invalid argument: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Unknown exception occurred" << std::endl;
}
return 0;
}
在这个例子中,当年龄为负数时,会抛出一个invalid_argument
异常,然后被相应的catch块捕获并处理。
C++标准库提供了一组预定义的异常类,这些类都派生自std::exception
基类,用于处理程序中可能出现的各种错误情况。标准异常类定义在
、
等头文件中。
std::exception
what()
,返回一个描述异常的C风格字符串。逻辑错误(std::logic_error
)
std::exception
,表示程序逻辑错误。std::invalid_argument
:无效参数。std::domain_error
:参数超出定义域。std::length_error
:超出最大允许长度。std::out_of_range
:访问越界(如数组、字符串)。运行时错误(std::runtime_error
)
std::exception
,表示运行时错误。std::range_error
:计算结果超出有意义的值范围。std::overflow_error
:算术上溢。std::underflow_error
:算术下溢。std::system_error
:操作系统或底层API错误。其他异常类
std::bad_alloc
:内存分配失败(new
操作抛出)。std::bad_cast
:dynamic_cast
失败。std::bad_typeid
:typeid
操作符应用于空指针。#include
#include
#include
int main() {
try {
std::vector<int> v(5);
// 可能抛出 std::out_of_range
std::cout << v.at(10) << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "Out of range error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Standard exception: " << e.what() << std::endl;
}
return 0;
}
what()
方法)。std::exception
。在C++中,自定义异常类允许开发者创建特定于应用程序需求的异常类型。通过继承标准异常类(如std::exception
或其派生类),可以定义更符合业务逻辑的异常。
自定义异常类通常包含以下部分:
std::exception
。what()
方法:返回异常描述信息(const char*
类型)。#include
#include
class MyException : public std::exception {
private:
std::string message; // 自定义异常信息
public:
// 构造函数,接收异常描述
MyException(const std::string& msg) : message(msg) {}
// 重写what(),返回异常信息
const char* what() const noexcept override {
return message.c_str();
}
};
try {
throw MyException("自定义异常:数据无效");
} catch (const MyException& e) {
std::cerr << "捕获异常: " << e.what() << std::endl;
}
noexcept
修饰:what()
应标记为noexcept
,保证不抛出异常。const std::exception&
)以避免对象切片。what()
返回的字符串在异常对象生命周期内有效(如使用成员变量而非局部变量)。异常规范(Exception Specification)是 C++ 中用于指定函数可能抛出的异常类型的一种机制。它允许程序员在函数声明或定义中明确列出该函数可能抛出的异常类型,从而提供一种编译时的异常处理约束。
异常规范的语法形式如下:
return_type function_name(parameters) throw(type1, type2, ...);
其中:
throw(type1, type2, ...)
表示该函数可能抛出的异常类型列表。如果列表为空(即 throw()
),则表示该函数不会抛出任何异常。在 C++11 之前,异常规范被称为动态异常规范(Dynamic Exception Specification)。它的行为如下:
std::unexpected()
,默认行为是调用 std::terminate()
终止程序。noexcept
规范(C++11 及之后)C++11 引入了 noexcept
规范,取代了动态异常规范。noexcept
是一种更简单、更高效的异常规范形式:
noexcept
:表示函数不会抛出任何异常。noexcept(true)
:等价于 noexcept
。noexcept(false)
:表示函数可能抛出异常。语法示例:
void func() noexcept; // 不会抛出异常
void func2() noexcept(true); // 同上
void func3() noexcept(false); // 可能抛出异常
C++11 开始,动态异常规范(如 throw(type1, type2)
)被标记为弃用(deprecated),并在 C++17 中完全移除。推荐使用 noexcept
替代。
noexcept
是编译时检查,性能更高,且更易于优化。noexcept
但抛出了异常,程序会直接调用 std::terminate()
终止。// 动态异常规范(C++11 之前,已弃用)
void oldFunc() throw(int, const char*) {
throw 42; // 允许
// throw std::string("error"); // 会调用 std::unexpected()
}
// noexcept 规范(C++11 及之后)
void newFunc() noexcept {
// 不会抛出异常
}
void mayThrow() noexcept(false) {
throw std::runtime_error("error"); // 允许
}
异常处理是C++中处理错误的一种机制,但它在运行时可能会带来一定的性能开销。以下是关于异常处理性能的几个关键点:
throw
)是一个昂贵的操作,因为它需要:
catch
块:运行时需要遍历调用栈,检查每个函数的异常表。异常处理在错误发生时是强大的工具,但其性能开销主要集中在抛出和捕获阶段。在性能敏感的代码中,应谨慎使用异常,优先考虑其他错误处理机制(如错误码或断言)。
RTTI(Run-Time Type Information,运行时类型信息)是C++的一个特性,它允许程序在运行时获取对象的类型信息。RTTI主要通过两个运算符实现:typeid
和dynamic_cast
。
类型识别:
typeid
运算符可以获取对象的实际类型信息,返回一个std::type_info
对象,其中包含类型的名称等信息。安全类型转换:
dynamic_cast
运算符可以在运行时检查指针或引用是否可以安全地转换为目标类型。dynamic_cast
会返回nullptr
(对于指针)或抛出std::bad_cast
异常(对于引用)。多态支持:
typeid
示例:
#include
#include
class Base {
public:
virtual ~Base() {} // 必须有虚函数才能使用RTTI
};
class Derived : public Base {};
int main() {
Base* ptr = new Derived();
std::cout << typeid(*ptr).name() << std::endl; // 输出Derived的类型信息
delete ptr;
}
dynamic_cast
示例:
class Base {
public:
virtual ~Base() {}
};
class Derived : public Base {
public:
void derivedMethod() {}
};
int main() {
Base* ptr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(ptr);
if (derivedPtr) {
derivedPtr->derivedMethod(); // 安全调用
}
delete ptr;
}
性能开销:
必须有多态:
typeid
和dynamic_cast
。禁用RTTI:
-fno-rtti
选项禁用RTTI,以减小代码体积和提高性能。但在禁用后,typeid
和dynamic_cast
将无法使用。RTTI是C++中处理运行时类型识别和安全类型转换的重要工具,尤其在复杂的多态场景中非常有用。
typeid
是C++中的一个运算符,用于在运行时获取对象的类型信息。它是RTTI(Run-Time Type Identification,运行时类型识别)机制的一部分,定义在
头文件中。
#include
#include
int main() {
int i = 42;
std::cout << typeid(i).name() << std::endl; // 输出int类型的名称
return 0;
}
返回类型信息
typeid
运算符返回一个std::type_info
对象的引用,该对象包含类型的相关信息。
支持多态类型
当应用于多态类型(即有虚函数的类)时,typeid
可以返回对象实际类型的动态类型信息。
class Base {
public:
virtual ~Base() {}
};
class Derived : public Base {};
Base* ptr = new Derived();
std::cout << typeid(*ptr).name() << std::endl; // 输出Derived的类型名称
比较类型
通过std::type_info
的==
和!=
运算符,可以比较两个类型是否相同。
if (typeid(a) == typeid(b)) {
std::cout << "a和b的类型相同" << std::endl;
}
获取类型名称
通过type_info::name()
可以获取类型的名称,但返回的名称可能因编译器而异(通常是修饰过的名称)。
头文件依赖
使用typeid
需要包含
头文件。
非多态类型
对于非多态类型(没有虚函数的类),typeid
返回的是静态类型信息。
class NonPolymorphic {};
NonPolymorphic obj;
NonPolymorphic* ptr = &obj;
std::cout << typeid(*ptr).name() << std::endl; // 输出NonPolymorphic的类型名称
空指针异常
如果对空指针解引用并应用typeid
,会抛出std::bad_typeid
异常。
Base* ptr = nullptr;
try {
std::cout << typeid(*ptr).name() << std::endl; // 抛出std::bad_typeid
} catch (const std::bad_typeid& e) {
std::cerr << e.what() << std::endl;
}
typeid
常用于调试、日志记录或需要动态类型检查的场景,例如:
void process(Base* obj) {
if (typeid(*obj) == typeid(Derived)) {
std::cout << "处理Derived对象" << std::endl;
}
}
但更推荐使用dynamic_cast
和虚函数来实现多态行为,而不是直接依赖typeid
。
dynamic_cast
是C++中用于处理多态类型转换的运算符,主要用于在继承层次结构中进行安全的向下转型(downcasting)。
dynamic_cast<new_type>(expression)
std::bad_cast
异常class Base {
public:
virtual ~Base() {} // 必须有虚函数
};
class Derived : public Base {};
int main() {
Base* b = new Derived;
// 安全的向下转型
Derived* d = dynamic_cast<Derived*>(b);
if (d) {
// 转换成功
}
delete b;
return 0;
}
type_info
是 C++ 标准库
头文件中定义的一个类,用于在运行时获取类型信息。它通常与 typeid
运算符一起使用,用于查询对象的类型信息。
type_info
类删除了拷贝构造函数和赋值运算符,因此不能直接复制或赋值 type_info
对象。type_info
类的构造函数是私有的,只能通过 typeid
运算符获取其实例。typeid
用于多态类型(即有虚函数的类)时,返回的是动态类型(实际对象的类型);否则返回静态类型(编译时类型)。name()
:返回一个表示类型名称的字符串。名称的格式由编译器决定,通常是可读性较差的编码形式(如 "int"
、"class A"
)。const char* typeName = typeid(int).name();
operator==
和 operator!=
:用于比较两个 type_info
对象是否表示相同的类型。if (typeid(a) == typeid(b)) { /* 类型相同 */ }
before()
:返回一个 bool
值,表示当前类型在某种内部排序中是否位于另一个类型之前(通常用于实现 type_index
)。#include
#include
class Base { public: virtual ~Base() {} };
class Derived : public Base {};
int main() {
int i;
double d;
Base* ptr = new Derived();
std::cout << "i 的类型: " << typeid(i).name() << std::endl;
std::cout << "d 的类型: " << typeid(d).name() << std::endl;
std::cout << "ptr 的静态类型: " << typeid(ptr).name() << std::endl;
std::cout << "ptr 的动态类型: " << typeid(*ptr).name() << std::endl;
delete ptr;
return 0;
}
typeid
和 type_info
主要用于调试或类型检查,不应用于日常编程中的类型分发(应使用虚函数或模板)。name()
返回的字符串格式是编译器相关的,可能需要进行解码(如 GCC 的 c++filt
工具)。typeid
的操作数是解引用空指针,会抛出 std::bad_typeid
异常。RTTI(Run-Time Type Information)是C++中的一个特性,允许程序在运行时获取对象的类型信息。主要通过typeid
和dynamic_cast
来实现。
仅适用于多态类型
RTTI只能用于带有虚函数的类(多态类型)。对于非多态类型,typeid
和dynamic_cast
的行为可能不符合预期。
编译器支持
某些编译器(如嵌入式系统编译器)可能默认禁用RTTI以节省空间或提高性能。需要手动启用。
类型安全性
dynamic_cast
在失败时返回nullptr
(指针)或抛出std::bad_cast
异常(引用),但过度依赖它可能导致代码脆弱。
跨模块问题
在动态链接库(DLL)或共享库中,RTTI可能因类型信息不一致而引发问题。
空间开销
RTTI需要存储类型信息(如类型名称和继承关系),这会增加二进制文件的大小。
时间开销
dynamic_cast
需要在运行时遍历继承层次结构,复杂度为O(n)(n为继承深度)。typeid
通常比dynamic_cast
快,但仍需访问类型信息。优化限制
启用RTTI可能阻止某些编译器优化(如去虚拟化),因为类型必须在运行时确定。