0%

cpp语言核心

阅读本文之前,可先参考c 语言核心

c++相对于 c 语言,主要是增加了面向对象和模板编程

c++中的实体

C++ 程序中的实体包括:值、对象、引用、 结构化绑定 (C++17 起)、函数、枚举项、类型、类成员、模板、模板特化、命名空间和形参包。预处理器宏不是 C++ 实体。

对象类型:非函数类型、非引用类型且非 void 类型的
所以,以下实体都不是对象:值,引用,函数,枚举项,类型,类的非静态成员,位域,模板,类或函数模板的特化,命名空间,形参包,和 this。

名字查找和命名空间

作用域:文件作用域(全局作用域)、命名空间作用域、类作用域、块作用域、枚举作用域、函数作用域、函数形参作用域、模板形参作用域

内部链接和外部链接

内部链接:其他编译单元无法访问的名称,拥有内部链接,相反是外部链接

以下为内部链接:

  1. 所有声明
  2. 类型(struct/union/enum 等)
  3. 命名空间中的 static 函数和变量以及 const 常量
  4. inline 函数

以下为外部链接:

  1. 命名空间中的非 static 函数和变量
  2. 类成员函数(包含成员函数和 static 成员函数)和静态成员变量

类型比较

c++有如下类型:

  • 基本类型
    • void
    • nullptr:空指针
    • 算术类型
      • 整数类型
        • bool 类型
        • 字符类型
          • 窄字符:(char、signed char、unsigned char、char8_t)
          • 宽字符:(char16_t、char32_t、wchar_t)
        • 整数类型:各种限定的 int
      • 浮点类型:(float 、 double 、 long double)
  • 复合类型
    • 数组类型
    • 函数类型
    • 指针类型(包括对象指针,函数指针,成员函数指针,数据成员指针)
    • 引用类型
    • 枚举类型
    • 类类型
    • 联合体类型

基本类型异同

空指针字面量

为什么需要空指针字面量?
空指针用于表示一个无效的指针,它的值为 0(早期 C 语言的实现中可能有非 0 空指针,现在已经不用)。对指针置 NULL 即标记指针无效,避免“野指针”的恶果

c 语言空指针定义于<stddef.h>,它可以被定义为 ((void*)0), 0 或 0L,这取决于编译器供应商。

1
2
3
4
5
6
7
// 兼容 C++ :
#define NULL 0
// 不兼容 C++ :
#define NULL ((void*)0)
// 允许void * 与任意其他对象指针的隐式类型转换
int* p = malloc(10 * sizeof(int)); // malloc 返回 void*
void *pv = p; // int * 转换为void *

c++中空指针定义如下:

1
2
3
#define NULL 0
// C++11 起
#define NULL nullptr

问题一:为什么 c++把 NULL 定义为 0,而不是 void
因为 c++中不允许(void
)到其他类型指针的隐式类型转换

1
2
3
4
#define NULL ((void*)0) /* 如果在 C++ 语言中这么定义的话 */
int* a = NULL; /* 隐式转换,编译错误 */
int* a = (int*)NULL; /* 显示转换,正确,但很麻烦*/
int* a = 0; // 可以,字面量0可以隐式转换为指针类型

结论:c++中,NULL 只能被定义为 0

问题二:有了 NULL 表示空指针,c++11 为什么增加 nullptr_t ?
当 NULL 被定义为 0 后,在函数重载时,会发生歧义,如下

1
2
3
4
5
6
7
8
void Func(char *);
void Func(int);

int main()
{
Func(NULL); // NULL=0,所以不知道应该调用哪个
Func(nullptr); // 调用Func(char *);
}

c++11 的解决方案,定义个 std::nullptr_t 类型,该类型定义了转到任意指针类型的转换操作符,同时不允许该类型的对象转换到非指针类型

结论:用 nullptr 类型表示无效指针后,既不需要初始化时显示转换的麻烦,又避免 NULL 定义为 0 时的函数重载问题,而保留 NULL 只是为了向后兼容。所以 c 中使用 NULL,c++11 中使用 nullptr。

bool 类型
c99 开始,增加关键字_Bool 支持布尔类型

1
2
3
#define bool	_Bool
#define true 1
#define false 0

c99 后,bool,true,false 为宏定义,在<stdbool.h>中
c++中,bool,true,false 均为关键字

字符类型

c++中,目前有以下内置字符类型(关键字):
char
char8_t (C++20 起):UTF-8 字符表示的类型,char8_t utf8[] = u8”我”
char16_t (C++11 起):UTF-16 字符表示的类型,char16_t utf16[] = u”我”
char32_t (C++11 起):UTF-32 字符表示的类型,char32_t utf32[] = U”我”
wchar_t: 实现定义,windows 为 16 位 short 类型,gcc 为 32 位 int 类型,定宽字符

c 语言中,使用宏定义实现:

1
2
3
typedef unsigned short wchar_t;  // wctype.h
typedef uint_least16_t char16_t; // 定义于<uchar.h>
typedef uint_least32_t char32_t; // 定义于<uchar.h>

字符串使用原则:
程序内部使用 char8_t(UTF-8 编码)字符串,既可以表示所有字符,又不浪费空间
求字符串长度等操作时,转换为 wchar_t 宽字符串方便操作,char16_t 和 char32_t 存在字节序问题,不建议使用
注意:宽字符和窄字符相互转换,或者输出到控制台时,需要设定正确的 locale,才能正确解析窄字符串

引用类型
c++增加引用类型,表示一个标识符的别名,故不占用存储空间
没有数组的引用,没有指针的引用,没有引用的引用

面向对象

通过 class 或者 struct 支持面向对象编程

编译器默认会定义的成员函数:

1
2
3
4
5
6
7
8
class A {
A() {} // 默认构造函数
A( const A & ) {} // 复制构造函数
//移动构造函数 (C++11 起)
//复制赋值运算符
//移动赋值运算符 (C++11 起)
//析构函数
}

c++中的多态:
函数重载:可以定义不同参数的相同名称的函数,编译期,原理是编译器会产生不同的符号
多态:基类中定义的虚函数,可以被子类覆盖,运行时调用不同的函数
多态原理:通过虚函数表和虚表指针实现,编译器为每个定义了虚函数的类分配一个虚函数表,表中存放的是这个类所有虚函数的指针,每个类对象的最前面存放的是虚函数表指针,

模板编程

通过模板编程支持范型编程,让程序员编写与类型无关的通用代码,比如通用算法

1
2
3
4
5
6
// 函数模板
template <class 形参名, class 形参名, ...> 返回类型 函数名(参数列表) { ... }

// 类模板
template <class 形参名, class 形参名, ...> class 类名{ ... };
}

模板有显式实例化,隐式实例化,特化(具体化)
隐式实例化:运行期间,根据参数动态生成模板实例
显式实例化:编译期间,生成对应的实例
特化:针对自定义参数,不能生成对应实例时,跟显示实例化一样,编写针对特定类型的模板