我学C++并不久,最开始的时候认为它除了个指针,最多针对C来说,多了个继承多态的面向对象之外也没什么神奇的地方,一直很好奇为什么这么多高手牛人持续的研究者它。但当越深入的了解它之后,越来感觉到了它的恐怖。就像Unix程序员鄙视Windows程序员的理由一样:“你根本不知道它背着你做了多少你不知道的事情”。 而,这不是最恐怖的,更恐怖的正是Linux之父Linus Torvalds说的“C++是一门很恐怖的语言,而比它更恐怖的是很多不合格的程序员在使用着它”。你是否合格呢?

下面说一些例子来说明C++中一些小问题。 1: //————————————————————

#include <iostream>
#include <vector>

typedef int UINT4;
using namespace std;

class CMyFile
{
};
CMyFile& operator< ( CMyFile& a, CMyFile& b )
{
 std::cout << "小于" << std::endl;
 return a;
}
CMyFile& operator> ( CMyFile& a, CMyFile& b )
{
 std::cout << "大于" << std::endl;
 return a;
}
int _tmain(int argc, _TCHAR* argv[])
{
 CMyFile vector;
 CMyFile UINT4;
 CMyFile foo;

 vector< UINT4 > foo;  //  注意这一行

 int n;
 cin >> n;
 return 0;
}

//———————————————————— OK,上面的代码在G++和VC2008下都是可以正确编译运行的,那么它的值是什么?为什么?

2: //———————————————————— Fun() = 7; 你觉得有意义么? 那么当Fun是一个Array Fun[1] = 7; 你觉得有意义么?为什么? //————————————————————

3: //———————————————————— Cout << a << b << endl; 是怎么实现的? //————————————————————

4: //———————————————————— 我们是否可以让一个引用重新指向一个新的对象? //————————————————————

5: //———————————————————— 句柄是神马东东?是指针么?是引用么?是指针的指针么?它到底是神马东西? //————————————————————

6: //———————————————————— 内联函数我们知道,是用于频繁使用的简短的函数时会有效能提高,那么,它的实现原理是什么?为什么可以效能提高? //————————————————————

7: //———————————————————— 内联函数可以在cpp内实现么?内联函数除了用inline,__forceinline标示,还可能用其他方式标示么?内联inline标示应当放在声明处还是定义处,或是两地都标示? //————————————————————

8: //———————————————————— A a; 和 A a(); 有区别吗? //————————————————————

9: //———————————————————— 构造函数内可以使用 this 指针么? //————————————————————

10: //———————————————————— 若你现在有两个CPP,一个A.CPP有一个静态对象a,另一个B.CPP有一个静态对象b。其中a 调用了b的方法,b也调用了a的方法。 那么能编译么?假设编译链接都正常,能运行么? //————————————————————

11: //———————————————————— 构造函数没有返回值,那么要是构造函数失败了怎么办? //————————————————————

12: //———————————————————— 数组是如何析构的?能重载类析构函数吗?能显式调用析构函数吗? //————————————————————

13: //———————————————————— 我想一个局部变量在其创建的}之前被析构,可以吗? 例如: Void fun() { File a; // 希望此时a存在,然后做一些事情。 // 希望a在这里被析构 // 希望此时a不在,然后做一些事情。 } 这样我可以显式的析构a了么? //————————————————————

14: //———————————————————— 好吧,我看了问题13的答案,那么显式调用析构是不是完全没有意义了? //————————————————————

15: //———————————————————— 那么,当析构函数失败时候,我们该怎么处理?能像构造函数失败一样进行异常抛出么? //————————————————————

16: //———————————————————— Class A{}; Class B { A* a; Public: B& operator= ( cosnt B& other ) { Delete a; A = new A( *other.a ); Return *this; } } 上面代码有什么问题? //————————————————————

17: //———————————————————— 友元关系是否可以得到继承或者传递? //————————————————————

A1: 会输出 小于 大于 真正的语句是vector.operator<(UINT4).operator>(foo);

A2: Fun() = 7; 其实是有意义的,是返回引用类型的函数。其实Fun[i] = 7 是一个函数调用的伪装。其实就是调用了下面函数的 Array::operator[](int t) Class CArray { Public: float& operator[] ( int index ); }; 任何返回值为引用的函数都意味着它可以存在于赋值操作符的左边。

A3: 首先明白a.Fun1().fun2() 这样的代码是什么意思。 这种写法称之为方法链。第一个被执行的是a.fun1() 。它返回对象的引用,可能是return *this结束,可能是其他一些对象。我们姑且称呼这个返回的对象是b,然后被执行的是b.fun2(). 那么 cout << a 返回的则是cout的引用即可。

A4: 不能。引用和指针不同,一旦和对象绑定则不可指向其他对象。

A5: 句柄是一个获得另外一个对象的方法。也可以理解为一种广义的假指针,这个术语是故意含糊不清的。 当我们不知道一个对象是否可以用一个简单的指针或者引用或者指向指针的指针或者指向引用的指针或者整形来标识的时候,他就是必要的了。我们经常用一个指针去获取一个对象,但是假若对象需要移动怎么办?当对象被安全删除时我们如何确保被通知?若对象需连续的从磁盘获取怎么办?这些情况下大多数我们可以增加一个间接层进行管理。例如,句柄可以是Obj** ,Obj的指针Obj*可以不停的变化,但是我们可以保证指向Obj*的指针不会被移动,这也是C++的一种“弱引用”。 其实重点就是当我们不知道对象做事的细节时,使用句柄间接访问会更加安全。 若对句柄重载了 -> 和 * 操作符的时候,它非常类似指针了。

