C语言深度解剖读书笔记之——C语言基础测试题



转载http://blog.csdn.net/feitianxuxue/article/details/7334929


前几天天看到这本书,感觉不错,在看之前,先做了后面的习题,结果只得了60多分,一直以为自己的基础还是不错的,做完后对了答案后,感觉自己的自信心一下全没有了,不过遇到问题解决问题,我用了2天时间好好研读了这本书,感觉真不错。虽然感觉都是一些基础的知识,但是我读的还是津津有味,感觉收获蛮多的,感谢这本书的作者陈正冲。呵呵,说来我本科专业和这位大牛还是同一个专业呢,呵呵。不是只有计算机科班出身的才能学好编程,知真正的高手都是自学的。

今天就把我当时做错的题目和认为比较好的题目一个个写出来。再次分析下

如果我在哪家公司遇到类似这种题目我会感觉这家公司出题很有水平,重基础,真正理解C语言的人才能得高分。注重细节,知其然知其所以然。

题目1.

下面代码有什么问题,为什么?

1.#include    
2.using namespace std;   
3.   
4.int _tmain(int argc, _TCHAR* argv[])   
5.{   
6.       char string[10],str1[10];   
7.       int i;   
8.       for (i=0;i<10;i++)   
9.       {   
10.              str1[i] = 'a';   
11.       }   
12.       strcpy(string,str1);   
13.       cout<<string<<endl;   
14.       system("pause");   
15.       return 0;   
16.}   

#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) {    char string[10],str1[10];    int i;    for (i=0;i<10;i++)    {       str1[i] = 'a';    }    strcpy(string,str1);    cout<<string<<endl;
    system("pause");    return 0; }

运行的时候回出现下面情况:

error1.exe 中的 0xcccc6161 处未处理的异常: 0xC0000005: Access violation

做这个题目的时候,我也知道字符串strcpy是以'\0'为结束标识的。会产生数组越界,但是表达不清楚。

答案:运行到strcpy的时候可能会产生内存异常。

因为:str1没有结束符标识,str1数组后面继续存储的可能不是'\0',而是乱码。Cout函数,对于输出char*类型,顺序打印字符串中的字符直到遇到空字符('\0')或打印了由精度指定的字符数为止。

 

题目2.

下面代码的结果是多少?为什么?

1.#include    
2.using namespace std;   
3.   
4.int _tmain(int argc, _TCHAR* argv[])   
5.{   
6.    char a[1000];   
7.    int i;   
8.    for (i=0;i<1000;i++)   
9.    {   
10.        a[i] = -1-i;   
11.    }   
12.    cout<<strlen(a)<<endl;   
13.     
14.    system("pause");   
15.    return 0;   
16.}   

#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) {    char a[1000];    int i;    for (i=0;i<1000;i++)    {    a[i] = -1-i;    }    cout<<strlen(a)<<endl;    system("pause");    return 0; }

做题目的时候没有考虑到字符溢出,自然做错了

分析:

我们知道计算机底层只认识0,1,所以任何数据到了底层都会通过计算转换成0,1,那么负数是怎样存储呢?由于“-”无法存入内存,我们把它做个标记,把基本数据类型的最高位腾出来,用来存符号,同时约定如下:如果最高位是1,表明这个数是负数,其值为除最高位以外的剩余位的值添上这个“-”号。

 一个32位的signed int 类型整数,其值表示的范围为:-8位的char,其值表示的范围为。一个32位的unsigned int 类型整数,其值表示的范围为:8位的unsigned char,其值表示的范围为。需要说明的是,在默认情况下,编译器默认的数据位signed类型。

for循环内,当i的值为0时,a[0]的值为-1.在计算机系统中,数值一律用补码来表示。在用两个补码的数相加时,如果最高位(符号位)有进位,则进位被舍弃。正数的补码与其原码一致;负数的补码:符号位为1,其余位为该数绝对值的原码按位取反,然后整个数+1.

-1的补码为0xff,-2的补码为0xfe......

i的值为127时,a[127]的值为-128,而-128char类型数据能表示的最小的负数。

