[C++学习笔记] 数据类型

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变量)

指针与引用的区别:

  1. 指针本身就是一个对象, 可以对指针赋值和拷贝,可以先后指向不同的对象;而引用不是对象,一旦绑定也不能改变
  2. 指针无需在定义时就初始化,和其他内置类型一样,未被初始化时获得一个不确定的值
    不能定义指向引用的指针 因为引用不是对象

但是可以定义指向指针的指针 因为指针本身是对象 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修饰指针有三种情况

  1. const修饰指针 --- 常量指针
  2. const修饰常量 --- 指针常量
  3. 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. 类型的特殊使用

使用变量类型的别名:

  1. Typedef : typedef double wage, *p; wagedouble的别名, pdouble *的别名
  2. Using(别名声称): using wage = double;

但是使用指针别名时需要注意 如上pdouble *的别名,则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 &, 因为加上括号的变量被视为特殊的表达式, 其类型为对应引用

迭代器类型类似于指针 使用值时也需解引用