18.1 异常处理
异常处理是C++中处理错误和异常情况的一种机制。通过异常处理机制,可以将错误处理代码与正常代码分离,增强代码的可读性和可维护性。在本节中,我们将详细探讨异常处理的各个方面,包括抛出异常、捕获异常、函数try语句块与构造函数、noexcept
异常说明以及异常类层次。
18.1.1 抛出异常
当程序运行过程中出现异常情况时,可以通过throw
语句抛出异常。这将触发异常处理机制,程序控制流会从当前执行点转移到最近的catch
块。抛出异常的过程涉及栈展开、对象销毁和异常对象的传递。
栈展开
栈展开(stack unwinding)是异常处理的一部分,当异常被抛出时,程序会沿着调用栈向上搜索,直到找到一个匹配的catch
块。在这个过程中,所有局部对象会被销毁。
#include <iostream>
#include <stdexcept>
class Resource {
public:
Resource() { std::cout << "Resource acquired" << std::endl; }
~Resource() { std::cout << "Resource released" << std::endl; }
};
void might_go_wrong() {
Resource res;
throw std::runtime_error("Error occurred");
}
int main() {
try {
might_go_wrong();
} catch (const std::runtime_error& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
在这个示例中,当might_go_wrong
函数抛出异常时,局部对象res
的析构函数会在栈展开过程中自动调用,确保资源被正确释放。
异常对象
当抛出异常时,会创建一个异常对象,异常对象可以是任何类型的对象。标准库中提供了许多异常类,可以直接使用这些类来抛出异常。
#include <iostream>
#include <stdexcept>
void might_go_wrong() {
throw std::runtime_error("Error occurred");
}
int main() {
try {
might_go_wrong();
} catch (const std::runtime_error& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
18.1.2 捕获异常
捕获异常是通过try
和catch
语句来实现的。try
块包含可能抛出异常的代码,catch
块用来捕获并处理这些异常。
#include <iostream>
#include <stdexcept>
void might_go_wrong() {
throw std::runtime_error("Error occurred");
}
int main() {
try {
might_go_wrong();
} catch (const std::runtime_error& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught general exception: " << e.what() << std::endl;
}
return 0;
}
在这个示例中,我们首先尝试捕获std::runtime_error
异常,如果没有捕获到,再尝试捕获所有继承自std::exception
的异常。
18.1.3 函数try语句块与构造函数
在C++中,构造函数初始化列表中发生的异常无法直接捕获。为了处理这种情况,可以使用函数try语句块。
#include <iostream>
#include <stdexcept>
class MyClass {
public:
MyClass() try : resource(new int[1000000]) {
// 可能引发异常的初始化代码
throw std::runtime_error("Initialization failed");
} catch (...) {
delete[] resource;
std::cerr << "Exception caught in constructor" << std::endl;
throw; // 重新抛出异常
}
private:
int* resource;
};
int main() {
try {
MyClass obj;
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
在这个示例中,函数try语句块用于捕获构造函数初始化列表中发生的异常,并确保资源在异常情况下得到正确释放。
18.1.4 noexcept
异常说明
noexcept
说明符用于指示函数是否抛出异常。它有助于编译器进行优化,并提高代码的安全性和可维护性。
违反异常说明
如果函数标记为noexcept
,但在运行时抛出了异常,程序会调用std::terminate
终止。
#include <iostream>
void no_throw() noexcept {
throw std::runtime_error("Error occurred"); // 这会导致程序终止
}
int main() {
try {
no_throw();
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
noexcept
运算符
noexcept
运算符用于检查表达式是否抛出异常。
#include <iostream>
void might_throw() {
throw std::runtime_error("Error occurred");
}
int main() {
std::cout << "might_throw is noexcept: " << noexcept(might_throw()) << std::endl;
return 0;
}
18.1.5 异常类层次
C++标准库提供了一组异常类,用于表示各种常见的错误情况。所有标准异常类都继承自std::exception
。
std::exception
:所有标准异常类的基类。std::logic_error
:表示逻辑错误,如非法参数。
-
std::invalid_argument
std::domain_error
std::length_error
std::out_of_range
std::runtime_error
:表示运行时错误,如溢出错误。
-
std::range_error
std::overflow_error
std::underflow_error
std::bad_alloc
:表示内存分配失败。std::bad_cast
:表示类型转换失败。
示例代码
#include <iostream>
#include <stdexcept>
void use_standard_exceptions() {
throw std::logic_error("Logical error occurred");
}
int main() {
try {
use_standard_exceptions();
} catch (const std::logic_error& e) {
std::cout << "Logic error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "General exception: " << e.what() << std::endl;
}
return 0;
}
在这个示例中,我们抛出了一个std::logic_error
异常,并在main
函数中捕获和处理它。
重点与难点分析
重点:
- 抛出异常:理解异常抛出的过程,包括栈展开、对象销毁和异常对象的传递。
- 捕获异常:掌握如何使用
try
和catch
语句捕获和处理异常。 - 函数try语句块与构造函数:了解如何使用函数try语句块处理构造函数中的异常。
noexcept
异常说明:理解noexcept
说明符的作用及其在函数、指针和虚函数中的应用。- 异常类层次:熟悉标准库中的异常类层次结构,了解各种常见的标准异常类。
难点:
- 栈展开和资源管理:确保在异常抛出和栈展开过程中,资源能够被正确释放。
- 自定义异常类的设计:正确设计和实现自定义异常类,确保其提供有用的错误信息。
noexcept
的正确使用:合理使用noexcept
说明符,确保代码的安全性和可维护性。
练习题解析
- 练习18.1:编写一个程序,演示如何使用
throw
、try
和catch
进行基本的异常处理。
-
- 示例代码:
#include <iostream>
#include <stdexcept>
void function_that_throws() {
throw std::runtime_error("Error occurred");
}
int main() {
try {
function_that_throws();
} catch (const std::runtime_error& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
- 练习18.2:定义一个自定义异常类,并在函数中抛出该异常,在
main
函数中捕获并处理它。
-
- 示例代码:
#include <iostream>
#include <exception>
class MyException : public std::exception {
public:
const char* what() const noexcept override {
return "My custom exception";
}
};
void function_that_throws() {
throw MyException();
}
int main() {
try {
function_that_throws();
} catch (const MyException& e) {
std::cout << "Caught custom exception: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught general exception: " << e.what() << std::endl;
}
return 0;
}
- 练习18.3:编写一个程序,演示栈展开过程中局部对象的析构函数被调用的过程。
-
- 示例代码:
#include <iostream>
#include <stdexcept>
class Resource {
public:
Resource() { std::cout << "Resource acquired" << std::endl; }
~Resource() { std::cout << "Resource released" << std::endl; }
};
void function_that_throws() {
Resource res;
throw std::runtime_error("Error occurred");
}
int main() {
try {
function_that_throws();
} catch (const std::runtime_error& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
总结与提高
本节总结:
- 掌握了异常处理的基本概念和使用方法,包括
throw
、try
和catch
关键字的用法。 - 理解了栈展开和资源管理的机制,确保在异常情况下资源能够被正确释放。
- 学会了定义和使用自定义异常类,能够提供自定义的错误信息。
- 熟悉了函数try语句块的使用方法,能够在构造函数中处理异常。
- 理解了
noexcept
说明符的作用及其在函数、指针和虚函数中的应用。 - 熟悉了标准库中的异常类层次结构,了解各种常见的标准异常类。
提高建议:
- 多练习异常处理:通过编写更多的异常处理相关代码,熟悉异常处理的基本流程和高级特性,掌握异常处理的最佳实践。
- 深入理解标准异常类:通过阅读相关文档和书籍,深入理解C++标准库中的异常类及其用途,确保在实际项目中能够正确选择和使用异常类。
- 优化异常处理代码:在实际项目中,合理设计和实现异常处理代码,提高代码的健壮性和可维护性,确保程序在异常情况下能够正确运行。
18.2 命名空间
命名空间(namespace)是C++中用于组织代码和防止命名冲突的一种机制。在大型程序中,命名空间可以有效地组织代码并提高代码的可读性和可维护性。
18.2.1 命名空间定义
命名空间通过关键字namespace
引入,用于定义一个作用域,其中可以包含变量、函数、类等声明。
namespace MyNamespace {
int myVariable = 42;
void myFunction() {
std::cout << "Hello from MyNamespace" << std::endl;
}
}
命名空间与作用域
命名空间提供了一种将标识符封装在特定作用域中的机制,以避免命名冲突。
#include <iostream>
namespace FirstNamespace {
void display() {
std::cout << "FirstNamespace" << std::endl;
}
}
namespace SecondNamespace {
void display() {
std::cout << "SecondNamespace" << std::endl;
}
}
int main() {
FirstNamespace::display();
SecondNamespace::display();
return 0;
}
命名空间的使用场景
命名空间适用于以下场景:
- 防止命名冲突:不同模块的同名实体不会相互冲突。
- 组织代码:将相关功能封装在一起,便于管理。
全局命名空间
全局命名空间是程序默认的命名空间,所有不在命名空间内声明的标识符都属于全局命名空间。
#include <iostream>
int globalVariable = 10;
int main() {
std::cout << globalVariable << std::endl;
return 0;
}
嵌套命名空间
命名空间可以嵌套,以进一步组织和划分代码。
#include <iostream>
namespace OuterNamespace {
namespace InnerNamespace {
void display() {
std::cout << "InnerNamespace" << std::endl;
}
}
}
int main() {
OuterNamespace::InnerNamespace::display();
return 0;
}
内联命名空间
内联命名空间允许在外部命名空间中直接访问内联命名空间中的成员。
#include <iostream>
namespace OuterNamespace {
inline namespace InnerNamespace {
void display() {
std::cout << "InnerNamespace" << std::endl;
}
}
}
int main() {
OuterNamespace::display();
return 0;
}
匿名命名空间
匿名命名空间用于定义只在当前文件中可见的实体,类似于static
关键字。
#include <iostream>
namespace {
int localVariable = 100;
void localFunction() {
std::cout << "Anonymous namespace" << std::endl;
}
}
int main() {
std::cout << localVariable << std::endl;
localFunction();
return 0;
}
18.2.2 使用命名空间成员
命名空间的别名
为了简化对深层次嵌套命名空间的访问,可以使用命名空间别名。
#include <iostream>
namespace Outer {
namespace Inner {
void display() {
std::cout << "InnerNamespace" << std::endl;
}
}
}
namespace Alias = Outer::Inner;
int main() {
Alias::display();
return 0;
}
using声明
using
声明将特定命名空间的成员引入当前作用域。
#include <iostream>
namespace MyNamespace {
void display() {
std::cout << "MyNamespace" << std::endl;
}
}
int main() {
using MyNamespace::display;
display();
return 0;
}
using指示
using
指示将整个命名空间引入当前作用域。
#include <iostream>
namespace MyNamespace {
void display() {
std::cout << "MyNamespace" << std::endl;
}
}
int main() {
using namespace MyNamespace;
display();
return 0;
}
using指示与作用域
using
指示的作用域仅限于其所在的块或全局作用域。
#include <iostream>
namespace MyNamespace {
void display() {
std::cout << "MyNamespace" << std::endl;
}
}
void func() {
using namespace MyNamespace;
display();
}
int main() {
// display(); // 错误:display在此作用域不可见
func();
return 0;
}
头文件与using声明或指示
在头文件中使用using
声明或指示可能导致命名冲突,通常应避免这种做法。
// myheader.h
namespace MyNamespace {
void display();
}
// mysource.cpp
#include "myheader.h"
int main() {
MyNamespace::display();
return 0;
}
18.2.3 类、命名空间与作用域
实参相关的查找与类类型形参
在调用函数时,编译器会在实参所属的命名空间中查找对应的函数。
#include <iostream>
namespace MyNamespace {
class MyClass {};
void func(MyClass) {
std::cout << "MyNamespace::func" << std::endl;
}
}
int main() {
MyNamespace::MyClass obj;
func(obj); // 调用MyNamespace::func
return 0;
}
友元声明与实参相关的查找
友元函数的查找规则与普通函数有所不同,友元声明会影响函数查找。
#include <iostream>
namespace MyNamespace {
class MyClass {
friend void display(const MyClass&);
};
void display(const MyClass&) {
std::cout << "Friend function" << std::endl;
}
}
int main() {
MyNamespace::MyClass obj;
display(obj); // 调用MyNamespace::display
return 0;
}
18.2.4 重载与命名空间
实参相关的查找与重载
函数重载时,编译器会在实参所属的命名空间中查找对应的函数。
#include <iostream>
namespace MyNamespace {
void func(int) {
std::cout << "int version" << std::endl;
}
void func(double) {
std::cout << "double version" << std::endl;
}
}
int main() {
MyNamespace::func(10); // 调用int版本
MyNamespace::func(10.5); // 调用double版本
return 0;
}
重载与using声明
using
声明可以引入特定的重载函数,但需要显式指定每个重载版本。
#include <iostream>
namespace MyNamespace {
void func(int) {
std::cout << "int version" << std::endl;
}
void func(double) {
std::cout << "double version" << std::endl;
}
}
using MyNamespace::func;
int main() {
func(10); // 调用int版本
func(10.5); // 调用double版本
return 0;
}
重载与using指示
using
指示可以引入整个命名空间,包括所有重载函数。
#include <iostream>
namespace MyNamespace {
void func(int) {
std::cout << "int version" << std::endl;
}
void func(double) {
std::cout << "double version" << std::endl;
}
}
using namespace MyNamespace;
int main() {
func(10); // 调用int版本
func(10.5); // 调用double版本
return 0;
}
跨越多个using指示的重载
当多个命名空间通过using
指示引入时,重载解析可能涉及多个命名空间。
#include <iostream>
namespace FirstNamespace {
void func(int) {
std::cout << "FirstNamespace::int version" << std::endl;
}
}
namespace SecondNamespace {
void func(double) {
std::cout << "SecondNamespace::double version" << std::endl;
}
}
using namespace FirstNamespace;
using namespace SecondNamespace;
int main() {
func(10); // 调用FirstNamespace::func(int)
func(10.5); // 调用SecondNamespace::func(double)
return 0;
}
重点与难点分析
重点:
- 命名空间的定义与使用:理解命名空间的基本定义和作用,掌握全局、嵌套、内联和匿名命名空间的用法。
- 使用命名空间成员:掌握命名空间别名、
using
声明和using
指示的用法及其作用域。 - 类、命名空间与作用域:理解类和命名空间的作用域规则,掌握实参相关的查找、
std::move
和std::forward
的查找规则,以及友元声明的查找规则。 - 重载与命名空间:掌握函数重载在不同命名空间中的查找规则,以及
using
声明和指示对重载的影响。
难点:
- 命名空间管理:在大型项目中有效管理命名空间,避免命名冲突。
- 跨命名空间重载解析:理解跨多个命名空间的重载解析规则,确保函数调用的正确性。
练习题解析
- 练习18.7:编写一个程序,定义两个嵌套命名空间,并通过命名空间别名简化访问。
-
- 示例代码:
#include <iostream>
namespace Outer {
namespace Inner {
void display() {
std::cout << "InnerNamespace" << std::endl;
}
}
}
namespace Alias = Outer::Inner;
int main() {
Alias::display();
return 0;
}
- 练习18.8:编写一个程序,演示如何在头文件中声明命名空间成员,并在源文件中定义和使用这些成员。
-
- 示例代码:
// myheader.h
namespace MyNamespace {
void display();
}
// mysource.cpp
#include "myheader.h"
#include <iostream>
void MyNamespace::display() {
std::cout << "MyNamespace" << std::endl;
}
int main() {
MyNamespace::display();
return 0;
}
总结与提高
本节总结:
- 掌握了命名空间的定义和使用方法,能够有效地组织和管理代码。
- 理解了命名空间别名、
using
声明和using
指示的用法,能够简化命名空间成员的访问。 - 熟悉了类、命名空间与作用域的查找规则,能够正确处理实参相关的查找、
std::move
和std::forward
的查找规则,以及友元声明的查找规则。 - 掌握了函数重载在不同命名空间中的查找规则,能够正确处理跨命名空间的重载解析。
提高建议:
- 多练习命名空间的使用:通过编写更多的命名空间相关代码,熟悉命名空间的定义和使用方法,掌握命名空间的最佳实践。
- 深入理解命名空间查找规则:通过阅读相关文档和书籍,深入理解命名空间的查找规则,确保函数调用的正确性。
- 优化命名空间管理:在实际项目中,合理设计和组织命名空间,提高代码的可读性和可维护性,避免命名冲突。
18.3 多重继承与虚继承
多重继承和虚继承是C++中高级的面向对象编程技术,用于解决复杂的继承关系和钻石继承问题。在本节中,我们将深入探讨多重继承和虚继承的概念、使用方法及其实际应用。
18.3.1 多重继承
多重继承允许一个类从多个基类继承成员。虽然这提供了很大的灵活性,但也带来了一些复杂性和潜在的问题。
示例代码
#include <iostream>
class Base1 {
public:
void show() {
std::cout << "Base1 show" << std::endl;
}
};
class Base2 {
public:
void display() {
std::cout << "Base2 display" << std::endl;
}
};
class Derived : public Base1, public Base2 {};
int main() {
Derived d;
d.show(); // 调用Base1的show
d.display(); // 调用Base2的display
return 0;
}
在这个示例中,类Derived
从两个基类Base1
和Base2
继承了成员函数show
和display
,可以通过派生类对象调用这些函数。
18.3.2 多重继承的复杂性
多重继承带来的一个主要复杂性是名称冲突。当多个基类中有同名成员时,需要明确指定要访问的基类成员。
示例代码
#include <iostream>
class Base1 {
public:
void show() {
std::cout << "Base1 show" << std::endl;
}
};
class Base2 {
public:
void show() {
std::cout << "Base2 show" << std::endl;
}
};
class Derived : public Base1, public Base2 {};
int main() {
Derived d;
// d.show(); // 错误:show在Base1和Base2中都存在,导致二义性
d.Base1::show(); // 调用Base1的show
d.Base2::show(); // 调用Base2的show
return 0;
}
在这个示例中,Derived
类从Base1
和Base2
继承了同名的show
函数,必须通过基类作用域限定符明确指定要调用的函数。
18.3.3 虚继承
虚继承是一种解决多重继承中“钻石问题”的方法。钻石问题是指多个路径继承同一个基类时,派生类会有多个基类子对象。虚继承可以确保只有一个共享的基类子对象。
示例代码
#include <iostream>
class Base {
public:
void show() {
std::cout << "Base show" << std::endl;
}
};
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class Derived3 : public Derived1, public Derived2 {};
int main() {
Derived3 d;
d.show(); // 调用唯一的Base子对象的show
return 0;
}
在这个示例中,Derived1
和Derived2
都虚继承自Base
,因此Derived3
类中只有一个Base
子对象。
18.3.4 虚继承的初始化
由于虚基类在派生类中只有一个子对象,虚基类的初始化由最远派生类负责。
示例代码
#include <iostream>
class Base {
public:
Base() { std::cout << "Base constructor" << std::endl; }
};
class Derived1 : virtual public Base {
public:
Derived1() { std::cout << "Derived1 constructor" << std::endl; }
};
class Derived2 : virtual public Base {
public:
Derived2() { std::cout << "Derived2 constructor" << std::endl; }
};
class Derived3 : public Derived1, public Derived2 {
public:
Derived3() : Base() { std::cout << "Derived3 constructor" << std::endl; }
};
int main() {
Derived3 d;
return 0;
}
在这个示例中,Base
的构造函数只调用一次,由Derived3
负责初始化。
18.3.5 多重继承和虚继承的实际应用
多重继承和虚继承在实际应用中需要慎重使用,特别是在设计复杂的类层次结构时。合理使用可以提高代码的灵活性和重用性,但滥用可能导致难以维护和调试的代码。
示例代码
#include <iostream>
class Printer {
public:
void print() {
std::cout << "Printing" << std::endl;
}
};
class Scanner {
public:
void scan() {
std::cout << "Scanning" << std::endl;
}
};
class Copier : public Printer, public Scanner {};
int main() {
Copier c;
c.print();
c.scan();
return 0;
}
在这个示例中,Copier
类通过多重继承同时具备打印和扫描功能,展示了多重继承在实际应用中的一个简单例子。
重点与难点分析
重点:
- 多重继承的基本概念:理解多重继承的定义和使用方法,掌握如何从多个基类继承成员。
- 虚继承的基本概念:理解虚继承的定义和使用方法,掌握如何解决多重继承中的钻石问题。
- 多重继承和虚继承的初始化:了解虚继承的初始化规则,理解最远派生类负责虚基类的初始化。
难点:
- 名称冲突的解决:在多重继承中,正确处理基类中同名成员的名称冲突问题。
- 虚继承的复杂性:理解虚继承带来的复杂性,特别是在初始化和资源管理方面。
练习题解析
- 练习18.10:编写一个程序,演示多重继承中名称冲突的解决方法。
-
- 示例代码:
#include <iostream>
class Base1 {
public:
void show() {
std::cout << "Base1 show" << std::endl;
}
};
class Base2 {
public:
void show() {
std::cout << "Base2 show" << std::endl;
}
};
class Derived : public Base1, public Base2 {};
int main() {
Derived d;
d.Base1::show(); // 调用Base1的show
d.Base2::show(); // 调用Base2的show
return 0;
}
- 练习18.11:编写一个程序,演示虚继承的基本用法。
-
- 示例代码:
#include <iostream>
class Base {
public:
void show() {
std::cout << "Base show" << std::endl;
}
};
class Derived1 : virtual public Base {};
class Derived2 : virtual public Base {};
class Derived3 : public Derived1, public Derived2 {};
int main() {
Derived3 d;
d.show(); // 调用唯一的Base子对象的show
return 0;
}
- 练习18.12:编写一个程序,演示虚继承的初始化规则。
-
- 示例代码:
#include <iostream>
class Base {
public:
Base() { std::cout << "Base constructor" << std::endl; }
};
class Derived1 : virtual public Base {
public:
Derived1() { std::cout << "Derived1 constructor" << std::endl; }
};
class Derived2 : virtual public Base {
public:
Derived2() { std::cout << "Derived2 constructor" << std::endl; }
};
class Derived3 : public Derived1, public Derived2 {
public:
Derived3() : Base() { std::cout << "Derived3 constructor" << std::endl; }
};
int main() {
Derived3 d;
return 0;
}
总结与提高
本节总结:
- 掌握了多重继承的基本概念和使用方法,理解了多重继承带来的名称冲突问题及其解决方法。
- 理解了虚继承的基本概念和使用方法,掌握了如何通过虚继承解决多重继承中的钻石问题。
- 熟悉了虚继承的初始化规则,理解了最远派生类负责虚基类初始化的机制。
- 了解了多重继承和虚继承在实际应用中的使用场景和注意事项。
提高建议:
- 多练习多重继承和虚继承:通过编写更多的多重继承和虚继承相关代码,熟悉这些高级继承机制的使用方法,掌握它们的最佳实践。
- 深入理解虚继承的初始化规则:通过阅读相关文档和书籍,深入理解虚继承的初始化规则,确保在实际项目中能够正确实现和使用虚继承。
- 优化类层次结构设计:在实际项目中,合理设计和优化类层次结构,提高代码的可读性和可维护性,避免多重继承和虚继承带来的复杂性和潜在问题。
关于虚继承的深入分析,请进一步阅读以下的文章:
深度解读《深度探索C++对象模型》之C++虚继承的实现分析和效率评测(一)-CSDN博客
深度解读《深度探索C++对象模型》之C++虚继承的实现分析和效率评测(二)-CSDN博客
关于多重继承的深入分析,请进一步阅读以下的文章:
深度解读《深度探索C++对象模型》之C++数据成员的存取效率分析(三)-CSDN博客
关于C++的命名空间更详细的讨论,请阅读以下文章:
本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“iShare爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。