代码疑云(1)-掌握初始化列表
代码:
- #include<iostream>
- using namespace std;
- class A
- {
- private:
- int x1;
- int x2;
- public:
- A():x2(1),x1(x2++){} //初始化列表
- void print()
- {
- cout<<"x1="<<x1<<endl
- <<"x2="<<x2<<endl;
- }
- };
- int main()
- {
- A a;
- a.print();
- return 0;
- }
疑:x1,x2最终被输出什么值呢?为什么?
解答:上机调试下会发现输出的结果是:x1是一个随机数,x2是1。为什么?因为在初始化列表中在给x1赋值为x2++时,这个x2并未初始化,也就是说x2里面什么东西也没装。也许你会问我不是在前面已经给x2赋值了吗,没错,但是有一个问题你忽略了,那就是初始化列表的赋值顺序是依照x1和x2的声明顺序的顺序来初始化的,也就是说在代码中,程序是先给x1赋值为x2++(此时x2并未初始化),再给x2赋值为1。还有初始化列表里的赋值,是在变量被声明时进行的,所以多使用初始化列表是可以提升程序效率的。
!!代码疑云系列由本人在天天唯C论坛下首发
代码疑云(2)-c函数调用约定
代码:
- #include<iostream>
- using namespace std;
- void foo(int p1,int p2,int p3)
- {
- cout<<"p1="<<p1<<endl
- <<"p2="<<p2<<endl
- <<"p3="<<p3<<endl;
- }
- int main()
- {
- int i;
- cout<<"first call:"<<endl;
- i=0;
- foo(++i,++i,++i);
- cout<<"second call:"<<endl;
- i=0;
- foo(++i,i++,i++);
- return 0;
- }
疑:两次调用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)-静态字符串
代码:
- #include<iostream>
- using namespace std;
- int main()
- {
- char *str1 = "string";
- char *str2 = "string";
- if(str1 == str2)
- cout<<"str1 is same as str2";
- }
疑:str1 的值是否等于 str2 而输出字符串“str1 is same as str2”呢,为什么? 解答:是的 “str1 is same as srr2”,也就是说str1与str2指向了相同的内存地址,因为"string"是静态对象,是由编译器分配给他的内存空间,在代码中出现了两次,编译器并不会给他们分别分配空间,因为如果这样将会造成不必要的浪费。
代码疑云(4)-类的sizeof值
代码:
- #include<iostream>
- using namespace std;
- class A
- {
- };
- class B
- {
- char a;
- int b;
- };
- class C
- {
- void foo(){};
- };
- class D
- {
- virtual void foo(){};
- };
- int main()
- {
- cout<<sizeof(A)<<sizeof(B)<<sizeof(C)<<sizeof(D);
- return 0;
- }
疑:结果是什么,为什么呢?
解答: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约定
代码:
- #include<iostream>
- using namespace std;
- class A
- {
- private:
- int value;
- public:
- A()
- {
- value=0;
- }
- void coutHello()
- {
- cout<<"hello"<<endl;
- }
- void coutValue()
- {
- cout<<value<<endl;
- }
- };
- int main()
- {
- A *pA=NULL; //空指针,所指向的内容不可访问存取
- pA->coutHello();
- pA->coutValue();
- return 0;
- }
(感谢网友提供的题目)
疑:调用coutHello和coutValue方法有什么问题?解答: 成员函数的地址在编译器编译时给出的,所以是已知的,根据thiscall约定,类的成员函数在编译时编译器会传入一个this指针,通过this指针指向成员变量,在调用couthello时并未用到this指针所以调用正常,而调用coutvalue时,value需要用到this指针,因为此时this是NULL指针,所以会发生内存报错。
代码疑云(6)-头文件的正确定义
代码:
头文件print_tools.h
- #include<stdio.h>
- void printStr(const char *pStr)
- {
- printf("%s\n",pStr);
- }
- void printtInt(const int i)
- {
- printf("%d\n",i);
- }
头文件counter.h
- #include"print_tools.h"
- static int sg_value;
- void counter_init()
- {
- sg_value=0;
- }
- void counter_count()
- {
- sg_value++;
- }
- void counter_out_result()
- {
- printStr("the result is:");
- printtInt(sg_value);
- }
main.cpp
- #include "print_tools.h"
- #include "counter.h"
- int main()
- {
- char ch;
- counter_init();
- printStr("please input some charactors:");
- while((ch=getchar())!='$')
- {
- if(ch=='A')
- <span style="white-space:pre"> </span>counter_count();
- }
- counter_out_result();
- }
疑:以上代码编译时有何问题吗,是什么导致的呢?
解答:void printStr(const char*)和'void printtInt(int) 函数redefine了,道理很简单,因为在counter.h中已包含了print_tools.h,在main.cpp中又包含了print_tools.h头文件,也就是两次包含了print_toosl.h,所以也就发生了重定义错误,这是初学者常见问题,要避免这个问题,我们必须采取防范措施,就是使用预编译命令,如下作修改:
头文件print_tools.h
- #ifndef PRINT_TOOLS_H //如果未定义 PRINT_TOOLS_H
- #define PRINT_TOOLS_H //则定义 然后编译以下源代码
- #include<stdio.h>
- void printStr(const char *pStr)
- {
- printf("%s\n",pStr);
- }
- void printtInt(const int i)
- {
- printf("%d\n",i);
- }
- #endif //结束 //如此改头文件就只被编译器编译一次了,起到了防范重定义错误的作用
头文件counter.h
- #ifndef COUNTER_H //
- #define COUNTER_H //
- #include"print_tools.h"
- static int sg_value;
- void counter_init()
- {
- sg_value=0;
- }
- void counter_count()
- {
- sg_value++;
- }
- void counter_out_result()
- {
- printStr("the result is:");
- printtInt(sg_value);
- }
- #endif //
代码疑云(7)-构造函数在类继承时
代码:
- #include <iostream>
- using namespace std;
- class A
- {
- public:
- A()
- {
- Print();
- }
- virtual void Print()
- {
- cout<<"A is constructed.\n";
- }
- };
- class B: public A
- {
- public:
- B()
- {
- Print();
- }
- virtual void Print()
- {
- cout<<"B is constructed.\n";
- }
- };
- int main()
- {
- A* pA = new B();
- delete pA;
- return 0;
- }
疑:以上代码输出结果是?
输出结果如下:
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。