命名空间

命名空间(namespace)是C++中用于解决名称冲突问题的重要机制。

命名空间的作用:

  1. 避免命名冲突:命名空间提供了一种将全局作用域划分成更小的作用域的机制,用于避免不同的代码中可能发生的命名冲突问题;

  2. 组织代码:将相关的实体放到同一个命名空间;

  3. 版本控制:不同版本的代码放到不同的命名空间中;

    下面引用当前流行的命名空间使用指导原则

    1. 提倡在已命名的名称空间中定义变量,而不是直接定义外部全局变量或者静态全局变量。

    2. 如果开发了一个函数库或者类库,提倡将其放在一个命名空间中。

    3. 对于using 声明,首先将其作用域设置为局部而不是全局。

    4. 包含头文件的顺序可能会影响程序的行为,如果非要使用using编译指令,建议放在所有#include预编译指令后。

命名空间的基本概念:

命名空间提供了一种将全局作用域划分为不同区域的方法,防止不同库之间的名称冲突。

命名空间的访问方式

直接使用完全限定名称

使用using声明

使用using指令

嵌套命名空间

命名空间可以嵌套定义:

命名空间别名

可以为长命名空间名称创建别名:

匿名命名空间

匿名命名空间中的成员具有文件作用域,类似于static:

解析规则

当使用一个名称时,C++按以下顺序查找:

  1. 当前作用域

  2. 外层作用域

  3. 使用using声明引入的名称

  4. 使用using指令引入的命名空间

  5. 全局作用域

命名空间的跨模块调用:

命名空间中的实体跨模块调用时,要在新的源文件中再次定义同名的命名空间,在其中通过extern引入实体。

进行联合编译时,这两次定义被认为是同一个命名空间。

const关键字

const关键字是C++中非常重要的一个关键字,它用于声明常量,保证数据不被修改。

基本用法

定义常量

与指针结合使用

在函数中的应用

常量参数

常量引用

在类中的应用

常量成员变量

常量成员函数

常量对象

const的高级用法

常量表达式(C++11)

成员函数重载

const的好处

  1. 增强代码安全性:防止意外修改数据

  2. 提高代码可读性:明确表明哪些数据不应被修改

  3. 编译器优化:编译器可以对常量进行更多优化

  4. 接口设计:明确函数是否会修改传入的参数

const常量和宏定义常量的区别