A6: 假设有个函数 Inline fun( int x, int y, int z ){ dosth… } 被调用 Int main { Int x, y , z; Fun( x, y, z ); } 我们知道典型C++实现中有一系列寄存器和一个栈。我们在调用fun() 之前,x,y,z 是在main()函数的寄存器上。当我们调用fun()时,我们需要将寄存器中的 x,y,z 写入fun()函数的栈中, fun()从自己栈里读出参数值,然后处理完毕后,又要将x,y,z这些从寄存器中读出的值恢复到寄存器中,这里有很多不必要的读写操作。尤其是当编译器可以保存 x,y,z 时,每个变量都会写两次读两次,但若编辑器可以内联展开对fun() 的调用,则可以避免这些无谓的操作。

A7: Inline 函数不可以在cpp内实现。因为它是在编译阶段就已经展开的,若放在cpp 内,则引用它的其他类无法获知其内部信息,会出现无法解析的外部符号错误。 Inline标示的不一定是inline函数,没用inline标示的未必不是inline函数。这是编译器在作祟,即使你使用了windows的__forceinline也是一样,你无法force,一切仅仅是个骗局。你所能做的仅仅是提示编辑器,多考虑一下吧,加重一下它的考虑权值而已。和 windows的线程优先级一样可恶的东西。 一般,建议仅放在函数定义前标示 inline即可。但是,声明前加也没有错。仅仅是某志的一个个人爱好的建议而已。

A8: 这是一个开玩笑的问题,仅仅是放松一下你那紧绷的脑筋。A a;是定义A的对象a;而A a()是说一个返回A类型对象的函数a()。怎么?被某志骗了? 

A9: 一些人认为构造函数中不应当使用this指针,因为此时this对象还没有完全形成。我很赞同这种观点,但,只要你小心,你也可以在构造函数,甚至初始化列表里使用它。(发现没,隐藏的this,隐藏的构造函数,拷贝构造和operator = ,有时很讨厌的,写单件必须干掉它) This指针在构造函数访问基类的数据成员时进行使用时始终可行的: This 指针在调用派生子类重定义的虚函数时是不可以的,例如你使用this->Fun()调用子类重定义的虚函数会没有任何意义。而隐式的使用Fun()也是一样。甚至,你使用其他函数来调用子类该虚函数也是没有意义的,在构造函数结束前,this对象还不是一个派生类的对象。 但是若使用this指针访问一个数据成员,将其值传递给另外一个数据成员,你必须保证源数据成员已被初始化,但是除非你更清楚底层,不然不要做这种尝试。(当然庆幸的是,这种规则是C++标准规则,不依赖编译器。)

A10: 这个BUG是个很危险的BUG,某志自己写引擎时就出现了这样的问题。我的AudioSystem受GameMgr对象管理,GameMgr对象初始化时里调用了AudioSystem的初始化函数。但是AudioSystem初始化时候又需要GameMgr对象里的Config初始化加载功能。两个都是静态单件,然后悲剧发生了。编译没问题,运行时呢?却是有时正确,有时当。概率50%-50%。 我是在赌到底是a 先被初始化还是b先被初始化。 那么怎么办?乖乖的用指针替代对象吧,然后用单件模式保证其存在。一个简单的 static A a; static B b;是无法解决这个问题的。

A11: 抛出异常吧,没有更好的办法了。但如果不允许使用异常,那只能内部设置一个状态变量记录了……那会非常糟糕……

A12: 数组析构顺序和构造顺序也是相反的,析构顺序是a[5], a[4], … a[1], a[0] 析构函数是不允许重构的,不带任何参数,不返回任何东西。 可以,但是后果显然是严重的~:)所以,永远记住,绝不显式调用析构函数。

A13: 我们可以使用{}将问题中的局部变量包含起来以限制它的生命周期。 例如: Void fun() { { File a; // 希望此时a存在,然后做一些事情。 // 希望a在这里被析构 } // 希望此时a不在,然后做一些事情。 }

A14: 不是。当我们new时,指定了new内存的位置的情况下,需要我们负责析构的责任。例如 Char mem[sizeof(A)]; A* a = new (&mem)A(); // 标示a指向的内存要分配在mem处。 此时我们必须负责 a->~A(); 来进行显式的析构。 注意,若这么使用的话,要小心我们的内存必须足够大,一般编译器和运行时系统都会对对象间进行边界调整,上面代码是没有的,这是不安全的,你必须了解你的编译器和运行时系统,否则,不要这么去做。

A15: 绝对不允许! 析构函数失败时我们可以写LOG可以做其他,但是绝对不可以抛出异常! C++语言保证,在析构时进行栈展开情况下抛出异常,一定会调用terminate()杀死进程。

A16: 没有考虑自赋值,我们不能不考虑用户会这么写代码。 B b; b = b; // 自赋值 在delete 的时候,参数内的*this就不见了,这样就导致崩溃。 建议在操作符重载函数第一行加上 if( this == &other ) { return *this; }

A17: 不。若B是A的友元类。B有子类C,而D又是B的友元类。 那么D无法访问A。因为A不相信B的朋友。 那么C无法访问A。因为A不相信B的孩子。 那么A无法访问B。因为A认可了B是自己朋友,并不意味B认可了A是自己朋友。