代码疑云

来源:互联网 发布:淘宝售后服务怎么样 编辑:程序博客网 时间:2024/05/16 19:50

代码疑云(1)-掌握初始化列表


代码

[cpp] view plaincopy
  1. #include<iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5. private:  
  6.   int x1;  
  7.   int x2;  
  8. public:  
  9.   A():x2(1),x1(x2++){} //初始化列表  
  10.   void print()  
  11.   {  
  12.     cout<<"x1="<<x1<<endl  
  13.         <<"x2="<<x2<<endl;  
  14.   }  
  15. };  
  16. int main()  
  17. {  
  18.   A a;  
  19.   a.print();  
  20.   
  21.   return 0;  
  22. }  

:x1,x2最终被输出什么值呢?为什么?

解答:上机调试下会发现输出的结果是:x1是一个随机数,x2是1。为什么?因为在初始化列表中在给x1赋值为x2++时,这个x2并未初始化,也就是说x2里面什么东西也没装。也许你会问我不是在前面已经给x2赋值了吗,没错,但是有一个问题你忽略了,那就是初始化列表的赋值顺序是依照x1和x2的声明顺序的顺序来初始化的,也就是说在代码中,程序是先给x1赋值为x2++(此时x2并未初始化),再给x2赋值为1。还有初始化列表里的赋值,是在变量被声明时进行的,所以多使用初始化列表是可以提升程序效率的。

!!代码疑云系列由本人在天天唯C论坛下首发


代码疑云(2)-c函数调用约定

代码:

[cpp] view plaincopy
  1. #include<iostream>  
  2. using namespace std;  
  3. void foo(int p1,int p2,int p3)  
  4. {  
  5.   cout<<"p1="<<p1<<endl  
  6.       <<"p2="<<p2<<endl  
  7.       <<"p3="<<p3<<endl;  
  8. }  
  9. int main()  
  10. {  
  11.   int i;  
  12.   cout<<"first call:"<<endl;  
  13.   i=0;  
  14.   foo(++i,++i,++i);  
  15.   cout<<"second call:"<<endl;  
  16.   i=0;  
  17.   foo(++i,i++,i++);  
  18.   return 0;  
  19. }  

:两次调用foo函数分别输出了什么,为什么?

解答:按照cedel函数调用的约定,编译器使参数从左到右的入栈。第一次调用为什么p1,p2,p3的值全是3呢,原因在此,在foo被call之前三++i 操作将先被操作也就是连续自增了3次,最终结果i 的值是3,然后是编译器push(i),push(i),push(i)三次入栈,然后call到foo定义处依次出栈并相应地复制给了形参。第二次调用foo时,一开始与第一次一样先是计算三次++操作,但是所不同的是最后两个是i++,i++ 刚没说到它们的计算顺序,编译器计算这些的顺序是由右到左的,也就是先i++,再i++,最后是++i,而运算i++是先取值再自增的,编译器会先把i (这时为0)存入寄存器(cpu中的存储器),再加1,然后计算下一个i++ 与前一次一样,所不同的是这次 i 的值是1,因为前面已加1 ,最后++i 。

!!代码疑云系列由本人在天天唯C论坛下首发



代码疑云(3)-静态字符串

代码:

[cpp] view plaincopy
  1. #include<iostream>  
  2. using namespace std;  
  3. int main()  
  4. {  
  5.   char *str1 = "string";  
  6.   char *str2 = "string";  
  7.   if(str1 == str2)  
  8.     cout<<"str1 is same as str2";  
  9. }  

:str1 的值是否等于 str2 而输出字符串“str1 is same as str2”呢,为什么?解答:是的 “str1 is same as srr2”,也就是说str1与str2指向了相同的内存地址,因为"string"是静态对象,是由编译器分配给他的内存空间,在代码中出现了两次,编译器并不会给他们分别分配空间,因为如果这样将会造成不必要的浪费。


代码疑云(4)-类的sizeof值

代码:

