c++博大精深, 越看越觉得不会的太多. 回炉一下吧~ 大神们 请勿浪费时间~
第二章
细节比较多
2.1 算术类型和空类型 空类型 比较常见的就是void 函数无返回值时. 算术类型 整型 浮点型. 长度不同机器不同, c++只规定各种类型最短尺寸!!
一个char的大小和一个几个机器字节一样
一个int>=short, long >=int, long long >=long 类型长度的定义很有意思.
int short long long long 有 有符号和无符号两种 而 char 则有三种, 编译器决定了char是有符号还是无符号的 这个为啥要这样定义啊? 所以使用char还是明确使用signed char 还是 unsigned char
选择 数据类型的推荐方案:
1. 明确知道数据不可能为负时, 选无符号的
2. 如果数值有可能超过int , 直接 long long
3. signed char or unsigned char 而 char 就当不存在吧
4. 浮点 就 double
2.2 类型转换 假设char占8位
1. bool 只认 0 和非零, 转换到int 就只有0 1
2. signed char c2 =256 超出返回 则认为c2 未定义
3. unsigned char c = -1, 涉及到原码 反码 补码, 正数 原 反 补 一样
负数 -1=[10000001]原 = [11111110]反 = [11111111]补 作为正数 结果就是255, 快速方法是 将-n 赋值给unsigned char = -n+256
字符面值常量 20(十进制) = 0x14(十六进制) = 024(八进制)
单引号 字符字面值, 双引号"hello world" 字符串字面值, 字符串字面值的长度=实际长度+1, 结尾 '\0'
转义字符 \n \v \t \b 等
指定字面值的类型 字符类型在字符前面如 L'a' u8"hi" 数值在后 42 ULL 1E-3F 3.1415L
2.3 变量 {} () = 的区别
函数体外的变量会自动给个默认值, 函数体内的不会自动初始化, 直接使用会报错
long double ld = 3.1415926
int a{ld}, b={ld} 大括号如果存在丢失信息的风险则报错
int a(ld), b = ld 这种初始化 会丢信息,但是不报错.
声明和定义的区别
只声明 使用extern, 可以声明多次, 未申请空间 未初始化
定义, 只能定义一次, 申请空间了
作用域: 局部变量会覆盖全局变量, 如需使用全局变量需要使用::
int p44() {
int unique = 0;
std::cout << reused << " " << unique << std::endl;
int reused = 22;
for (int reused = 0; reused < 2; ++reused) {
std::cout << reused << " in for1 " << unique << std::endl; // 输出 0 0
// todo 如何使用外部的reused, 即 reused=22 这个呢?
std::cout << ::reused << " in for2 " << unique << std::endl; // 输出 42 0
}
std::cout << reused << " " << unique << std::endl; // 输出 22 0
std::cout << ::reused << " " << unique << std::endl; // 输出 42 0
return 0;
}
引用 &
1. 引用=别名 不是对象, 一旦定义 将无法再绑定到其余的对象
2. 引用必须被初始化
3. 引用不能定义引用
4. 不能用字面值常量定义引用 如int &ref=10
指针 * 取地址& 如 int i =42, int *p = &i; 使用 cout<< *p 输出42
1. 指针是对象, 允许对指针赋值和拷贝, 可以先后指向不同的对象
2. 无须在定义时赋初值
定义空指针时 不能使用变量 如下:
int zero = 0;
int* p = 0; //可以这么定义空指针
int* p1 = zero; // 不能这样定义空指针
int* p2 = nullptr; //建议如此定义空指针
指针定义时初始化和再定义时 不同
int ival = 1024;
int* p1 = 0;
int* p2 = &ival;
p1 = &ival; //p1 是地址 *p1 是指向的对象
void* 用于存放任意对象的地址, 该地址中的对象未知; 类似于其他语言中的object, 用于装箱, 然后将来拆箱. 因为 指向的对象未知, 所以不能直接操作该指针指向的对象.
指向指针的引用 这个太绕了
int z = 42;
int *p;
int *&r = p; // r是一个对指针p的引用
// 从右向左分析: 离r最近的修饰符是&, 则r是一个引用. 再往左是*, 则说明r引用的是指针, 最后是int
r = &i; // 因r是引用, 可以直接替换r为p 理解
*r = 0; // 因r是引用, 可以直接替换r为p 理解
const 一旦创建就不能再改变, 必须初始化(编译时初始化, 运行时初始化) 文件级别的作用域, 如果想跨文件作用域 需要使用 extern const int bufSize=124;
const int readOnly = 200;
const int& refReadOnly = readOnly;
int readOnly2 = 100;
const int& refReadOnly2 = readOnly2;
int& refReadOnly3 = readOnly2;
readOnly2 = 21;
refReadOnly2 = 20; // 错误 不能修改 值
refReadOnly3 = 222; // 正确 可以修改 值
可以定义一个常量引用, 保护可以修改的变量不被修改. 指针也可以这样
int &r1=42 (x) const int &r1 = 42 (√)
注意以下指针定义的区别
int i=10;
const int *p = &i; // *p不能修改, *p是const 值不能变
int *const p = &i; // p不能修改 而 *p 指向的对象值可以修改 地址不能变 必须定义时初始化
const int *const p =&i // 值和地址 都不能变
顶层const 指针本身是常量, 即地址是常量
底层const 指针所指对象是常量
转换时 非常量转换为常量可以 反之 则不行( 只针对 底层 const, 顶层不受影响)
这个比较难, 下面是例子:
int i = 0;
int* const p1 = &i;
const int ci = 42;
const int* p2 = &ci;
const int* const p3 = p2;
const int& r = ci;
int* p = p3; // 错误, p3 含有const, p 没有
p2 = p3; // 正确, p3 p2 都具有底层const
p2 = &i; // 正确, int * 可以转 const int * 即非常量 转常量
int& r = ci; //错误, ci是常量, 常量不能被非常量 绑定
const int& r2 = i; // 正确, const int&可以绑定到一个常量上
常量表达式: 值不会改变且在编译过程就能得到计算结果的表达式. 字面值,const对象都是
const int sz=get_size(); // 这种不是常量表达式
constexpr 由编译器来验证变量值是否是一个常量表达式.
const int *p = nullptr; // p 是一个指向整型常量的指针 p可以变, *p不能变
constexpr int *q = nullptr;
// q 是一个指向整数的常量指针, q不能变, *q可以变. 类似 int *const q=nullptr
constexpr const int *p =nullptr; // 实现双锁定
constexpr 仅对指针有效, 与其所指的对象无关
类型别名 typedef 这个很难理解, 必须把typedef 定义的内容 作为一个整体来看, 不能原样带入, 原样带入后 会出现理解错误. 例如下面的例子. 所以不能简单的通过带入理解, 需要把typedef定义的类型作为一个整体来理解
char a = 'a';
typedef char* pstring;
const pstring cstr = 0;
cstr = 1; // 错误 cstr 值是常量
*cstr = 1; // 正确, *cstr指向的对象 不是常量
const char* cstr2 = 0;
cstr2 = &a; // 正确 cstr2 不是常量
*cstr2 = 'b'; // 错误 cstr2 指向的对象 是常量
char* const cstr3 = 0; // typedef的等效定义
cstr3 = 1; // 错误 cstr3 是常量
*cstr3 = 1; // 正确 cstr3 指向的对象 不是常量
auto类型 必须有初始值, 编译器自动分析类型. 好像var, let ?
auto一次定义多个变量时, 变量类型应该一致.
auto一般会忽略顶层const, 需要显示声明
int i=0;
const int ci =i, &cr=ci;
auto e = &ci; // e 指向整数常量的指针 常量取地址是一种底层const
auto f1 = ci; // 顶层const 被忽略掉. 相当于int f1 = ci
const auto f =ci;
decltype 获取操作/函数的类型, 但是不使用该操作/函数 初始化变量
int i=42, *p=&i, &r=i;
decltype(r+0) b; // b 的类型为int , 因为r+0运算后结果为int
decltype(*p) c = i; // c为int& 引用类型, 引用类型必须初始化. 如果是解引用 则使用引用类型
decltype(i) d != decltype((i)) d // 右侧永远是引用, 此处是int&, 左侧是int类型
decltype(a = b) d2; // 错误的声明. 赋值表达式 是引用类型, 引用必须初始化.
decltype 和 auto的区别
decltype 会返回一个变量的类型 包括顶层const, auto 不会
decltype 如果表达式解引用, 则会得到引用类型
decltype 加不加内部括号 影响重大, 赋值表达式会返回引用.
auto 必须要有初始值, 表达式的值会初始化变量
结构体(以前c中的称呼) c++中也叫类 struct
struct xxx {} xxxName1, xxxName2, *xxxPtr;
预处理器, 用于处理头文件被多次包含的问题
#define 预处理变量一般全大写, 用于检查下面的代码是否已经定义. #ifdef, #ifndef #endif
预处理变量 无视 作用域
这章好难~ 没个几遍 真的没法掌握