i继续增加,a[128]的值肯定不能使-129,因为这时候发生了溢出。-129需要9位(1 1000 0001)才能存储下来,而char只有8位,所以最高位被丢弃,剩下的8位是原来9位补码(1 111 1111)的低8位的值,即0x7f。当i继续增加到255时,-256的补码低8位全为0,然后当i增加到256时,-257的补码的低8位全为1,即低8位的补码为0xff,如此又开始一轮新的循环......

按照上面的分析,a[0]~a[254]里面的值都不为0,而a[255]的值我0.strlen函数是计算字符串长度的,并不包含字符串最后的'\0'。判断一个字符串是否结束的标志就是看是否遇到'\0';如果遇到'\0',则认为字符串结束。

由此分析,strlen(a)的值为255

 

题目3.

下面的两段代码有什么区别?什么时候需要使用代码(2

代码(1):

int j = 10;

int j = i;

int k =i;

代码(2):

volatile int i = 10;

int j = i;

int k = i;

基础知识:

Volatile关键字和const一样,是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素改变,比如操作系统、硬件或者其他线程。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

int j = 10;

int j = i;   //1)语句

int k =i;   //2)语句

此时编译器对代码进行优化,这是因为在(1)(2)两条语句中,i没有被用作左值(没有被赋值)。这时候编译器认为i的值没有发生改变,所以在(1)语句时从内存中取出i的值赋给j之后,这个值并没有丢弃掉,而是在(2)语句时继续用这个值给k赋值。编译器不会生成出汇编代码重新从内存里取i的值,这样提高了效率。但是要注意的是:(1)(2)语句之间确认i没有被用作左值才行。

volatile int i = 10;

int j = i;  //3)语句

int k = i;  //4)语句

volatile 关键字告诉编译器,i是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从i的地址处读出数据放在k中。

这样看来,如果i是一个寄存器变量,表示一个端口数据或者是多个线程的共享数据,那么就容易出错,所以volatile 可以保证对特殊地址的稳定访问。

 

题目4.

在32位的x86系统下,输出的值是多少?

1.#include    
2.using namespace std;   
3.   
4.int _tmain(int argc, _TCHAR* argv[])   
5.{   
6.    int a[5] = {1,2,3,4,5};   
7.    int *ptr1 = (int *)(&a+1);   
8.    int *ptr2 = (int *)((int)a+1);   
9.    cout<<hex<<ptr1[-1]<<endl<<hex<<*ptr2<<endl;   
10.    system("pause");   
11.    return 0;   
12.}   

#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) {    int a[5] = {1,2,3,4,5};    int *ptr1 = (int *)(&a+1);    int *ptr2 = (int *)((int)a+1);    cout<<hex<<ptr1[-1]<<endl<<hex<<*ptr2<<endl;    system("pause");    return 0; }

运行结果:5   2000000

分析:这里需要注意的是&a是整个数组的首地址,a是数组首元素的首地址,其值相同,但是意义不同。

对于指针p+1,指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数,这个整数的单位不是字节而是元素的个数。

知道了这些就好办了。

int *ptr1 = (int *)(&a+1);

&a+1这下加的1跑到了a[5](虽然不存在)ptr1[-1]=*(ptr1-1),这下跑到了a[4],所以结果是5

int *ptr2 = (int *)((int)a+1);

a是数组a的首元素的首地址。(int)a+1是元素a[0]的第二个字节的地址。由于是int型,所以占4个字节

大端模式:

       a[0]                a[1]              a[2]                   a[3]  

        1                      2                  3                      4

0x00 0x00 0x00 0x01    0x00 0x00 0x00 0x02    0x00 0x00 0x00 0x03    0x00 0x00 0x00 0x04   

在32位的x86系统下,是小端模式——认为第一个字节是最低位字节

在内存中存放为:

a[0]                     a[1]

0x01 00 00 00     0x02 00 00 00

所以读取为:0x02000000

答案为:2000000

 

题目5.

假设p的值为0x100000,如下表达式的值分别为多少?

1.#include    
2.using namespace std;   
3.   
4.int _tmain(int argc, _TCHAR* argv[])   
5.{   
6. struct Test   
7. {   
8.     int Num;   
9.     char *pcName;   
10.     short sDate;   
11.     char cha[2];   
12.     short sBa[4];   
13. }*p;   
14. p = (Test *)0x100000;   
15. cout<<hex<<(p+0x1)<<endl;   
16. cout<<hex<<((unsigned long)p+0x01)<<endl;   
17. cout<<hex<<((unsigned int *)p+0x01)<<endl;   
18. system("pause");   
19. return 0;   
20.}   

#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) { struct Test { int Num; char *pcName; short sDate; char cha[2]; short sBa[4]; }*p; p = (Test *)0x100000; cout<<hex<<(p+0x1)<<endl; cout<<hex<<((unsigned long)p+0x01)<<endl; cout<<hex<<((unsigned int *)p+0x01)<<endl; system("pause"); return 0; }

运行结果为:

这里还是记住一句话:指针p+1,指针变量与一个整数相加减并不是用指针变量里的地址直接加减这个整数,这个整数的单位不是字节而是元素的个数。

p+0x1的值为0x100000+sizeof(Test)*0x01=0x100014

(unsigned long)p+0x01这里涉及到强制类型转换,将指针变量p保存的值强制转换成无符号的长整形数。任何数值一旦被强制转换,其类型就变了,所以这个表达式其实就是一个无符号的长整形数加上另一个整数,其值为0x100000+0x1=0x100001

(unsigned int *)p+0x01,这里p被强制转换成一个指向无符号整形的指针,所以其值为:0x100000+sizeof(unsigned int)*0x01=0x100004

 

题目6.

下面代码输出的结果是多少?

1.#include    
2.using namespace std;   
3.   
4.int _tmain(int argc, _TCHAR* argv[])   
5.{   
6. int a[3][2] = {(0,1),(2,3),(4,5)};   
7. int *p;   
8. p = a[0];   
9. cout<<p[0];   
10. system("pause");   
11. return 0;   
12.}   

#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) { int a[3][2] = {(0,1),(2,3),(4,5)}; int *p; p = a[0]; cout<<p[0]; system("pause"); return 0; }

运行结果:1

这里需要注意的是{}里面是逗号表达式,int a[3][2] = {(0,1),(2,3),(4,5)};相当于

int a[3][2] = {1,3,5};

 

题目7.

下面的代码有什么问题?为什么?

1.#include    
2.using namespace std;   
3.   
4.struct student   
5.{   
6. char *name;   
7. int score;   
8.}stu,*pstu;   
9.   
10.int _tmain(int argc, _TCHAR* argv[])   
11.{   
12. pstu = (struct student *)malloc(sizeof(struct student));   
13. strcpy(pstu->name,"Jimy");   
14. pstu->score = 99;   
15. free(pstu)+;   
16. system("pause");   
17. return 0;   
18.}   

#include using namespace std; struct student { char *name; int score; } stu,*pstu; int _tmain(int argc, _TCHAR* argv[]) { pstu = (struct student *)malloc(sizeof(struct student)); strcpy(pstu->name,"Jimy"); pstu->score = 99; free(pstu)+; system("pause"); return 0; }

运行错误:

error7.exe 中的 0x5b33f689 (msvcr90d.dll) 处未处理的异常: 0xC0000005: 写入位置 0xcdcdcdcd 时发生访问冲突

为指针变量pstu分配了内存,但是没有给name指针分配内存。

在为结构体指针分配内存的时候,是从外向里,即先分配结构体的指针,再分配成员指针,释放的时候,是从里向外,先释放成员指针,再释放结构体指针,顺序不能错。

正确的修改应该是:

pstu = (struct student *)malloc(sizeof(struct student));

pstu->name = (char *)malloc(20);

修正:由于疏忽,这道题没写完,再次把修正后的答案,贴出来,以免误导读者,下面是一个贴友的答案,谢谢! 

1.int main(int argc, char* argv[])     
2.{     
3.       pstu = (struct student *)malloc(sizeof(struct student));     
4.      pstu->name = (char *)malloc(20);     
5.      strcpy(pstu->name,"Jimy");     
6.      pstu->score = 99;   
7.      free(pstu->name);   
8.      pstu->name = NULL;   
9.      free(pstu);     
10.      pstu = NULL;   
11.       system("pause");     
12.      return 0;     
13.}   

发布了112 篇原创文章 · 获赞 162 · 访问量 36万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章