[cpp] view plaincopy
  1. #include<iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5.   
  6. };  
  7. class B  
  8. {  
  9.   char a;  
  10.   int b;  
  11. };  
  12. class C  
  13. {  
  14.   void foo(){};  
  15. };  
  16. class D  
  17. {  
  18.   virtual void foo(){};  
  19. };  
  20. int main()  
  21. {  
  22.   cout<<sizeof(A)<<sizeof(B)<<sizeof(C)<<sizeof(D);  
  23.   return 0;  
  24. }  

:结果是什么,为什么呢?

解答:sizeof(A)为1,而不是0,虽然空类没有任何成员变量,但其实体提供取地址操作,所以其内存空间不能为0。sizeof(B)为8,编译器在计算类体或结构体变量的地址时是通过字节对齐的方式进行的,也就是通过加多少偏移量来确认是那个变量。类的偏移量为其最大内存空间的类型的成员变量的长度,其最大内存空间类型成员是int,所以偏移量为4字节,即char a成员需要补上3个字节,才能让编译器准确高效地寻址,再加上int的4字节,sizeof(B)就是8了。sizeof(C)为1,类的成员函数在编译器编译时,其函数地址就已自动存放,所以不必为其分配额外的空间来存放他,所以它的长度跟空类一样为1。sizeof(D)为4,因为类D需要构造一虚函数列表来存放函数指针以实现动态调用,一个指针的长度占用4个字节,所以为4.


代码疑云(5)-类成员函数的thiscall约定

代码:

[cpp] view plaincopy
  1. #include<iostream>  
  2. using namespace std;  
  3.   
  4. class A  
  5. {  
  6. private:  
  7.   int value;  
  8. public:  
  9.   A()  
  10.   {  
  11.     value=0;  
  12.   }  
  13.   void coutHello()  
  14.   {  
  15.     cout<<"hello"<<endl;  
  16.   }  
  17.   void coutValue()  
  18.   {  
  19.     cout<<value<<endl;  
  20.   }  
  21. };  
  22. int main()  
  23. {  
  24.   A *pA=NULL; //空指针,所指向的内容不可访问存取  
  25.   
  26.   pA->coutHello();  
  27.   pA->coutValue();  
  28.   
  29.   return 0;  
  30. }  

(感谢网友提供的题目)

疑:调用coutHello和coutValue方法有什么问题?
解答:成员函数的地址在编译器编译时给出的,所以是已知的,根据thiscall约定,类的成员函数在编译时编译器会传入一个this指针,通过this指针指向成员变量,在调用couthello时并未用到this指针所以调用正常,而调用coutvalue时,value需要用到this指针,因为此时this是NULL指针,所以会发生内存报错。



代码疑云(6)-头文件的正确定义

代码:

头文件print_tools.h

[cpp] view plaincopy
  1. #include<stdio.h>  
  2. void printStr(const char *pStr)  
  3. {  
  4.   printf("%s\n",pStr);  
  5. }  
  6. void printtInt(const int i)  
  7. {  
  8.   printf("%d\n",i);  
  9. }  

头文件counter.h
[cpp] view plaincopy
  1. #include"print_tools.h"  
  2. static int sg_value;  
  3. void counter_init()  
  4. {  
  5.   sg_value=0;  
  6. }  
  7. void counter_count()  
  8. {  
  9.   sg_value++;  
  10. }  
  11. void counter_out_result()  
  12. {  
  13.   printStr("the result is:");  
  14.   printtInt(sg_value);  
  15. }  

main.cpp
[cpp] view plaincopy
  1. #include "print_tools.h"  
  2. #include "counter.h"  
  3. int main()  
  4. {  
  5.   char ch;  
  6.   counter_init();  
  7.   printStr("please input some charactors:");  
  8.   while((ch=getchar())!='$')  
  9.     {  
  10.       if(ch=='A')  
  11. <span style="white-space:pre">    </span>counter_count();  
  12.     }  
  13.   counter_out_result();  
  14. }  

疑:以上代码编译时有何问题吗,是什么导致的呢?

解答:void printStr(const char*)和'void printtInt(int) 函数redefine了,道理很简单,因为在counter.h中已包含了print_tools.h,在main.cpp中又包含了print_tools.h头文件,也就是两次包含了print_toosl.h,所以也就发生了重定义错误,这是初学者常见问题,要避免这个问题,我们必须采取防范措施,就是使用预编译命令,如下作修改:

