C++
开始
IO操作时,输入和输出数据会保存在缓冲区中,可以显示刷新缓冲区,读取cin会刷新cout,程序异常终止也会刷新cout。
endl可以结束当前行,并将缓冲区内容刷新到设备中。
while(std::cin >> value)
循环执行过程中,不断检测流的状态,遇到错误或者文件结束符才会结束,Linux中的文件结束符是Ctrl+D。
变量和基本类型
| 类型 | 最小宽度 |
|---|---|
| bool | 未定义 |
| char | 8位 |
| wchar_t | 16位 |
| char16_t | 16位 |
| char32_t | 32位 |
| short | 16位 |
| int | 16位 |
| long | 32位 |
| long long | 64位 |
| float | 6位有效数字 |
| double | 10位有效数字 |
| long double | 10位有效数字 |
char16_t和char32_t用于Unicode字符集,wchar_t可以存放最大的扩展字符集。
类型的选择规则:
- 知道非负,使用无符号类型
- 整数运算用
int,short太短,long一般和int一样 - 算数表达式不要用
bool和char - 浮点运算用
duoble,比float精度高速度快
unsigned char c = -1;
c的范围是0~255,给他赋了这个范围外的值时,实际值为对256取mod,即-1 % 256=255。
无符号数与有符号数相加时,有符号数会转换成无符号数,严禁混用。
八进制数以0开头。
数字-42的字面值是不包括-的,也就是说十进制数字面值不会为负。字面值的类型是可以指定的:
L'a' // wchar_t类型
u8"hi!" // utf-8字符串
42ULL // unsigned lon long类型
1E-3F // float类型
3.14159L // long double类型
字面值在选择所占空间时,尽量找最小的,存不下再找大的,如后缀有U,会在unsigned int、unsigned long、
unsigned long long中选择最小的。
字符串太长可以这样分开书写:
std::cout << "a"
"b"
"c";
nullptr是指针字面值。
初始化几种形式:
- int a = 0;
- int a(0);
- int a = {0};
- int a{0};
使用大括号的初始化方式由C++11支持,用他初始化内置变量时,如果存在丢失信息风险,就会报错,如用double初始化int。
默认初始化的值通常由变量类型决定。定义在函数体内部的内置变量不会被初始化,值未定义。尽量给每一个内置类型初始化。
声明与定义是有区别的,声明只是让程序知道存在这个名字,定义会将名字与值关联。仅仅需要声明使用extern。
extern int i; // 仅声明
int j; // 声明加定义
当需要在多个文件使用同一个变量,只能在一处定义,可以在多处声明。
当局部变量覆盖全局变量时,仍要访问全局变量可以用::变量名访问。不过尽量不要与全局变量重名。
int global = 1;
int main(void) {
int global = 2;
std::cout << ::global << global; // 12
return 0;
}
引用类型定义时必须用对象初始化,而且引用的类型要一致,引用会与对象一直绑定在一起。引用只是一个别名,不是对象,所以不能定义引用的引用, 不过可以用引用初始化引用,即都绑定到同一个对象。
int a = 1;
int &b = a;
int &c = b;
b和c都与a绑定,他们的值会同时改变。
引用的定义:
int a = 1;
int &b = a, c = a, &d = a;
b和d是引用,c是int。
引用不能用字面值和表达式初始化,而且引用要与被引用类型完全匹配
指针与引用不同,他是对象,可以进行赋值和拷贝。指针与指向的变量类型也应该完全匹配。
int i;
int *p = &i;
指针的值有4种:
- 指向一个对象
- 指向对象所占空间的下一个位置
- 空指针
- 无效指针
定义空指针的方法:
int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL;
这三种作用相同,尽量使用第一种。
void*这种指针可以指向任意对象的地址,但是不能用*解引用,只能进行比较,函数输入输出或者赋值给另一个void*。
int i = 4;
int *p;
int *&r = p; // 从右往左读
r = &i;
*r = 0;
r是一个引用,引用的是一个指针,指针是int指针。
const对象必须初始化,可以通过函数的返回值初始化。
// const.cc
extern const int buf = fcn();
// const .h
extern const int buf;
要在多个文件间共享变量,需要在定义前加extern。
可以为const变量创建引用:
const int c = 707;
const int &rc = c;
可以用const和非const变量初始化const引用,但不能用非const变量初始化const引用。
在这一点上,指针与引用类似。
指针是一个变量,所以存在常量指针,即始终指向某个地址:
int i = 707;
int *const a = &i;
const int *const b = &i;
为了区分两个const分别定义,顶层const表示任何对象是常量,底层const表示指针或引用指向的对象是常量。
int i = 0;
int *const p1 = &i; // 顶层
const int ci = 707; // 顶层
const int *p2 = &ci; // 底层
const int *const p3 = p2; // 左边是底层,右边是顶层
/**
* 拷贝时,顶层不受影响,const可以拷贝给非const
*/
i = ci;
p2 = p3;
/**
* 而底层必须相同才能拷贝,这两种都不能,不管顶层是什么
*/
int *p = p3;
int *const p4 = p3;
常量表达式指值不改变,并且在编译时就能定下来的表达式。比如const int sz = get_size()就不是。
通过定义constexpr变量可以让编译器判断表达式是否是常量。改为constexpr int sz = get_size(),
当get_size是一个constexpr函数就不会报错。constexpr指针必须初始化为nullptr或
指向存储在固定地址中的对象。
特别要注意的是constexpr int *p = nullptr是定义了一个常量指针,对象被定义为顶层const。
constexpr const int *p = nullptr表示指向整数常量的常量指针。
类型别名有助于程序理解,有两种定义方式:
typedef double wages;using SI = SalesItem;
在声明指针类型别名时要注意:
typedef char *pstring;
const pstring cstr = 0;
pstring是指针类型,const表示的是常量指针,而不能这样替换理解const char *cstr = 0。
auto一般会忽略顶层const,保留底层const。
const int ci = 1, &cr = ci;
auto a = ci; // 整型
auto b = cr; // 整型,顶层const,因为cr是ci的别名
auto c = &ci; // 指向整数常量的指针
const auto d = ci; // 明确顶层const
// 同时用引用和指针时,类型要相同
auto &m = ci, *p = &ci;
decltype可以用来计算表达式类型
decltype(f()) sum = x; // sum的类型是函数f的返回值类型
decltype能够得到变量的顶层const和引用
const int ci = 0, &cj = ci;
decltype(cj) y = ci; // y的类型是const int &
decltype(cj) z; // z是引用必须初始化
这里的引用cj就是引用,不是作为别名处理的。
int i = 707, *p = &i, &r = i;
decltype(r + 0) b; // int
decltype(* p) c; // int&
r是引用,经过计算后变为int类型。*p是解引用,c就是引用类型。
当变量有双括号时都是引用类型,因为变量是一个左值,加括号代表是一个表达式,可以作为赋值的左值。
decltype((i)) d; // 引用类型
创建自定义类型时,新标准规定,可以为数据成员提供初始值。
struct S {
int a = 0;
double b = 1.0;
}; // 记得结尾加分号
类的定义、const变量、constexpr变量通常放在头文件中,头文件还可包含其他头文件,头文件应该与类的命名相同。
在使用#include时,预处理过程中会用头文件的内容替换#include。
为了避免重复包含,可以用头文件保护符:#define、ifdef、ifndef、endif。
#ifndef PERSON_H
#define PERSON_H
#include <string>
struct Person {
std::string name;
unsigned age = 0;
};
#endif
即使头文件没有被包含,也应该加上保护符,说不定哪天就被使用了。
字符串、向量和数组
作用域操作符告诉编译器,从操作符左侧的作用域寻找右侧的名字,如:std::cin就是从命名空间std中寻找cin。
头文件中不应包含using,因为头文件的内容会被引用时拷贝到另一个文件,多个引入可能会导致命名冲突。
string初始化方法:
string s1;
string s2(s1);
string s3("value"); // 直接初始化,直接在创建时设置了值
string s3 = "value"; // 拷贝初始化,使用了=的都是拷贝初始化,将右值对象拷贝到左边
string s4(n, 'c'); // n个字符c
string.size()返回的类型是string::size_type。
string在进行加法操作时,加号两边有一个是string类型即可,另一个可以是字面值。
访问string的所以字符时,使用for(auto i : string),而其他情况使用下标取值。
vector初始化方法:
vector<T> v1;
vector<T> v2(v1);
vector<T> v2 = v1;
vector<T> v3(n, val); // n个val
vector<T> v4(n); // n个T类型的初始化对象
vector<T> v5{a, b, c...};
vector<T> v5 = {a, b, c...};
vector<int> vi = 10; // 错误,必须用直接初始化
// 易混淆的
vector<int> v1(10); // 10个0
vector<int> v2{10}; // 1个10
vector<int> v3(10, 1); // 10个1
vector<int> v4{10, 1}; // 1个10,1个1
如果向vector中添加删除元素,则不能使用for(auto i : v)。
vector下标不能用来添加元素,必须用push_back。
迭代器的类型有:
// 可读写
vector<int>::iterator it;
string::iterator it2;
// 只读
vector<int>::const_iterator it3;
string::const_iterator it4;
箭头运算符a->b相当于(*a).b。
凡是使用了迭代器就不要添加删除元素,这会使迭代器失效。
两个迭代器相减得到一个difference_type类型的带符号整数。
声明数组的大小时,变量的类型必须是constexpr:
constexpr unsigned sz = 707;
int *parr[sz]; // sz必须是constexpr
字符数组的初始化:
char a1[] = {'a', 'b', 'c'};
char a2[] = "c++"; // 最后会自动存储一个'\0'
复杂的数组声明:
int *ptrs[10]; // 10个整型指针的数组
int &refs[10] = /* ? */ // 错误!不能定义引用数组
int (*Parray)[10] = &arr; // 指向一个含有10个整数的数组
int (&arrRef)[10] = arr; // 引用一个含有10个整数的数组
int *(&arry)[10] = ptrs; // 从内向外阅读,一个引用,引用了一个含有10个整型指针的数组
数组名通常就相当于首元素指针:
string nums[] = {"one", "two", "three"};
string *p = &nums[0]; // 等价于 p = nums
auto a(nums); // a是string*类型,相当于a(&nums[0])
decltype(nums) b = {"one", "two", "three"}; // 这里的nums不是指针,而是数组,注意decltype
// 指针也是迭代器
string *b = begin(nums);
string *e = end(nums); // 指向数组下一个位置
auto n = end(nums) - begin(nums); // 得到ptrdiff_t类型的变量
string last = *(nums + 2); // 等价于nums[2]
指针可以和下标相换操作,是等价的;与string和vector不同的是,数组的下标可以是负值。
C风格字符串处理函数位于cstring.h中:strlen、strcmp、strcat和strcpy。
为了与旧的C风格字符串代码衔接,可以将string对象转换为字符串:
string s("hello");
char *str = s; // 不能用string初始化char*
const char *str = s.c_str(); // c_str返回一个字符串
可以用数组初始化vector,只需提供首元素和尾后元素的地址:vector<int> v(begin(arr), end(arr))。
多维数组初始化为0:int arr[10][20][30] = {0}。
多维数组使用范围for时,除了最内层可以不使用引用,其他层都需要,为了防止被转换为指针。
for (auto &row : ia) // 引用去掉row会被转成指针,内层循环会报错
for (auto &col : row)
;
多维数组名和指针:
int ia[3][4];
int (*p)[4] = ia; // 指向含有四个整数的数组
p = &ia[2]; // 指向ia第三行
使用auto可以使多维数组指针声明简化,在循环数组时,使用begin和end可以更加简洁。
可以用类型别名简化多维数组指针声明using int_arr = int[4]。
表达式
求值顺序与优先级和结合律无关,如f() + g() * h() - j()的表达式,函数的调用顺序是不确定的,
如果这几个函数不相关则不会有问题,要是有共享数据可能会出错。
运算符对bool类型进行操作时,将被提升为int类型。
bool b = true;
bool b2 = -b; // true