const常量和宏定义常量(#define)是C++中两种定义常量的方式,它们有以下几个重要区别:

1.类型检查

2.内存分配

3.作用域

4.调试

5.安全性

6.指针操作

7.复杂常量定义

8.函数内联

在现代C++中,推荐使用const常量而非宏定义:

new/delete表达式

new和delete是C++中用于动态内存管理的操作符,它们允许程序在运行时分配和释放内存。

new表达式

基本语法

分配单个对象

分配数组

分配对象

delete表达式

基本语法

注意事项

new/delete的工作原理

new的执行过程

  1. 调用operator new分配内存

  2. 在分配的内存上调用构造函数

  3. 返回对象的指针

delete的执行过程

  1. 调用对象的析构函数

  2. 调用operator delete释放内存

内存分配失败处理

异常处理

nothrow形式

自定义内存管理

重载全局new/delete

重载类的new/delete

常见问题

内存泄漏

悬挂指针

###

避免出现问题,使用智能指针:

new/delete与malloc/free的区别

  1. new/delete会调用构造函数和析构函数,malloc/free不会

  2. new返回正确类型的指针,malloc返回void*

  3. new/delete可以被重载,malloc/free不能

  4. 内存分配失败时,new抛出异常,malloc返回NULL

  5. malloc/free是库函数,new/delete是表达式,后两者使用时不是函数的写法

  6. malloc申请的空间不会进行初始化,获取到的空间是有脏数据的,但new表达式申请空间时可以直接初始化

  7. malloc的参数是字节数,new表达式不需要传递字节数,会根据相应类型自动获取空间大小

引用

引用是已定义变量的别名,必须在定义时初始化,初始化后不能改变引用的对象。

引用的特点

引用的主要用途

函数参数

函数返回值

常量引用

引用与指针的区别

  1. 必须初始化:引用必须在定义时初始化,指针可以稍后初始化

  2. 不能改变引用对象:引用一旦初始化,不能改变引用的对象;指针可以随时改变指向

  3. 没有空引用:引用必须指向有效对象;指针可以为nullptr

  4. 使用方式:引用使用时与普通变量相同;指针需要解引用操作

  5. 内存占用:引用不占用额外内存;指针是一个变量,占用内存

右值引用(C++11)

C++11引入了右值引用,用于支持移动语义和完美转发。

移动语义

完美转发

注意事项

不要返回局部变量的引用

引用作为类成员

引用的本质:引用在底层实现上通常是通过指针实现的,但在语法层面上提供了更安全、更方便的使用方式。

强制转换

C++提供了多种类型转换机制,比传统C语言的类型转换更加安全和明确。

static_cast

static_cast 是最常用的类型转换操作符,主要用于:

dynamic_cast

dynamic_cast 主要用于有继承关系的类指针或引用之间的转换,特点是:

const_cast

const_cast 用于移除或添加const/volatile限定符:

reinterpret_cast

reinterpret_cast 是最危险的转换操作符:

传统C风格转换

C++仍然支持C风格的类型转换,但不推荐使用:

各种转换的比较

转换类型安全性运行时检查主要用途
static_cast中等基本类型转换,有继承关系的类转换
dynamic_cast有继承关系的类转换,运行时类型识别
const_cast移除或添加const/volatile限定符
reinterpret_cast极低任意指针类型之间的转换
C风格转换不推荐使用

函数重载

函数重载是C++中一个重要的特性,它允许在同一作用域中定义多个同名函数,只要它们的参数列表不同。

函数重载允许同一个函数名有多个不同的实现,根据调用时提供的参数类型和数量来决定调用哪个函数。

函数重载的规则

必须有不同的参数列表

函数重载必须在以下至少一个方面有所不同:

仅返回类型不同不构成重载

const修饰符可以构成重载

对于成员函数,const修饰符也可以构成重载:

解析过程

编译器如何选择调用哪个重载函数:

  1. 精确匹配:寻找参数类型完全匹配的函数

  2. 提升转换:如char到int,float到double的转换

  3. 标准转换:如int到double,派生类到基类的转换

  4. 用户定义的转换:通过转换构造函数或转换运算符

  5. 可变参数函数:如果前面都没匹配到

函数重载的实际应用

标准库中的重载

构造函数重载

运算符重载

函数重载的注意事项

默认参数与重载

默认参数可能导致调用歧义:

函数重载与类型转换

当多个重载函数都可以通过类型转换匹配时,可能导致歧义:

重载与作用域

重载函数必须在同一作用域内:

函数重载的作用

  1. 提高代码可读性:使用相同的函数名表示相似的操作

  2. 简化接口:不需要为相似功能创建不同的函数名

  3. 类型安全:编译器会选择最匹配的函数

  4. 便于维护:功能相似的函数集中在一起

函数重载与模板

函数重载和函数模板可以结合使用:

当调用print函数时,编译器会优先选择具体函数,如果没有匹配的具体函数,才会实例化模板。

函数默认参数

默认参数是指在函数声明时为形参提供的默认值,当函数调用时如果没有提供该参数的值,则使用默认值。

在这个例子中,times参数有默认值1,调用时可以省略该参数。

使用规则

1.默认参数必须从右向左提供

2.默认参数只能在声明或定义中指定一次

3.默认参数可以是常量、全局变量或函数调用

函数调用

使用默认参数的函数可以通过以下方式调用:

默认参数与函数重载

默认参数和函数重载可能导致调用歧义:

类中的默认参数

在类中,成员函数也可以使用默认参数:

默认参数的计算时机

默认参数在函数调用时计算,而不是在函数定义时:

默认参数的优缺点

优点:

缺点:

bool类型

bool类型是C++中表示布尔值的基本数据类型,用于表示逻辑值:真(true)或假(false)。

bool类型在C++98标准中被引入,是C++的基本数据类型之一。

内存占用

隐式类型转换

1.其他类型转换为bool

2.bool转换为其他类型

在条件语句中的应用

bool类型最常用于条件语句中:

逻辑运算

bool类型可以进行逻辑运算:

与C语言的区别

inline函数

inline函数(内联函数)是C++中一种特殊的函数类型,它的主要目的是通过在调用点直接插入函数代码来避免函数调用的开销。

内联函数是一种对编译器的请求,希望编译器将函数调用替换为函数体代码,从而避免函数调用的开销。

inline函数的特点

1.性能优化

2.只是对编译器的建议

3.定义通常放在头文件中

使用场景

适合内联的函数

不适合内联的函数

隐式内联

类定义中直接定义的成员函数会被隐式地视为内联函数:

inline与宏的比较

内联函数相比宏定义有以下优势:

特性内联函数 (inline)宏 (#define)
类型安全提供类型安全,编译器进行类型检查没有类型检查,可能产生不匹配的错误
编译期替换编译器决定是否内联(有优化机制)预处理器简单文本替换
代码可读性和调试性支持断点调试,可读性和普通函数相似调试困难,无法跟踪宏的展开过程
副作用参数只求值一次,不会有多次求值副作用参数会多次求值,可能导致副作用
代码膨胀函数被多次内联可能导致代码膨胀频繁替换也会导致代码膨胀
灵活性适用于明确类型的函数可以处理不同类型的参数
性能小型函数可以避免函数调用开销无函数调用开销

注意事项

内联函数的限制

内联与链接

过度使用的问题

异常处理

异常处理是C++中处理程序运行时错误的一种机制,它允许程序在遇到错误时以结构化的方式响应。

异常处理包含三个主要部分:

异常的抛出与捕获

抛出异常

可以抛出任何类型的异常:

捕获异常

使用catch块捕获异常:

标准异常类

C++标准库提供了一系列异常类,都派生自std::exception基类:

常用的标准异常类:

自定义异常类

可以通过继承std::exception创建自定义异常类:

异常规范(Exception Specification)

C++11之前可以使用异常规范指定函数可能抛出的异常类型:

C++11引入了noexcept说明符,表示函数不会抛出异常:

示例:完整的异常处理

内存布局

C++程序的内存空间通常分为以下几个区域:

内存布局

Note

因为编译器的优化, 局部变量的地址分配通常是:

  • 后创建的变量分配高地址

  • 先创建的变量分配低地址

C风格字符串

C++支持两种主要的字符串表示方式:C风格字符串和C++的std::string类。

C风格字符串是从C语言继承而来的字符串表示方式,它本质上是一个字符数组,以空字符'\0'结尾。

这里的str数组实际包含6个字符,最后一个是不可见的空字符'\0',用于标记字符串的结束。

声明和初始化

字符数组

字符指针

操作函数

C风格字符串的操作主要通过<cstring>(C++中)或<string.h>(C中)头文件中的函数实现。

字符串长度

字符串复制

字符串连接

字符串比较

字符串查找

内存管理

栈上分配

堆上分配

常见问题

缓冲区溢出

忘记结束符

字符串常量修改

C风格字符串与std::string的转换

C风格字符串转std::string

std::string转C风格字符串