C++ 的基本内置类型分为两大类:

  • 算术类型:用于表示数值和字符
  • 空类型(void):不对应具体的值,常用于特殊场景,如函数无返回值、void* 指针等

算术类型

算术类型分为:

  • 整型:包括字符类型和布尔类型
  • 浮点型

常见内置类型

类型 含义 最小尺寸
bool 布尔类型 未规定
char 字符 8 位
wchar_t 宽字符 至少 16 位
char16_t Unicode 字符 16 位
char32_t Unicode 字符 32 位
short 短整型 16 位
int 整型 16 位
long 长整型 32 位
long long 长长整型 64 位
float 单精度浮点数 6 位有效数字
double 双精度浮点数 10 位有效数字
long double 扩展精度浮点数 10 位有效数字

注意:表中的“最小尺寸”是语言标准规定的下限,实际大小依赖编译器和平台。

整型之间的大小关系

C++ 保证:

1
short <= int <= long <= long long

也就是说:

  • int 至少和 short 一样大
  • long 至少和 int 一样大
  • long long 至少和 long 一样大

有符号与无符号

bool 和扩展字符类型外,整数类型通常都有:

  • 有符号类型signed
  • 无符号类型unsigned

例如:

1
2
3
unsigned int a;
unsigned long b;
unsigned c; // 等价于 unsigned int

字符类型

字符类型有 3 种:

  • char
  • signed char
  • unsigned char

其中:

  • char 不等于 signed char
  • char 到底表现为有符号还是无符号,由编译器决定

建议:不要混用有符号类型和无符号类型。

字面值常量

字面值常量就是程序中直接写出的值。

整型字面值

字面值 含义
20 十进制
024 八进制
0x14 十六进制

浮点字面值

例如:

1
2
3
4
5
3.14159
3.14159E0
0.
0e0
.001

特点:

  • 可以写成小数形式
  • 也可以写成科学计数法
  • 指数部分用 eE
  • 默认类型是 double

字符和字符串字面值

字面值 含义
'a' 字符字面值
"Hello World!" 字符串字面值

说明:

  • 字符字面值表示单个字符
  • 字符串字面值本质上是由常量字符组成的数组
  • 编译器会自动在字符串末尾添加一个空字符 '\0'

例如:

1
"abc"

实际存储为:

1
'a' 'b' 'c' '\0'

所以它的长度是 4,不是 3。

转义序列

转义序列 含义
\n 换行
\t 横向制表符
\v 纵向制表符
\r 回车
\b 退格
\f 进纸
\a 响铃
\\ 反斜杠
\' 单引号
\" 双引号
\? 问号

转义序列在程序中通常作为一个字符处理。

布尔字面值和指针字面值

  • 布尔字面值:truefalse
  • 空指针字面值:nullptr

变量

变量提供一块有名字的存储空间,程序可以通过变量名访问和操作它。

变量定义

变量定义的一般形式:

1
类型说明符 变量名;

也可以一次定义多个变量:

1
int sum = 0, value, sold = 0;

说明:

  • 多个变量名之间用逗号分隔
  • 语句以分号结束
  • 每个变量都具有同一个基本类型,但声明符可以不同

初始化

初始化:变量创建时赋予初始值
赋值:对象创建后,再把新值赋给它

例如:

1
2
int a = 10;  // 初始化
a = 20; // 赋值

在 C++ 中,初始化和赋值是两个不同的概念。

初始化方式

常见初始化写法:

1
2
3
4
int a = 0;
int b(0);
int c{0};
int d = {0};

其中:

  • {} 形式称为列表初始化
  • 列表初始化有助于避免某些隐式类型转换

例如:

1
2
int x{3.14};   // 错误,存在精度丢失
int y = 3.14; // 合法,但会截断为 3

默认初始化

如果定义变量时没有提供初值,就会发生默认初始化

内置类型变量的默认初始化规则

  1. 定义在函数体之外的变量
    自动初始化为 0

  2. 定义在函数体内部的内置类型变量
    不会被初始化,值是未定义的

例如:

1
2
3
4
5
int global_var;   // 值为 0

int main() {
int local_var; // 未初始化,值未定义
}

使用未初始化的内置类型变量是危险的。

声明与定义

C++ 支持分离式编译,因此“声明”和“定义”是不同的概念。

