本文原载于https://imlogm.github.io,转载请注明出处~
摘要:记录在使用C/C++时遇到的常见问题:友元,计时,cout与printf,结构体内存对齐,虚函数,派生类,auto关键字,指针的sizeof,短路求值,qsort与sort,类的拷贝构造&&等号赋值&&深拷贝&&浅拷贝,输入函数cin
关键字:C/C++
1. 友元
关于友元函数,可以看这篇文章:【C++基础之十】友元函数和友元类-偶尔e网事
文章里说的友元的缺点是破坏封装性比较好理解,友元的优点是提高程序运行效率这点我没有太想明白。(为什么我感觉友元的优点是让程序员能少写一点代码?)
文章最后提到友元不具有继承性和传递性,记一下就好。实际编程时,我还没有遇到使用友元的情况。
2. 计时
在调试程序的时候,我们经常需要记录某一段代码运行的时间。我一般是使用clock()
来计时的。示例代码如下:1
2
3
4
5
6
7
8
9
10
11
int main() {
clock_t start = clock(); // clock_t 其实是long int
for (int i=0; i<1000000; i++); // 需要计时的程序段
clock_t end = clock();
double time_used = 1.0 * (end - start) / CLOCKS_PER_SEC;
printf("程序用时: %.5f秒", time_used);
return 0;
}
如果有过硬件开发经验的同学应该能明白,函数clock()
获取的是滴答计数器的值。可以形象地理解:计算机的底层(或硬件或软件模拟)能不断地发出滴答声,并且滴答声的频率是稳定的,每秒发出CLOCKS_PER_SEC
次滴答声。程序一开始运行,就有一个计数器启动,不断记录它听到的滴答声的次数。clock()
获取到的就是这个计数器的值。clock() / CLOCKS_PER_SEC
得到的就是从程序开始运行到当前经过几秒。
在我的电脑上,CLOCKS_PER_SEC
=1000000,类型clock_t
是long int
的别名,在不同的硬件上,这些值不一定相同。
注意:这种计时方式有最大可计时时间。我们可以算一下,
long int
型最大为$2^{31}$,$2^{31} \div 1000000=2147$,2147秒大约是35分钟。也就是说程序运行后,每过大约35分钟,clock()
取到的值会再次回到0。网上也有教程说最大计时时间为72分钟,可能是当成了unsigned long在算了。
3. cout与printf
cout比printf要方便一点,因为可以不用写格式控制字符串%d
、%s
等。重载运算符后也能让cout输出非基本类型的变量。
但是printf比cout在IO效率上要更高一些,当然也有方法可以弥补这点,可以看这篇文章:cin与scanf cout与printf效率问题
4. 结构体内存对齐
这个涉及到比较底层的问题,用来解释sizeof(结构体)
得到的结果为什么和直观想的不一样。可以看这篇文章:如何理解 struct 的内存对齐?-Courtier的回答
注意,形似下方代码的东西不是普通结构体,是位域
,它的内存占用分析更复杂,这里就不展开了:
1 | struct bs // 位域 |
5. 虚函数
定义一个函数为虚函数,不代表函数为不被实现的函数。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。纯虚函数定义如下:1
virtual void fun()=0;
6. 关于派生类
- 公有继承的公有成员还是公有的,可以被访问
- 公有继承的私有成员不被继承,所以不能访问
- 公有继承的保护成员可以被类的方法访问,不能被对象访问
- 私有继承的公有成员会变成派生类的私有成员,也不能被访问
7. auto关键字
auto关键字在C++11以后有了不同于以前的含义。以前,auto关键字用来表示该变量是具有自动存储期的局部变量。如下面代码所示:
1 | // C++11标准以前,auto关键字的用法 |
C++11以后,auto表示该变量的类型由编译器推理。如下面代码所示:
1 | // C++11标准以后 |
新标准下的auto主要在定义STL的迭代器时用到,因为这些迭代器类型名字都太长了(比如std::vector<std::string>::iterator
),使用auto关键字可以少打很多字。
8. 指针的sizeof
这其实是一个值得注意的地方,因为老教材通常默认是32位机器。32位程序,sizeof(指针)=4;64位程序,sizeof(指针)=8
1 |
|
9. 短路求值
在用到&&
和||
时会有短路求值的情况出现。比如下面的代码:
1 | // && 的短路求值示例 |
在代码执行的效率上说,短路求值能避免一些无意义的计算,但是短路求值也会有坑。
假如函数isRed(a)
会对全局变量进行操作,或者有指针操作,就会出现问题,比如下面的代码:
1 | int red_count = 0; // 全局变量,记录红色物品的数量 |
应该不难看出上述代码的问题出在red_count
,由于短路求值,isRed(a)
仅当isClothes(a)
为true
时执行,也就是说red_count
只记录了红色衣服的数量,而我们本来是希望记录所有红色物体的数量的。
有可能会有同学说,上面的代码逻辑不规范,我自己写肯定不会这样统计红色物体数量。是的,一般都不会这么写代码,我这边这样写是为了说明短路求值会带来的问题。实际情况中,往往是涉及指针操作时会出现。
一些“简洁精妙”的代码,以“行数少”著称。它们&&
和||
两边的函数往往是精心设计,不能调换次序的。我们平时写代码的时候,没必要刻意追求“行数少”。如果“行数少”,但复杂度还和原来一样,那么这样的“行数少”有什么意义呢?反而造成阅读代码的困难。我们真正要追求的是复杂度的降低,和代码的易阅读性。
10. qsort与sort
qsort与sort都是C/C++中的排序函数,其中qsort属于库stdlib.h,sort属于库algorithm,它们都只能对连续内存的数据进行排序(比如数组等),对不连续内存的数据不能排序(比如链表)。
有关这两个函数的用法,请看:qsort函数、sort函数-JokerSmithWang的博客
11. 类的拷贝构造&&等号赋值&&深拷贝&&浅拷贝
这块内容需要一定的时间消化,参见:C++ 拷贝构造函数和赋值运算符-Brook的博客
12. 输入函数cin
平时读取字符串一直用的是std::cin >> str
读入输入的,但是这种方式如果字符串里有空格,那么会把空格视为分隔符,把原字符串分成多个字符串。如果要实现读取带空格的字符串,可以使用std::cin.getline(),具体使用方式可以参见:C++输入cin,cin.get(),cin.getline()详细比较及例子-Mac Jiang的博客