头文件print_tools.h

[cpp] view plaincopy
  1. #ifndef PRINT_TOOLS_H //如果未定义 PRINT_TOOLS_H  
  2. #define PRINT_TOOLS_H //则定义 然后编译以下源代码  
  3. #include<stdio.h>  
  4. void printStr(const char *pStr)  
  5. {  
  6.   printf("%s\n",pStr);  
  7. }  
  8. void printtInt(const int i)  
  9. {  
  10.   printf("%d\n",i);  
  11. }  
  12. #endif //结束 //如此改头文件就只被编译器编译一次了,起到了防范重定义错误的作用  

头文件counter.h
[cpp] view plaincopy
  1. #ifndef COUNTER_H  //  
  2. #define COUNTER_H  //  
  3. #include"print_tools.h"  
  4. static int sg_value;  
  5. void counter_init()  
  6. {  
  7.   sg_value=0;  
  8. }  
  9. void counter_count()  
  10. {  
  11.   sg_value++;  
  12. }  
  13. void counter_out_result()  
  14. {  
  15.   printStr("the result is:");  
  16.   printtInt(sg_value);  
  17. }  
  18. #endif //  

在一个工程项目中头文件众多繁杂,采用这个方法可以很好的避免,所以每当我们定义一个头文件时要养成如此的良好习惯。


代码疑云(7)-构造函数在类继承时

代码

[cpp] view plaincopy
  1. #include <iostream>  
  2. using namespace std;  
  3. class A  
  4. {  
  5. public:  
  6.   A()  
  7.   {  
  8.     Print();  
  9.   }  
  10.   virtual void Print()  
  11.   {  
  12.     cout<<"A is constructed.\n";  
  13.   }  
  14. };  
  15.   
  16. class B: public A  
  17. {  
  18. public:  
  19.   B()  
  20.   {  
  21.     Print();  
  22.   }  
  23.   
  24.   virtual void Print()  
  25.   {  
  26.     cout<<"B is constructed.\n";  
  27.   }  
  28. };  
  29.   
  30. int main()  
  31. {  
  32.   A* pA = new B();  
  33.   delete pA;  
  34.   
  35.   return 0;  
  36. }  
(感谢网友提供的题目)

以上代码输出结果是?

输出结果如下:
A is constructed.
B is constructed.
解释:当new B()时,因为B继承了A,所以编译器首先调用A的构造函数,A的构造函数里是调用A类里的Print(),所以首先输出A is constructed ,然后调用的是B的构造函数,此时Print虚函数指针已指向B的void print(),所以输出B is constructed。



代码疑云(8)-逻辑符号间的逻辑

代码

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. int main()  
  3. {  
  4.   int a=0,b=0;  
  5.   
  6.   printf("%d\n",!a||++b||b++);  
  7.   printf("b=%d\n",b);  
  8.   printf("%d\n",!a&&++b&&b++);  
  9.   printf("b=%d\n",b);  
  10.   
  11.   return 0;  
  12. }  

:以上输出什么,为什么?

解答:int a=0,b=0;在在第一次printf语句中,!a为1,后面的++b和b++就不做了,所以第二次输出是0,第三次printf中是与运算,后面的++b和b++还要做的,所以第二次会输出b=2。

代码疑云(9)-属于函数作用域里的字串

代码

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. char* GetString1()  
  3. {  
  4.     char p[] = "Hello World";  
  5.     return p;  
  6. }  
  7. char* GetString2()  
  8. {  
  9.     char *p = "Hello World";  
  10.     return p;  
  11. }  
  12. int main()  
  13. {  
  14.     printf("GetString1 returns: %s. \n", GetString1());  
  15.     printf("GetString2 returns: %s. \n", GetString2());  
  16.    
  17.     return 0;  
  18. }  
(感谢网友提供的题目)

:以上输出结果是什么情况?




………………………………………………………………………………………………………………………………………………………



