此文属学习笔记,随学习过程中碰到的问题而不断更新....
1、 在读取相对路径时,GCC认为当前路径为当前的源文件所在目录为当前路径,所以其相对路径皆以此路径为基准;而VC则认为当前工程文件所在目录为当前路径。
假设当前cpp文件位于“E:\C++”文件夹中,而对应的VC工程在"F:\PROJECT\"中,Data目录位于E盘根目录下。则使用GCC可以读取成功,而VC读取失败。
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string filename_str = "..//Data//World.txt";
ifstream ifs(filename_str.c_str());
if(ifs.fail())
{
cout << "failed" << endl;
return 1;
}
ifs.close();
return 0;
}
2、在C++中,自增(++)和自减(--)运算符,是唯一能够同时用于前缀和后缀的运算符。因此对于其重载必须要区分出用于何种方式。(下面以自增++为例)
(1)一般而言,为了区分出是用于前缀还是用于后缀,可以在重载函数的参数表中加一个虚设的参数来表示用于后缀,以作区别,这里虚设的参数是没有任何的作用,也不会被使用的,仅仅只是作为一个区别的标志,在实际编译过程中,始终为0。【本来以为这个虚设的参数可以是任意类型的,因为反正不会被使用的,测试一下,发现虚设的参数必须是int类型,希望要注意一下】
(2)在将++作为后缀操作符重载时,返回值必须为const。原因很简单,对一个整数n, n++++是非法的,而++++n合法,因此我们也要杜绝作后缀重载时解析为Object.operator ++ (0) .operator ++ (0)。设置返回值为const后,就可以使得编译时发现错误了(返回值为const型,当然不能在此基础上++啦!)。从下面的代码中,不难看出,我们在使用前缀和后缀的过程中,应该尽量避免使用后缀,原因嘛,就是后缀操作过程中,创建了临时变量,同时它也间接地用到了前缀操作。
【参考:《C++程序设计语言》和《More effective C++》】
下面是测试的代码:
#include <iostream>
using namespace std;
class TestClass
{
public:
TestClass(const int val = 0) : value(val) {}
TestClass& operator ++ ();
const TestClass operator ++ (int m);
private:
int value;
};
TestClass& TestClass::operator ++ ()
{
value += 1;
cout << "using ++value" << endl;
return *this;
}
const TestClass TestClass::operator ++ (int m)
{
const TestClass temp = *this;
++(*this);
cout << "using value++" << endl;
return temp;
}
int main()
{
TestClass TestObj1;
int i = 0;
//i++++; 编译错误,因此在重载时,也要避免这种情况
TestObj1++;
++TestObj1;
return 0;
}
3、C++中对于运算符的重载分为两类:一元运算符和二元运算符。(不允许重载类似于 "a ? b : c"这样的三元运算符)具体的重载是如何呢?
(1)、对任何二元运算符@,a@b可以解释为a.operator@ (b) 或 operator@ (a, b)。当二者都有定义时,按函数重载的方式来进行匹配;
那么,编译器又是如何解析a@b呢?(假设a的类型为A,b的类型为B)
首先,若A为类,检查其成员函数或A的基类是否有operator @的声明;然后在a@b的环境中查寻operator @的声明;若A在名字空间N里定义,在N里查寻operator @的声明;若B在名字空间M里定义,在M里查寻operator @的声明;(一元运算符与此类似)
(2)、对于任何一元运算符#,分两类:前缀和后缀。
【A】、一元前缀运算符情况下,可以表示为#a,解释为a.operator # () 或operator# (a),当二者都有定义时,按函数重载的方式来进行匹配;
【B】、一元后缀运算符情况下,可以表示为a#,解释为a.operator # (int) 或operator# (a, int),当二者都有定义时,按函数重载的方式来进行匹配,注意,这里的int表示的就是第2条所说的虚设的参数,以表明这是用于后缀运算符。
事实上,具体例子可以参考ostream类对于 << 的重载:ostream类中有成员函数ostream & operator << (const char *),却没有形如ostream & operator << (const string &)的成员函数,倒是在类外面,有函数ostream & operator << (ostream &, const string &)的声明。
【参考:《C++程序设计语言》】
4、Dereferencing(对间接运算符 -> 的重载)
首先,应该说明,对于->的重载最重要的是创建“灵巧指针”(smart pointer)。下面的笔记主要是分析这个"smart pointer"类。
TO BE EDIT
【参考:《C++程序设计语言》、《More effective C++》中item28】
5、explicit声明
在单变量构造函数中,是允许对象使用隐式构造函数调用的,这样就会很容易地将一个类型转换为另一个类型。
例:
class test
{
string name;
int value;
public:
test(int value = 0) : name(""), value(0) {}
//....
};
在后面的函数中,是可以使用test Object = 4;的,这就是隐式调用,而显式调用就是test Object(4);
看到上面的这个例子了没?虽然这种“伎俩”有时是很方便也很cool的,但是,如果你看了<More effective C++>中的那个经典例子Array<int>,我想你会改变你的想法的,例子见下:
template<class T>
class Array
{
public:
Array(int lowBound, int highBound);//允许调用者确定数组索引的范围
Array(int size);//能做为类型转换函数使用,能导致无穷的痛苦。
T& operator[] (int index);
//...
};
//比较Array<int>对象
bool operator==( const Array<int>& lhs,
const Array<int>& rhs)
{
Array<int> a(10);
Array<int> b(10);
//...
for (int i = 0; i < 10; ++i)
if (a == b[i]) // 哎呦! "a" 应该是 "a[i]"
{
// do something for when
// a[i] and b[i] are equal;
}
else
{
// do something for when they're not;
}
}
因此就有必要避开这种不期望的隐式调用方法了,而要解决这一问题,只需在函数声明的前面加一个explicit,告诉编译器:对这个单变量构造函数,不允许隐式调用,从而从源头上杜绝发生一些莫名其妙的错误。
关于类型转换的一些知识,如重载operator Type () {}这种,如果Type为内置类型,在没有重载opeator <<的情况下,都有可能可以输出一个复杂的类,所以这点也要引起注意,参考《More effective C+》 ItemM5。
【参考:《C++ Primer》、《Programming: Principles and Practice Using C++》、《More effective C++》】
6、类中const成员变量如何定义?
首先,我们知道,一个类是允许定义const成员变量,但是直接在类中对const成员赋值是会出错的;那在构造函数中呢?提示错误会是const成员不允许更改。这就与我们所要求的const成员变量背道而驰了,既然如此,我们一方面想定义一个不允许被修改的常量,另一方面还希望最好就在类中就能够定义好,那有没有什么解决的方法呢?其实还是有的。。。第一种方法:我们可以在类中定义一个enum类型,这样enum{NUM_TO_BE_CONST = ...},而后我们直接在类中使用这个量就OK了。
第二种方法:定义一个static const成员变量,而且这样的话,我们可以直接在类中进行赋值,或者在类的实现文件中赋值。