小心笔试中的小陷阱(一)持续修改中~~

来源:互联网 发布:动漫软件 编辑:程序博客网 时间:2024/05/22 17:01

1.巧分sizeof(char a[])和strlen(char a[])

strlen():求的是字符串的实际长度,即不包括“\0”的长度,如strlen(“hello”)=5,而不是6,而且要特别注意如下情况:char a[]="hello\0 world"; strlen(a)=5 而不是12,因为字符串里含有"\0",strlen()在计算字符串长度的时候遇到"\0"就结束计算了,所以只有“hello”的长度被读取了。

sizeof()就与此不同了,sizeof()求的是其参数实际所占的存储空间大小,所以在计算字符数组所占空间的时候,会把“\0"也给包含进去,即sizeof(a)=12+1=13(注:“\0”算是一个字符,不要傻傻的当做是“\”和“0”两个字符哦)

再有就是当数组a当形参的时候,a退化成指向数组首个元素的指针,即传的不是数组本身,而是数组地址,所以sizeof(a)求的是指针所占空间的大小,结果或者为4(32bit机)或者为8(64bit机),具体可参照文章“数组形参”。

2.extern vs static

3.左值 VS 右值

区分左值和右值最简单的一个方法就是看能否都表达式取地址,如果可以就是左值,不行就是右值。a++是先取出持久对象a的一份拷贝,再使持久对象a的值加1,最后返回那份拷贝,而那份拷贝是临时对象(不可以对其取地址),故其是右值++a则是使持久对象a的值加1,并返回那个持久对象a本身(可以对其取地址),故其是左值;a+b是临时对象(不可以对其取地址),是右值。m是一个常量引用,引用到一个右值,但引用本身是一个持久对象(可以对其取地址),为左值。

4.引用

参考文献:http://www.cnblogs.com/hujian/archive/2012/02/13/2348621.html

区分左值、右值的最佳方法是看能不能对表达式取地址。

a++是右值,++a是左值

我们平时说的引用一般是左值引用,不过C++11引用了右值引用的概念,我们先来研究左值引用

常量左值引用 VS 非常量右值引用

非常量左值引用只能绑定到非常量左值,不能绑定到常量左值、非常量右值和常量右值。如果允许绑定到常量左值和常量右值,则非常量左值引用可以用于修改常量左值和常量右值,这明显违反了其常量的含义。如果允许绑定到非常量右值,则会导致非常危险的情况出现,因为非常量右值是一个临时对象,非常量左值引用可能会使用一个已经被销毁了的临时对象。

常量左值引用可以绑定到所有类型的值,包括非常量左值、常量左值、非常量右值和常量右值。

左值引用 VS 右值引用

5.类内存分配+内存对齐

http://hi.baidu.com/lovestartian/item/5e10f99e1b1faedb1e4271a3

http://my.oschina.net/u/164131/blog/28391

http://blog.csdn.net/haoel/article/details/3081328

注意空类http://blog.csdn.net/hitblue/article/details/3726754

内存对齐:http://blog.csdn.net/cuibo1123/article/details/2547442

#pragma pack:http://www.cppblog.com/range/archive/2011/07/15/151094.html

6.C语言的结构体中只能定义成员变量,不能定义成员函数,但是c++中可以,但是也是针对命名结构体的,C++中匿名结构体也只能定义变量。

7.枚举类型enum

不能改变枚举成员的值,枚举成员本身就是一个常量表达式。注意:把整数赋给枚举类型对象是非法的,哪怕此整数是和该枚举类型的成员相关联。

enum Forms{False,True}; //不要忘记“;”void Print(Forms f); //形参是Forms类型Print(0);  ///errorForms n=False;Print(n);  //ok

8.void & void*

相关链接:http://blog.csdn.net/yming0221/article/details/6249151

void即“无类型”,void *则为“无类型指针”,