解答:结果是GetString1()是乱码,GetString2()是 “Hello World”。因为GetString1()返回的是是指向该函数作用域里的局部变量char p[]的首内存空间,而当函数返回到主函数时,该内存空间将会被释放,所以P指向了一个无效的内存空间。而GetString2()中的p是指向全局对象(字符串“Hello World”)的,所以是“HelloWorld”.


代码疑云(10)-浅谈联合体

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. #include <string.h>  
  3. struct TStudent  
  4. {  
  5.   long int ID;  
  6.   union{  
  7.     char name[10];  
  8.     char mingzi[11];  
  9.   };  
  10.   char dept[17];  
  11. };  
  12. int main()  
  13. {  
  14.   TStudent stu;  
  15.   strcpy(stu.name,"zh");  
  16.   strcpy(stu.dept,"computer science");  
  17.   printf("student`s mingzi is:%s\n",stu.mingzi);  
  18.   printf("student`s name is:%s\n",stu.name);  
  19.   printf("student`s department is:%s\n",stu.dept);  
  20. }  

:以上代码输出结果是什么,为什么?




……………………………………………………………………………………………………………………………………




参考解答:

union{
    char name[10];
    char mingzi[11];
  };
定义这个就代表name[10]和mingzi[11]共享同一块内存。这里顺便提一下,mingzi[11]的空间大于name[10],所以申请空间的时候以union里面最大的字段为准。假如
union 
{
       char name[10];
       char mingzi[11];
}  UN_NAME;
那么sizeof(UN_NAME) 应该等于11。即取最大的字段的空间。当然这里不涉及字节对齐问题。
所以当strcpy(stu.name,"zh");执行这句的时候,mingzi[11]的内存空间也被赋值了"zh",所以最后打印两者的结果是一样的。


代码疑云(11)——指针与类型转换


代码

[cpp] view plaincopy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. void swap(int  *pa, int *pb)  
  4. {  
  5.     int temp;  
  6.     temp = *pa;  
  7.     *pa = *pb;  
  8.     *pb = temp;  
  9. }  
  10.   
  11. int main()  
  12. {  
  13.   char a[]="BACD";  //a字符串数组  
  14.    
  15.   
  16.   swap((int*)&a[0],(int*)&a[1]);//经过强制转换后交换a和b的值,使原序列变为“ABCD”  
  17.   
  18.   
  19.   printf("after swap:\n");  
  20.   printf("%s",a);  //打印a字符串数组  
  21.     
  22.   system("pause");  
  23.   return 0;  
  24. }  


以上c程序调用swap函数后能否得到预期的结果?结果是?




……………………………………………………………………………………………………………………………………………………………………………………




解释:不能,在32位机中,a为char型数组,各元素占用1字节内存空间,而在swap函数声明里的pa和pb为int 型指针,也就是说编译器会把pa,pb解析为指向连续四字节空间的指针,在进行各种运算时就是对pa、pb所指向的四字节进行运算,那么以上程序的pa、pb指向哪里呢?通过打印它们发现pa和pb的地址相差为1,pa小于pb,因为pa,pb对应a数组的地址所以实际上pa所指的四个字节是a数组下标第0,1,2,3的四个元素,而pb指向a数组下标为1,2,3,4的四个元素。图示:


pa对应a数组编号:       0        1        2      3

pa所指内存空间: | ‘B’ | ‘A’ | 'C' | 'D' |


pb对应a数组编号:      1        2        3      4

pb所指内存空间: | ’A‘ | 'C' | 'D' |'\0' |

所以在swap里,首先temp存储了*pa为[B,A,C,D],执行*pa=*pb后pa就变为[A,C,D,\0],此时a数组就变为[A,C,D,\0,\0], 然后执行*pb=temp,结果pb=[B,A,C,D],a数组又变为[A,B,A,C,D]。所以打印a字符串数组的最终结果是:ABACD然后后面跟着一堆乱码。

总结:强制转换指针变量类型是不明智的选择,除非你能确保转换后所指向的内存空间大小与没转换后是一样的,如果不是将会带来灾难性的后果,导致程序中其它变量被串改或由于指向了某段未经声明的内存空间而变为野指针。



原创粉丝点击