区别

  • 声明:让名字为程序所知
  • 定义:创建与名字关联的实体

extern

如果只想声明变量而不定义,可以使用 extern

1
2
extern int i;  // 声明,不定义
int j; // 定义

注意:

  • 带初始化的声明就是定义

例如:

1
extern int k = 10;  // 这是定义,不是单纯声明

结论:

  • 变量只能定义一次
  • 变量可以声明多次

标识符

标识符就是程序中名字的统称,比如:

  • 变量名
  • 函数名
  • 类名
  • 类型名

命名规则

C++ 标识符:

  • 由字母、数字、下划线组成
  • 必须以字母或下划线开头
  • 区分大小写
  • 不能使用关键字

例如:

1
2
3
num
_value
student1

不合法:

1
2
3
2abc
int
class

命名建议

  • 名字应体现含义
  • 变量名通常用小写
  • 类名通常首字母大写
  • 多单词命名风格应统一,如:
  • student_name
  • studentName

作用域

作用域是程序中某个名字有效的区域。

常见作用域

  • 全局作用域:定义在所有函数外
  • 块作用域:定义在 {} 内部

例如:

1
2
3
4
5
int global_val = 10;   // 全局作用域

int main() {
int local_val = 20; // 块作用域
}

说明:

  • 内层作用域可以隐藏外层同名变量
  • 不建议随意定义与全局变量同名的局部变量

复合类型

复合类型是基于其他类型定义出来的类型,常见的有:

  • 引用
  • 指针

引用

引用是对象的别名

定义引用

1
2
int val = 1024;
int& ref = val;

说明:

  • refval 的另一个名字
  • 引用必须初始化
  • 引用一旦绑定,就不能再绑定到其他对象

错误示例:

1
2
int& r;      // 错误,引用必须初始化
int& r2 = 10; // 错误,普通引用不能绑定字面值

引用的特点

  • 引用不是对象
  • 引用本身不占用独立语义上的存储对象身份
  • 对引用的操作就是对原对象的操作

例如:

1
2
3
int a = 10;
int& r = a;
r = 20; // 实际修改的是 a

指针

指针是“存放地址的对象”。

定义指针

1
2
int* p;
double* dp;

多个变量一起定义时要注意:

1
2
int* p1, *p2;   // p1 和 p2 都是 int*
int i, *p3; // i 是 int,p3 是 int*

获取对象地址

用取地址符 & 获取对象地址:

1
2
int val = 42;
int* p = &val;

解引用

* 访问指针所指向的对象:

1
2
3
int val = 42;
int* p = &val;
cout << *p << endl; // 输出 42

只有当指针指向有效对象时,才能解引用。

指针的状态

指针可能处于以下几种状态:

  1. 指向一个对象
  2. 指向紧邻对象末尾的下一个位置
  3. 空指针
  4. 无效指针

空指针

空指针不指向任何对象。

1
2
3
int* p1 = nullptr;
int* p2 = 0;
int* p3 = NULL;

推荐使用:

1
int* p = nullptr;

nullptr 是 C++11 引入的,更安全、更明确。

void* 指针

void* 可以存放任意对象的地址,但不能直接访问对象内容。

1
void* p = nullptr;

因为 void* 不知道指向对象的具体类型,所以不能直接解引用。

复合类型的声明

一条定义语句中,基本类型只有一个,但每个声明符可以不同:

1
int i = 1024, *p = &i, &r = i;

这里:

  • iint
  • pint*
  • rint&

指向指针的指针

1
2
3
int val = 1024;
int* p1 = &val;
int** p2 = &p1;

关系:

1
p2 -> p1 -> val

指针的引用

引用不能“指向引用”,但可以“引用一个指针”:

1
2
3
int val = 42;
int* p = &val;
int*& r = p;

这里 rp 的引用。

const 限定符

const 用来表示只读,即对象的值不能被修改。

1
2
const int val = 42;
// val = 52; // 错误

说明:

  • const 对象必须初始化
  • 一旦初始化,其值不能修改

const 引用

指向常量的引用通常称为常量引用

1
2
const int val = 42;
const int& r = val;

特点:

  • 不能通过 r 修改 val
  • 普通引用不能绑定到常量对象
1
int& r2 = val;   // 错误

