C++语法复习
运算符操作
- 从左往右多个连续不等号的时候是从左往右依次运算的
- 连续的多个赋值操作是从右往左的
- 移位运算最好针对无符号数使用,否则可能因为使用1填充高位导致出错
sizeof
- 是个运算符
- 用
sizeof计算数组大小的时候会返回整个数组的大小而不是指针占的空间
- sizeof用在
string或者vector之类的数据结构的时候只考虑这些机构固定部分的内容,不考虑其中可变元素的体积
逗号运算符
- 含有两个运算对象,从左往右运算
int a, b;
for(;;++a, ++b) {}
|
- 返回的是右侧表达式的值
对无符号数的操作
- 假如对无符号数赋值负数的话,得到的结果是这个负数对无符号数最大值(2的位数次方)求模的结果
- 数字溢出的话是未定义行为,程序也许会崩溃
- 有符号和无符号的数字不能简单相加,会先把有符号数字转化为无符号数字,编码不同
- 假如有符号数字是负数的话,会变为前面说的对最大值求模的结果,再相加,以至于结果错误
不同数字
- 十进制:直接写
- 八进制:开头有个0,比如
024
- 16进制:
0x15
- 浮点数可以写
3.14, 3.14e5, .314等等转义序列
- 换行
\n, 制表符横向\t, 报警符号\a, 纵向制表符\v, 退格符\b, 双引号\", 反斜线\\, 问号\?, 单引号\', 回车符\r, 进纸符\f
- 如果
\后面是x说明是16进制数字转义,如果没有x说明是八进制的,\115和\x4d一样
switch case
- case后面的必须是常量,不能是变量
- 不可写初始化的语句
强制类型转换
static_cast<type>类似于C的强制类型转换
dynamic_cast<type>比static安全一些
const_cast<type>转化的时候只能去掉底层的const不能改变类型,但是可能导致未定义的行为
reinterpret_cast<type>对运算对象按底层位进行重新解释
- 旧式的强制转换,少用
字符串换行
cout<<"Hello World"<<endl;
cout<<"Hello " "World"<<endl;
|
字面值的类型(支持大写或小写)
- 后缀
u是无符号
- 后缀
L是long,小数后缀L是long double
- 后缀
ll是long long
- 小数后缀
f是浮点数变量的初始化
int a = 0; int a = {0}; int a{0}; int a(0);
|
- 调用
new int;创建的时候,假如不加括号的话是个未初始化的int,加括号的话是0
- 列表初始化对类型检查比较严格,比如用
long double初始化int的时候就会报错,但是用传统的 初始化方式不会报错
- 一个int类型的变量,再函数体内部(包括main),不会初始化
- 但是假如不在任何函数内部,会初始化为0
- 赋值操作同样可以用花括号,这样的话会严格检查是否有数据丢失,比如舍弃小数位等
变量的声明和定义
- 如果想声明一个变量而不是定义(在别处定义过),那就在变量前面添加
extern关键字并且不要赋值
extern int a = 0;这句话是定义,但是不能放在任何函数的内部,这个语法的意思是这个变量是给别的文件中的人用的,放在函数中会导致这个变量只能在该函数的作用域中使用,无法被他人看到
- 但是如果将
extern的位置替换为extern,会使其无法被其他文件使用extern应用
数据类型 |字节数(16位编译器)| 字节数(32位编译器) 字节数|(64位编译器)| 总结
|—|—|—|—|—|
char |1| 1 |1 |char一直占用1个字节
char* |2 |4 |8 |char* 是指针位宽,N位系统含有N个位,占用N/8个字节
short |2 |2 |2 |short一直占用2个字节
int |2 |4 |4 |int在x86和x64都是4个字节
unsigned int |2 |4 |4 |无符号int与int本身的占位一样,不区分编译器
long |4 |4 |8 |long一直等同于float
long long|8 |8 |8 |long long一直等同于double
float |4 |4 |8 |float由于带浮点至少需要4字节,在x64编译器里是8字节
double| 8 |8 |8 |double一直占8个字节
标识符
- 只能用数字,字母和下划线,数字不能开头
- 不能用C++自己的关键字冲突
- 不能与操作符的替代名冲突
引用类型
- 引用类型只能引用左值
- 引用必须初始化
- 不存在指向引用的指针
- 常量类型的引用必须也是常量
- 如果引用类型嵌套的话,上层的引用类型也会绑定到最底层被引用的对象上
- 指针的引用
常量
- 默认情况下,const对象只对当前文件生效,如果希望在其他文件中使用,必须使用extern声明
指针
- 指针只能指向与自己类型符合的变量
void*类型的指针可以指向任何类型int *const a = &b; const int * a = &b;
|
不可以对临时对象取地址,比如&(&a)
- 从右往左看,const离谁近,谁就是const
常量表达式
- 编译过程就能知道结果并且值不会改变的式子
- 可以在语句前面加一个
constexpr修饰
- 常量和指针
- 被引用的变量必须定义在函数体外面,否则会导致变量生命周期提前结束
typedef
typedef a b;是b定义为a的别名
typedef a *b是将b定义为a*
- 注意使用
const关键字的时候,用法的不同特性
typedef A B;和using B = A;等价typedef char* cstr; const cstr a; const char* b;
|
auto
- 让编译器使用初始值判断类型
- 不可以使用
auto i = 0, p = 3.14;二者类型不能不同
- 用auto推导引用类型的时候会给变量设置为引用原本的类型,不会设置为引用
- 推导const类型的指针的时候会保留const
- 但是推导用const类型初始化的变量的时候不会保留const
- 可以手动指定
const或者`&
- 可以写
const auto & i = 42;
decltype
- 类型分析符
int i = 0; decltype(i) a = 1;
|
*p解引用会被认为是对应基础类型的引用类型
int i是int类型,但是假如decltype((i))或者是decltype(((i)))都是int的引用类型,不存在引用的引用,无论嵌套多少层都是引用
引用类型
struct
- 默认是public
- C++11开始,定义的时候可以对其中的变量赋初值
- 声明成员类型的时候要用分号
命名空间
#include <iostream> using std::cin; using std:: cout;
|
for循环的执行顺序
- 循环
cin如何判断输入已经结束
=
- cin的返回值可以拿来判断用户是输入了还是退出了
- 遇到
Ctrl Z之类的EOF符号或者是输入的值与目标变量类型不匹配(比如要求int遇到字符)也会退出
数组
- 数组不可以使用auto推断类型
- 数组的长度必须是
constexpr
- 可以不给大小,初始化的时候使用花括号,让编译器推断大小
- 数组部分没给初始值的时候,会是0或者空字符串
- 数组初始化不可以拷贝
- 不存在引用数组
int (*arr)[10] = &array; int (&arr)[10] = array; int *(&arr)[10] = ptrs;
|
- 判断的时候从右往左,从里往外,离谁近先结合谁,先跟右边的结合再考虑左边的,结合后的部分可以删去,方便判断
decltype和auto推断类型的不同
int a[10]; auto a1(a); decltype(a) a2;
|
函数传参的时候传递数组的引用
- 形参设置为
int (&a)[10]
- 此时在函数内部可以像使用数组一样使用参数,比如使用范围for循环等等
- 而不会退化为指针
- 注意传递多维数组的时候除了最高维度别的必须给出明确大小
int matrix [][10]或者int (*matrix)[10]
借用迭代器找到数组头尾
#include <iterator> int ia[] = {0, 1, 2, 3}; int *beg = std::begin(ia); int *end = std::end(ia);
|
- 数组的下标可以是负数
多维数组
- 对多维数组非最低维度直接求加法,会跳行而不是连续往前
- 也可以用范围for
int a[10][10]; for(auto& row:a) { for(auto& col:row) { } }
|
- 上述代码必须用
auto&,不使用的话auto会将类型推断为一个int指针,导致下一层范围循环无法使用C风格的字符串
- 操作在
<cstring>头文件中
- 使用头文件中操作函数操作的话必须保证字符串是以
\0结尾的
string类型的.c_str()函数会返回一个c风格的字符串
- 注意假如之后修改了string的话,会导致这个字符串失效
STL库函数
swap
swap(a, b)交换两个容器的元素,比拷贝快很多assign
- 清除原有内容
- 接受两个参数,开始迭代器和结束迭代器
- 或者接受一个初始化列表
- 或者接受n和k两个参数,表示n个k元素,类似于赋初值的操作
unordered_map
- 初始化
- 可以用
{{key, val}, {key, val}, ...}的形式初始化
unordered_map不能在使用for (auto &i : m){m.erase()}
|
类型的循环中删除元素,否则可能会因为删除了i指向的元素导致迭代器失效,引起内存错误
- 安全的删除方式为
for (auto it = m.begin(); it != m.end();) { it = m.erase(it); }
|
- 对于方法erase而言
iterator erase(const_iterator position):删除迭代器 position 指向的元素,并返回指向下一个元素的迭代器
size_type erase(const key_type& k):删除容器中键为 k 的元素,并返回被删除的元素个数
iterator erase(const_iterator first, const_iterator last)删除迭代器范围 [first, last) 内的所有元素,并返回指向最后一个被删除元素之后的元素的迭代器
template< class K > size_type erase( K&& x ):C++23 引入了这种重载形式,它可以删除容器中与 x 等价的所有元素,并返回被删除的元素个数
- 可以直接使用
[]操作尚未添加的键的值,这样会导致这个键对应的值先被初始化位默认值(比如int的0),然后再进行对应的操作
其他获得某个元素的方式
find(key)返回的是迭代器,不存在就指向的是尾后遍历map
for (auto it = m.begin(); it != m.end(); it++) { cout << it->first << " " << it->second << endl; }
|
- 随机获得容器中的第某个元素
unordered_set<int> um; auto it = um.begin(); advance(it, rand() % um.size()); return *it;
|
- 注意此处不能直接it+数字,会出错,需要使用
advance函数`vector
- 使用
vector(对象值, 对象数量)初始化vector的时候,会创建一个对象然后调用复制构造函数创建剩余的初始化
vector<int> v{1, 2, 3, 4};
|
大小比较
- 逐个元素比较,都相同的时候看谁先没,没了的小
- 前提是容器存储的元素本身是可以比较大小的
迭代器
- 操作与指针类似
- 不支持递减,只能递增
vector.cbegin()和vector.cend()是常量迭代器,不能修改其中的内容
- 任何可能改变vector元素
capacity(容量)的操作都会使得迭代器失效,因为内存位置变了
- 迭代器可以像指针一样做差,得到的结果是相差的元素的个数
insert
- insert会返回插入的元素的迭代器
- 可以向当前尚且不存在的位置(比如尾后)插入元素
- insert也可以用初始化列表的方式插入,只需指定位置和初始化列表即可
- 也可以给定插入位置,迭代器开始和迭代器结束实现同样功能
erase
- 删除某个迭代器指定位置的元素
- 删除某两个迭代器指定范围的元素(不包括最后一个迭代器指定的元素)
- 返回被删元素后的第一个元素
clear
- 清空
resize(size, val)
- 假如超出的话,截断后面的元素
- 假如没超出的话,用val填充多出来的
- 可以用来增加大小但是不改变原来的元素
- 但是此时其中的元素是真实存在的,不只是扩大了占用的内存空间
reserve(size)
- 这个函数会增加容器的容量而不会改变其
size,只是在内存为其分配更多的空间
- 容器的实际长度不会发生改变,只是向其中添加元素的时候不需要增加额外的空间了
emplace_back
- 可以给定参数调用容器存储的类型的构造函数构造对象插入
back()函数
- 返回的是尾元素的引用
front()函数
- 返回的是首元素的引用
at(n)函数
- 返回的是下标为n元素的引用
其他技巧
- 提取一个数字最右侧为1的位:
i & (~i + 1)
cpp如何初始化一个类的静态数据成员
- 类似于初始化全局变量
class MyClass { public: static int myStaticVar; };
int MyClass::myStaticVar = 0;
|
- 注意在一个文件中定义的类静态变量在其他文件中使用的时候需要先用
extern声明string的一些用法
其他的初始化方法
string(<char array>, i)从字符数组中拷贝i个字符
string(s, pos)s从pos开始的字符的拷贝
string(s, pos, len)s从pos开始长度为len的字符串初始化某个重复多次的字符
整数或者小数转换为字符串
#include <iostream> to_string(<int or double>);
|
字符串转化为整数或小数
| 函数 |
转化方式 |
| stoi(s,p,b) |
把字符串s从p开始转换成b进制的int |
| stol(s,p,b) |
把字符串s从p开始转换成b进制的long |
| stoul(s,p,b) |
把字符串s从p开始转换成b进制的unsigned long |
| stoll(s,p,b) |
把字符串s从p开始转换成b进制的long long |
| stoull(s,p,b) |
把字符串s从p开始转换成b进制的unsigned long long |
| stof(s,p) |
把字符串s从p开始转换成float |
| stod(s,p) |
把字符串s从p开始转换成double |
| stold(s,p) |
把字符串s从p开始转换成long double |
取子串
string.substr(startPos, length); string.substr(startPos);
|
删除特定位置的字符(串)
string.erase(startIndex, length);
|
在特定位置插入字符
string.insert(index, <个数>, <字符>); string.insert(index, <string>); string.insert(index, <string>, begin, len);
|
寻找字符串位置
find(s, index)函数。从pos开始查找字符或者字符串在哪,返回第一次出现的下标(index默认是0,可以不给)
- 没找到的话会返回一个
string::npos插入字符串
string.insert(index, string);
|
字符串字面量不支持直接相加,必须有至少一个string类型的量才行
lower_bound函数
lower_bound函数在一个有序的序列中查找不小于给定值的最小值
- 底层是二分查找
- 如果没找到,会返回数组尾迭代器
class Solution { public: bool searchMatrix(vector<vector<int>>& matrix, int target) { for (const auto& row: matrix) { auto it = lower_bound(row.begin(), row.end(), target); if (it != row.end() && *it == target) { return true; } } return false; } };
|