第二章 变量和基本类型¶
2.1 基本内置类型¶
1
2
| sizeof(int) = 4; sizeof(long int) = 8; sizeof(long long int) = 8;
sizeof(float) = 4; sizeof(double) = 8; sizeof(long double) = 16;
|
字面值常量(literal)¶
分多行书写字符串:C++ 允许在一条语句中自动连接多个双引号字符串(连接处无空格)
1
2
| std:cout<<"wow, a really, really long string"
"literal that spans two lines" <<std::endl;
|
2.2 变量¶
声明和定义¶
- 初始化(initialize):初始化不是赋值
- 初始化 = 创建变量 + 赋予初始值
- 赋值 = 擦除对象的当前值 + 用新值代替
- 变量的声明(declaration)和定义(define)
- 声明使得名字为程序所知;定义负责创建与名字关联的实体(分配内存)。
- extern:表示符号的定义在模块外部,但如果包含了初始值,就变成了定义
- C和C++关于声明和定义、强符号和弱符号、变量在ELF节中的位置规定似乎不同
1
2
3
4
5
6
7
8
9
10
| // a.c(或a.cpp)
#include <stdio.h>
int x;
int main(){
printf("%d\n", x);
return 0;
}
// b.c(或b.cpp)
int x = 3;
|
gcc a.c b.c -o test1
:编译链接并运行,正常
gcc a.cpp b.cpp -o test2
:链接出错:x多重符号定义
作用域¶
- 同时存在同名的全局和局部变量时,在内层作用域中可以使用
::reused
显式访问全局变量reused。
2.3 复合类型¶
复合类型的含义:比如int型是基本类型,但是引用和指针(即&、*称为类型修饰符)可以再作为声明符的一部分(类型修饰符+变量标识符),构成复合类型
- 类型修饰符是声明符的一部分,因此
int *a, b, &c;
中只有a是指针,c是引用 - 但是理解上类型修饰符可以看作是类型的一部分,比如
int *&pa=a;
中pa是对int*类型(指向int的指针)的引用 - 判断类型是从右向左读,最靠近变量名的类型修饰符说明这是一个什么复合类型,其余符号和基本类型共同说明指向/引用何种(复合)类型
(左值)引用¶
- 引用必须初始化。
- 引用和它的初始值是绑定在一起的,而不是拷贝。一旦定义就不能更改绑定为其他的对象
- 引用只能绑定到对象上,不能与字面值或某个表达式的计算结果绑定
- 引用不是对象
void*
指针可以存放任意对象的地址。因无类型,仅操作内存空间,对所存对象无法访问(不能直接操作void*指向的对象)。- 指针的类型要与所指向的对象严格匹配,两个例外:
- 可以使用指向常量的指针指向非常量
- 可以使用基类的指针指向派生类
- 注意不能直接给指针赋值一串地址,给指针赋值应该是另一个指针或是变量取地址
1
2
3
4
5
6
7
8
| int a = 1;
int *p = &a; // a是int型, p是int型的指针(int *)
int *q = p; // q也是int型的指针, 将p赋值给q
int* *pp = &p; // pp是指向int*类型变量的指针
// 不能是 int *pp = &p, 即犯了直接把内存地址赋值给指针的错误
int** *ppp = &pp; // ppp是指向int**类型的指针
int*** &r_ppp = ppp; // r_ppp是指向int***类型的引用
cout<<a<<" "<<*p<<" "<<**pp<<" "<<***ppp<<" "<<***r_ppp;
|
# 各类指针的详细介绍
指针与引用¶
- 不同
- 指针存储一个变量地址,是一个对象;而引用只是变量的一个别名
- 引用在定义时必须要进行初始化,而指针不用
- 指针可以在任意时候改变指向,引用在初始化时绑定对象后就无法改变绑定的对象
- sizeof(引用)=引用类型的大小,sizeof(指针)=4或8(地址空间所占字节数)
- 自加效果不同
- 有多级指针,但是没有多级引用
- 存在指向指针的指针,无引用的引用,不能定义指向引用的指针,可以定义指向指针的引用。
- 编译器实际上是将对引用的操作翻译成对指针的操作
- [# 简谈 C++ 中指针与引用的底层实现](# 简谈 C++ 中指针与引用的底层实现)
2.4 const限定符¶
初始化和const¶
- const表示编译器限制该变量只能是只读的,修改变量时编译器会报错,因此const对象必须在一开始声明时进行赋值
- 在C中,可以使用指针指向const对象,进行修改(不要这样写)
- 在CPP中,使用非const指针指向const对象会报错(但是可以将const指针强转为普通指针进而修改,不要这样写)
- const变量默认不能被其他文件访问(作用域在本模块中)
- 如果非要共享const对象,不管是声明还是定义,都要加上extern关键字(因此C++中const变量从内部变量转变为extern const的外部变量,如果再初始化就变成强符号)
| - | const | extern const |
---|
C | global | global | global |
C++ | global | local | global |
两个例子
引用和const¶
- reference to const(对常量的引用,简称为常量引用):指向const对象的引用
1
2
3
| const int ival = 1; // const
const int &r = ival; // 常量引用,指向常量的引用
int &r2 = ival; // 错误:非常量引用绑定一个常量对象
|
- 引用的类型必须与所引用对象的类型一致
- 例外:在初始化常量引用时允许用任意表达式作为初始值
- 因此,对const的引用(常量引用)可能引用一个非const的对象
1
2
3
| int a = 1;
const int &r = a;
a = 2; // 可以修改a的值,但是不能通过引用r来修改值
|
- 原理:如果不一致,则中间使用临时量进行类型转换,引用指向这个临时量
1
2
3
4
5
6
7
8
9
10
11
12
13
| double dval = 3.14;
const int &r1 = dval; // 常量引用r1的类型int与所引用对象dval的类型double不同
/* 相当于:
const int tmp = dval; // 临时未命名对象(临时量)
const int &r1 = tmp; // 因此修改dval的值,不会影响r1
*/
// 除了初始化常量引用时,普通引用的类型必须与所引用对象的类型一致
int &r2 = dval; // 错误
/* 相当于:
int tmp = dval;
int &r2 = tmp; // 引用绑定的是一个临时量,C++规定非法
*/
|
| int变量 | const int常量 | double变量 | const double变量 |
---|
int&普通引用 | √ | × | × | × |
const int&常量引用 | √ (可以修改int的值,但是不能通过引用来修改) | √ | warning (见dval ) | √ |
指针和const¶
- pointer to const(指向常量的指针):
const int i = 0; const int *p = &i;
- 变量p的类型是指针
*
,指向的类型是const int
- 指向常量的指针可以指向一个非常量对象,但是不允许通过指针修改变量的值
- 底层 const属性
- 底层const属性对元素赋值有影响:等号两边需要有相同的底层const资格,或者非常量转换为常量
- const pointer(常量指针):
int i = 0; int *const ptr = &i;
- 顶层const属性:这个类型的变量是只读的
- 对于常量指针而言,指针本身是常量(即指针固定指向某个地址)
- 顶层const属性对指向元素的拷贝无影响
- 变量ptr的类型是常量指针
*const
,指向的类型是int
constexpr和常量表达式¶
- 常量表达式:值不会改变,且在编译过程中就能得到计算结果的表达式。
constexpr
- 背景:有时很难判断一个初始值是否为常量表达式(因为有的const对象的值直到运行时才能知道)
- 使用:
C++11
规定,将变量声明为constexpr
类型以便由编译器来验证变量的值是否是一个常量的表达式,constexpr对象的值必须在编译期间确定。- 定义在函数体内部的变量,地址在编译器无法确定,无法用来初始化constexpr指针
- 定义在函数体外部的变量,地址固定,可以用来初始化constexpr指针
- constexpr指针相当于顶层const
- constexpr表示真正的常量,const表示只读
2.5 处理类型¶
类型别名¶
- 传统别名:使用typedef来定义类型的同义词:
typedef int* ptr
- C++11 别名声明(alias declaration):
using ptr=int*;
- 不能将类型别名像宏一样代回进行理解,类型别名本身就表示一种类型
1
2
3
4
5
| typedef int* ptr;
int a = 1;
const ptr p_a = &a; // p_a是const ptr类型的对象,即指向int的常量指针(顶层const)
const int* p = &a; // p是指向const int的指针(底层const)
const ptr* pp_a = &p_a; // pp_a是指向const ptr的指针,即指向int的常量指针的指针(底层const)
|
auto类型说明符¶
- 一条声明语句中只能有一个基本数据类型,但可以有复合类型(比如指针和引用)
- 复合类型自动推导
- auto会忽略引用类型:
int i = 0, &r = i; auto a = r;
推断a
的类型是int
而非int&
- 用 auto 定义引用时,必须用&指明要定义的是引用
- auto会忽略顶层const,但保留底层const:
1
2
3
4
5
6
| const int i = 1;
const int* pi = &i; // 底层const
auto a = *pi; // *pi类型是const int,推断a的类型是int,auto忽略顶层const
const auto b = *pi; // 如果希望是顶层const需要自己加const
auto p = pi; // 推断p的类型是const int*,auto保留底层const
// *p = 2; // 报错
|
decltype类型指示符¶
- 背景:希望获得表达式的类型但是不需要值(编译器分析类型但是不求值)
- 使用
- 不会忽略顶层const和引用
- 当获得的类型是引用时,必须进行初始化
- 如果表达式不是一个变量,返回表达式结果对应的类型,比如
b
- 一些表达式比如【指针解引用、变量加括号、赋值操作】的结果均为引用类型
1
2
3
4
5
6
| int a = 1, &r = a, *p = &a, b = 2;
decltype(r) r_a = a; // r_a是int&
decltype(r+0) b = a; // b是int型
decltype(*r) r_a2 = a; // r_a2是int&
decltype((a)) r_a3 = a; // r_a3是int&
decltype(a = b) r_a4 = a; //r_a4是int&
|
2.6 自定义数据结构¶
struct¶
1
2
3
4
5
6
7
| // 复杂写法
struct student {};
struct student xiaoli;
// 简单写法:使用typedef取别名,不用写struct student,而是直接写stu
typedef struct student {} stu;
stu xiaozhang;
|
编写自己的[[ch01-开始#头文件|头文件]]¶
各种“符”¶
- 声明符(declarator):类型修饰符(可有可无)+变量名(也称标识符)
- 类型修饰符:*,&
- 标识符(identifier):变量名
- 类型说明符(type specifier):int,char,void等
- 数据类型修饰符:unsigned等
- 类型限定符(qualifier):const,static等
- 访问限定符:public, private, protected
const和指针、引用的组合¶
1
2
| int a = 0;
const int ca = 1;
|
- 常量指针(const pointer):
int *const p = &a
,是顶层const - 指向常量的指针(pointer to const):
const int *p = &a;*
,是底层const - 对常量的引用(reference to const):
const int &r = a;
- 常量引用(const reference):不存在