补充:常量引用还能绑定到字面值或临时对象:

1
const int& r = 10;   // 合法

指针和 const

指向常量的指针

1
const int* p = &val;

或:

1
int const* p = &val;

含义:

  • 可以改 p 的指向
  • 不能通过 p 修改所指对象

常量指针

1
int* const p = &val;

含义:

  • 不能改 p 的指向
  • 可以通过 p 修改对象(前提是对象本身非常量)

指向常量的常量指针

1
const int* const p = &val;

含义:

  • 不能改指向
  • 也不能改所指对象

顶层 const 与底层 const

  • 顶层 const:对象本身是常量
  • 底层 const:对象所指向的内容是常量

例如:

1
2
3
4
5
6
int i = 0;
int* const p1 = &i; // 顶层 const
const int ci = 42; // 顶层 const
const int* p2 = &ci; // 底层 const
const int* const p3 = &ci; // 既有顶层 const,也有底层 const
const int& r = ci; // 引用中的 const 是底层 const

constexpr 和常量表达式

常量表达式

常量表达式:在编译时就能求值,并且值不会改变的表达式。

例如:

1
2
const int a = 10;
const int b = a + 1;

这里 ab 都可能是常量表达式。

但下面不是:

1
2
int val = 20;
const int sz = get_size(); // 如果 get_size() 不是 constexpr,则不是常量表达式

constexpr 变量

constexpr 用于声明真正的编译期常量:

1
2
constexpr int val = 20;
constexpr int sum = val + 1;

要求:

  • 必须是常量
  • 必须用常量表达式初始化
1
constexpr int sz = size(); // 只有 size() 是 constexpr 函数时才合法

如果确定一个值在编译期已知,优先考虑使用 constexpr

constexpr 与指针

1
2
const int* p = nullptr;      // 指向 const int 的指针
constexpr int* q = nullptr; // q 是常量指针

注意:

  • constexpr 修饰指针时,表示指针本身是常量
  • 不代表所指对象一定是常量

处理类型

当类型写法复杂时,可以使用类型工具来简化。

类型别名

typedef

1
2
typedef double wages;
typedef wages base, *p;

这里:

  • wagesdouble 的别名
  • basedouble 的别名
  • pdouble* 的别名

using

1
using db = double;

这是 C++11 推荐的写法。

auto

auto 让编译器根据初始值自动推断变量类型:

1
2
auto i = 0;      // int
auto d = 3.14; // double

要求:

  • 使用 auto 时必须初始化

decltype

decltype 用于推断表达式的类型,但不会计算表达式的值

1
decltype(f()) sum = x;

这里 sum 的类型就是函数 f() 的返回类型。

decltype 与引用

如果 decltype 的参数是变量名,则返回该变量的精确类型,包括 const 和引用。

1
2
3
4
5
const int val = 42;
const int& ref = val;

decltype(val) x = 0; // x 的类型是 const int
decltype(ref) y = x; // y 的类型是 const int&

注意:

1
decltype(ref) z;   // 错误,引用必须初始化

特别注意

1
decltype(var)

1
decltype((var))

不同:

  • decltype(var):如果 var 不是引用,结果就是变量本身类型
  • decltype((var)):因为 (var) 是表达式,所以结果通常是引用类型

例如:

1
2
3
int i = 0;
decltype(i) a = 0; // int
decltype((i)) b = i; // int&

自定义数据结构

C++ 中可以使用关键字:

  • struct
  • class

来定义自己的数据类型。

简单理解:

  • structclass 本质上都可以定义类类型
  • 主要区别在于默认访问权限不同:
  • struct 默认是 public
  • class 默认是 private

例如:

1
2
3
4
struct Student {
string name;
int age;
};

易错点总结

初始化 ≠ 赋值

1
2
int a = 10; // 初始化
a = 20; // 赋值

局部内置变量不会自动初始化

1
2
3
int main() {
int x; // 未定义值
}

引用必须初始化

1
int& r;   // 错误

不要解引用无效指针

1
2
int* p = nullptr;
// *p = 10; // 错误

区分两种 const 指针

1
2
const int* p;   // 不能通过 p 改值
int* const p2; // 不能改 p2 的指向

推荐使用 nullptr,而不是 0 或 NULL

1
int* p = nullptr;