void* 可以指向任何数据类型,亦即可用任意数据类型的指针对void指针赋值。但是如果将void*赋值给其他类型,则需要强制类型转换。

void* pVoid;int* pInt;pInt=(int*) pVoid;PVoid=pInt;

在ANSIC标准中,不允许对void指针进行算术运算如pvoid++或pvoid+=1等(VS2012中就不能进行以上操作),而在GNU(g++)中则允许,因为在缺省情况下,GNU认为void *与char *一样。sizeof(*pvoid )== sizeof( char)。在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:

void * pvoid;(char*)pvoid ++;//ANSI:正确;GNU:正确(char*)pvoid += 1;//ANSI:错误;GNU:正确

当函数不需要返回值时,必须使用void限定。例如: void func(int, int);当函数不允许接受参数时,必须使用void限定。例如: int func(void)。可以用void指针来作为函数形参,这样函数就可以接受任意数据类型的指针作为参数。即如果函数的参数可以是任意类型指针,那么应声明其参数为void*。

在C语言中,可以给无参数的函数传送任意类型的参数,但是在C++编译器中编译同样的代码则会出错。在C++中,不能向无参数的函数传送任何参数。所以,无论在C还是C++中,若函数不接受任何参数,一定要指明参数为void。

void不能代表一个真实的变量,void体现了一种抽象。

9.亲,int(*b)[10][10],b是指向二维数组的指针哦,所以sizeof(b)=4或者8呢。

10.原子操作就是不可再分的操作,谨记这句话,当无法区分多线程中哪些操作是原子操作时,就看看此些行为是否可以再分,如果可以,那么就不是了。。。典型的非原子操作:x=y(1.y到寄存器,2.将y值写入x);x++(1.读x到寄存器,2.在寄存器内将x加1,3.将值写入x);++x;可以反汇编一下看看这些操作具体干了些啥。x=1是原子操作哦,直接将1的值写入x了。

11.小心union了
http://www.cnblogs.com/xdotnet/archive/2011/04/20/cpp_union.html
union主要是共享内存,分配内存以其最大的结构或对象为大小,即sizeof最大的。在C/C++程序的编写中,当多个基本数据类型或复合数据结构要占用同一片内存时,我们要使用联合体;当多种类型,多个对象,多个事物只取其一时(我们姑且通俗地称其为“n 选1”),我们也可以使用联合体来发挥其长处。

union myun {struct { int x; int y; int z; }u; int k; }a; int main() { a.u.x =4;a.u.y =5; a.u.z =6; a.k = 0; printf("%d %d %d\n",a.u.x,a.u.y,a.u.z);return 0;}

union类型是共享内存的,以size最大的结构作为自己的大小,这样的话,myun这个结构就包含u这个结构体,而大小也等于u这个结构体 的大小,在内存中的排列为声明的顺序x,y,z从低到高,然后赋值的时候,在内存中,就是x的位置放置4,y的位置放置5,z的位置放置6,现在对k赋 值,对k的赋值因为是union,要共享内存,所以从union的首地址开始放置,首地址开始的位置其实是x的位置,这样原来内存中x的位置就被k所赋的 值代替了,就变为0了,这个时候要进行打印,就直接看内存里就行了,x的位置也就是k的位置是0,而y,z的位置的值没有改变,所以应该是0,5,6。
union经常和Little endian /Big endian 联系在一起,在确定union类型数据输出数据时要考虑CPU是哪种模式。Little endianBig endian 是CPU 存放数据的两种不同顺序。对于整型、长整型等数据类型,Big endian 认为第一个字节是最高位字节(按照从低地址到高地址的顺序存放数据的高位字节到低位字节);而Little endian 则相反,它认为第一个字节是最低位字节(按照从低地址到高地址的顺序存放数据的低位字节到高位字节)。x86 系列CPU 都是little-endian 的字节序,PowerPC 通常是Big endian,CPU 能通过跳线来设置CPU 工作于Little endian 还是Big endian 模式。如在x86-32位机上运行:
union myun {int k;short m;}a; int main() { a.k=0x0fffffff;a.m=0x1234;printf("%x",a.k);system("pause");return 0;}

