1. 计算机内部数据表示
计算机中二进制数据表示 第一位是符号位 后面表示数值 负数就是第一位是1 后面取补值
补码就是反码加1 反码就是除了符号位其他每位取反
例如8位的数据表示范围为 -27 ~ 27 - 1
-1 = 11111111
整数类型的数据范围
数据类型 | 范围 | 位数 |
---|---|---|
int | -231 ~ 231 - 1,即 -2147483648~2147483647 | 10位数,21亿 |
long long | -263 ~ 263 - 1,即 922*1016 | 19位数 |
unsigned long long | 0 ~ 264,即 1844*1016 | 20位数 |
不要混用无符号和有符号的数 无符号整数与整数直接运算可能会出错
因为会将整数自动转换为无符号整数 如果整数为负则转换成其对应补码的正整数形式
并且即使是两个无符号整数, 运算时也应该注意运算结果是否可能会溢出
2. 字符类型
对于字符数组而言 编译器自动会在字符串结尾加个'\0'表示结束 因此字符串实际长度比内容长度多1
如果两个字符串紧邻且仅由空格 缩进和换行符分割 那么它们其实是一个字符串
Cout<<"aaa" "bbb"<<endl;
可以通过添加前缀和后缀来改变数据的默认类型:eg. u8"hi"(utf-8 字符串), 42ULL(无符号longlong 整数 42)
String a(b)
表示创建一个字符串a复制b的值,拷贝初始化
int a{ld}
列表初始化 C++11新特性 在初始化丢失信息时报错
Cout<<::a
通过作用域运算符可以在定义了同名局部变量时使用全局变量a
3. 引用与指针
引用不是指针 引用必须初始化且初始化后不可更改 引用即别名 int &a=b;
不能定义引用的引用 因为引用不是一个对象 只是一个链接
引用绑定的必须是变量(对象) 不能是一个字面值 int &a=0
是错误的
引用绑定的变量必须严格符合它的类型 (int&
只能绑int
变量)
指针与引用的区别:
- 指针本身就是一个对象, 可以对指针赋值和拷贝,可以先后指向不同的对象;而引用不是对象,一旦绑定也不能改变
- 指针无需在定义时就初始化,和其他内置类型一样,未被初始化时获得一个不确定的值
不能定义指向引用的指针 因为引用不是对象
但是可以定义指向指针的指针 因为指针本身是对象 int **p
也可以定义指向指针的引用 int *&r=p;
注意是int *
的指针所以引用是int *&
定义时 自右向左判断类型 离的最近的符号有着最直接的影响 int *&
就代表是个int *
的引用
void*
指针可以存放任何类型的数据地址 但不能通过解引用来操作其指向的对象 因为类型不明确 仅仅只是作为内存地址保存 可以用于传参和比较地址
变量的定义包括一个基本数据类型和一组声明符, 基本数据类型只有一个, 但声明符可以有多个
int i=1024, *p=&I, &r=I;
同时通过多个声明符声明了三个不同类型的变量 有着相同的基本数据类型 但声明符不同
将指针形式写成int* p
也可以,与int *p
是一样的
4. 常量类型
const
变量的意义: 使用变量方便我们自己改变 但又防止程序不小心改变
cosnt
变量必须在定义时初始化
默认const
变量仅对当前文件生效 想在多个文件中使用的话必须使用extern
关键字
指向常量的引用必须也是常量引用
const int a=0; const int &b =a;
但常量引用可以绑定非常量的数据类型甚至是字面值
int i=42;
const int & r1 =i;
const int &r2=42;
const int &r3=r1 *2;
int & r4 = r1 * 2; //不行 r4是个非常量引用 而r1 *2 得到的是计算结果 是一个临时值 不允许改变
常量引用只是禁止了通过常量引用去改变绑定的值,但只要绑定的值本身不是常量,仍然可以改变
int i=12; int &r1=i;
const int &r2 = i;
r1=0; //可以修改i的值,但r2=0不行,不能通过常量引用修改
const
修饰指针有三种情况
const
修饰指针 --- 常量指针const
修饰常量 --- 指针常量const
即修饰指针,又修饰常量
int main() {
int a = 10;
int b = 10;
//const修饰的是指针,指针指向可以改,指针指向的值不可以更改
const int * p1 = &a;
p1 = &b; //正确
//*p1 = 100; 报错
//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
int * const p2 = &a;
//p2 = &b; //错误
*p2 = 100; //正确
//const既修饰指针又修饰常量
const int * const p3 = &a;
//p3 = &b; //错误
//*p3 = 100; //错误
system("pause");
return 0;
}
判断方法即看const
紧跟的是什么 紧跟的是指针则指针不可更改 紧跟的是指向的变量类型 则指向的变量不可更改
看const
右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量
想要存放常量的地址必须使用常量指针
但和常量引用类似 常量指针也不一定要指向常量 只是禁止通过指针来改变指向的对象
和一般的常量相同 对指针常量进行定义时必须初始化且值(即指向的对象地址)不可改变
非常量可以转换为常量 但反之不行 所以可以用常量指针保存非常量的地址 反之不行
常量表达式(constexpr)是指值不会改变且在编译过程就能得到值的表达式 由数据类型和初始值共同决定:
const int s = 20; //是
Const int a = s+1; //是
Int b= 22; //不是, 其数据类型不是常量
Const int c = get_v(); //不是 其值要在运行时才能得到
由于实际中几乎不可能判断是不是常量表达式 因此可以通过声明constexpr
类型来定义
constexpr int sz = get_v(); // 必须get_v()是一个constexpr函数
但是由于常量表达式必须在编译时得到 因此必须是很简单的类型 比如字面值类型(自定义类 库函数的不行)
注意constexpr
定义的指针等价于指针常量 constexpr int * a
等价于 int * const a
5. 类型的特殊使用
使用变量类型的别名:
- Typedef :
typedef double wage, *p;
wage
是double
的别名,p
是double *
的别名 - Using(别名声称):
using wage = double;
但是使用指针别名时需要注意 如上p
是double *
的别名,则const p
定义的是一个指针常量, const
修饰的是p
的整体类型
不能简单地将原名替换回来再看 是错误的
auto
类型 是让编译器根据值自己推断类型 auto a = b + c;
使用auto
在一行中声明多个时 必须保证都是同一个数据类型 但声明符可以不同
auto i=0, *p =&i; //正确 整型与整型指针 类型相同 声明符不同
auto a=0, b='a'; //错误 类型不一致
const int c = 0;
int d=0;
auto e=c, f=d; // 错误 cosnt int 与 int类型不一致
decltype
关键字用于选择并返回操作数的类型
这个过程中编译器分析表达式得到类型却不实际调用表达式计算
decltype(f()) sum =x;
const int i = 0;
decltype(i) y=0; // y的类型是 const int
int a=0, *p =&a, &r = a;
decltype(r) b = a;
decltype(r+0) c;
decltype(*p) d;
decltype((a)) f =a;
b
的类型是int &
, c
的类型是int
, d
的类是int &
, 因为解引用得到的其实就是指针指向对象的引用
f
的类型是int &
, 因为加上括号的变量被视为特殊的表达式, 其类型为对应引用
迭代器类型类似于指针 使用值时也需解引用