《重学C++》4. C++基础容器
1.数组下标非对称的原则
计算“满足x>=16且x<=37的x有多少个”时,容易计算成37-16=21个。
由于差一错误(off-by-one error),遍历序列容器、解决问题时最好使用数学上的左闭右开[a,b)的区间。
问题可以转换为:“满足x>=16且x<38的x有多少个”,此时计算得38-16=22个。
应该尽量全部转换为左闭右开[a,b)的区间,保证问题和解题的统一性,避免出错。
1 | //推荐方式 |
好处是:
- 上界永远>=下界;
- 便于计算 容量=上界-下界;
- 上界永远取不到值,能避免数组越界。
另外,我注意到C++中其他很多地方也是贯穿了这个原则,例如vector的begin()、end()函数,begin()是下界、可以取到,end()是上界、取不到。因此,和C++语言保持一致,统一用这种方式也更加便利。
2. 二维数组访问
因为cpu会将经常访问的内容放入cache,根据这个原理,在短时间内应该尽量访问相邻的空间。这就是为什么一般不会跨行访问(即外层循环行,内层循环列)。
此外,一般也将长的循环放在内层、短的循环放在内层。这样可以缩短cpu跨切循环层的次数。
3. vector
vector是面向对象方式的动态数组。
通过capacity和size函数,可以分别获取数组的容量(下次申请空间前,最多可以存储几个元素)和存储的元素个数(当前数组中有几个元素)。
string和vector类似,也有这两个函数,并且功能相同。另外还有一个和size函数功能相同的length函数。
4. 字符集
unicode是字符集,解决了自然字符数字化的问题;
UTF-8是编码方式,解决了数字字符在计算机中传播、存储的问题。
出现乱码的原因在于编码和解码方式的不统一。HTML、XML等文件能够被广泛传播的原因在于,他们文件一开头就指明了编码方式。而传统的文件并没有考虑到这点(但开头可能有big endian、BOM标记等)。
5. 字符串的数组表示和字符指针表示
对于下面这段代码,尽管他们都表示字符串“helloworld”,但底层原理并不相同。
1 | //第一行是字符串的数组表示、第二第三行是字符串的指针表示。 |
执行下面代码对各元素进行+1
1 | //strHelloWorld = pStrHelloWrold; 错误,strHelloWorld作为数组不可改变 |
字符数组StrHelloWorld不可变,strHelloWorld[index]的值可变;
字符指针pStrHelloWrold可变,pStrHelloWrold[index]的值可变不可变取决于所指区间的存储区域是否可变;
数组strHelloWorld将里面每个元素值+1。而尽管pStrHelloWrold一直是字符指针,但是对于它的两个不同的值,+1操作的结果是不同的。
当pStrHelloWrold指向”helloworld”,此时该指针指向字符串常量区域,对它的字符进行+1操作会引发写入访问权限错误;
当pStrHelloWrold指向strHelloWorld,此时该指针指向字符串变量区域,进行+1操作可以和数组strHelloWorld一样,将各字母再次增大1。
6. C字符串函数与缓冲区溢出
传统的strcpy、strcat等函数并不会在操作前对字符串长度进行越界检查,这就导致可能会出现缓冲区溢出,产生安全漏洞。
为了避免这种问题,应该尽量使用strnlen_s,strcpy_s,strncpy_s,strcat_s等API函数,更加安全。
7. string
C++中字符串string具有了更多功能,如动态分配大小、拷贝=、比较、拼接+等等。
string和vector类似,也有size和capacity两个函数,并且功能相同。另外还有一个和size函数功能相同的length函数。
需要注意的是,即使是未初始化的空字符串,其capacity也是大于0的。
在对性能要求高的场所,还是需要使用C字符串,string并不是万能的。(c_str对象函数可以返回C字符串的指针)。
并且,Redis(https://redis.io/)字符串的设计对C字符串空间和时间进行了折中。
另外,字符串的拼接不可以拼接两个字符串常量。
1 | string str = "abc"; |