因为m是short型,占两个字节,k是int型,占4个字节,所以m只覆盖了a的前两个字节,即成员k的两个低地址字节0xff和0xff,所以a在内存中的存储从低到高变为 0x34,0x12,0xff,0x0f,即打印出fff1234。同理,代码改为如下形式:

a.m=0x1234;a.k=0x00ff3456;printf("%x",a.m);

则输出3456。注意,下面这个就不一样了呢:

a.m=0x1234;a.k=0x0fffffff;printf("%x",a.m);

不会输出ffff哦亲,因为ffff已经超出short型能表示的最大数0x7fff了,所以输出了个ffffffff!
可以使用union结构来判定 CPU是哪种模式,如下代码:

int checkCPU(){  {    union w    {        int a;        char b;    } c;    c.a = 1;    return (c.b == 1);    }}

12.哈希表

哈希查找的平均查找长度(不管是用拉链法还是线性探测法来解决冲突问题)是和其装填因子a(填入表中的记录个数/表的长度)有关的,肯定是装的越满,查找长度越长。题目可能会给出哈希函数和装填因子大小,让你给给定序列构造哈希表,并对其求平均成功和不成功的查找长度。例子中计算查找不成功的方法就是如果查找到一个位置没有数据了,那就说明没有要查找的元素,但是这种情况没有考虑这个位置或许之前有数据,但是后来删除了,而其后位置还是可能会有带查找数据的,所以这种情况下计算出的不成功下的平均查找长度又不一样了,此时可以这样处理,在删除一个记录后在此位置放一个标志位,提示之前有过数据,查找过程中遇到此标志位就继续查找下去,等真正遇到一个空位置的时候才是查找不成功。(具体可参照数据结构课本255页)。(如:http://blog.csdn.net/w568083142/article/details/6342574,注意此题查找不成功的情况)。一般情况下,若表长为m,除留余数法里选取的除数通常是小于或者等于m的最小素数或者是不包含小于20质因子的合数。

13.迭代器范围

迭代器范围是左闭合区间,即[first,last)的形式,表示从first开始,到last结束,但是不考虑last,last可以等于first,或指向first后某个元素,但是不能指向first标记前的元素。通过迭代器给容器赋值的时候千万不要把last指向的元素也赋给容器!

14.string类不支持带有单个容器长度作为参数的构造函数。可以直接将一个char型数组a赋值给string类型s,但是不能讲单个字符赋给s哦。

15.注意printf 

http://www.360doc.com/content/09/0816/21/26398_4969316.shtml,printf只能操作内置数据类型,string不是内置的,所以不能直接printf,此时可以选择cout<<s,或者printf("%s\n",s2.c_str());

发现c++ primer一个问题,书291页说到string其中一个构造函数:string s(s2,pos2)创建s,并将其初始化为s2中从下标0开始到pos2的字符的副本,不过博主在VS2012中执行此语句却是将s2中从下标pos2开始到其结尾的字符副本赋给s了,文章后面部分也是按博主实现的方法讲的,所以该章节前面叙述有误呢,orz。。。

16.map VS hashmap(C++)

hashmap不是STL标准容器,其基本实现的数据结构是hashtable,而C++中map、multimap、set、multiset都是基于红黑树的,所以hash_map中的数据是无序的,插入、删除操作的时间复杂度是O(1)但是空间占用较大,相当于空间换时间;而map是有序的,插入和删除操作的时间复杂度是O(logn),空间占用少。

hash_map不提供反向迭代器,map则提供了。

17.TreeMap VS HashMap(Java)

Treemap是基于红黑树的,Hashmap是基于哈希表实现的,Treemap的键得实现comparable,HashhMap的键实现hashcode()和equals()。