Effective C++之一:让自己习惯C++

0导读

(1)

extern int x; //加了extern是声明

int x; //定义


(2)隐式类型转换:

  • 从小->大的转换中。比如从char转换为int。
  • 从int->long。
  •  自定义对象 派生类对象可以隐式转换为 基类对象。


而explicit阻止隐式类型转换。 经常使用在类的构造函数前面。

struct Month {

    explicit Month(int m):val(m) {}

    int val;

};

Date(2);                  //如果有explicit就会出错。如果没有,就可以隐式类型转换。

Date(Month(2)); //使用构造函数将int显式转换


(3)

copy构造函数被用来“以同类型对象初始化自我对象”

copy赋值操作符被用来“从另一个对象中拷贝其值到自我对象”

主要区别在于:被赋值的对象是不是当前创建的,如果是,那就是调用的构造函数

Widget w1;  //调用默认构造函数

Widget w2(w1); //调用copy构造函数

w1 = w2; //调用copy赋值操作符

Widget w3 = w2; //调用copy构造函数,因为是新对象被定义,一定会调用一个构造函数,不可能调用赋值操作。


(4)

int *p = 0; //p是一个null指针。 一般来说 内存 赋值为 0 。就是空。


(5)

命名习惯在 指针前加p,引用前加r


条款1 让自己习惯C++

(1)c++ 的 次语言

1、C

2、Object-Oriented C++ : classes、封装、继承、多态、virtual函数。

3、Template C++ :泛型编程部分。

4、STL


(2)C++高效编程视情况而变化,取决于使用c++哪部分。


条款2 尽量以constenuminline替换#define

(1)

宁可以编译器替换预处理器  


(2)

#define RATIO 1.653  //define语句在预处理阶段就替换了,编译器是看不见的,出现错误很难调试。这个名称并未进入记号表。

                        //大写名称用于宏


const double Ratio 1.653 //作为一个常量,编译器肯定看的到,也会进入记号表内


(3)

无法利用#define创建一个class专属常量,因为#define并不重视作用域。

class GamePlayer{

private:

    static const int NumTurns = 5; //常量声明式。如果是intcharbool,只要不取它们的地址,就可以只声明并使用它们无需提供定义式

    int scores[NumTurns]; //使用该常量

    ...

};


const int GamePlayer::NumTurns; //定义式,某些编译器坚持要看到一个定义式。

//由于声明式已经赋值了,这就不能再赋值


旧编译器不允许声明式赋值,那就只能在定义式赋值。

class GamePlayer{

private:

    static const int NumTurns; //位于头文件内

    ...

};


const int GamePlayer::NumTurns = 5; //位于实现文件内


如果在编译期间需要一个class常量的值,且编译器不允许声明式赋值,只能用“the enum hack”补偿做法。

理论基础是“一个枚举类型的数值可权充int被使用”

class GamePlayer{

private //都是编译期间就搞定的

    enum{ NumTurns = 5}; //the enum hack, NumTurns成为5的一个记号名称

    int scores[NumTurns]; //const地址是合法的,但是取#defineenum地址是非法的

    ...

};


(4)

#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))  //必须加小括号,否则可能会有歧义


int a = 5, b = 0;

CALL_WITH_MAX(++a, b); //a被累加两次

CALL_WITH_MAX(++a, b + 10); //a被累加一次,调用f之前,a的递增次数取决于它被拿来和谁比较


所以应该写为template inline函数

template<typename T>

inline void callWithMax(const T& a, const T& b)

{

    f(a > b ? a : b); //由于不知道T是什么,可以用 传递引用 的方式

}


(5)总结

对于单纯常量,最好用const或enums代替#define

对于形似函数的宏,最好改用inline函数替换#define


条款3 尽可能使用const

(1)

const在*左边,物是常量

const在*右边,指针是常量

在左右,都是常量


void f1(const Widget* pw);   //这两个意义相同,都是所指向的物是常量

void f2(Widget const* pw);


(2)迭代器中的情况:

const vector<int>::iterator it;  //   T* const 指针

//指针是常量,不能指向其他位置


vector<int>::const_iterator it;  //   const T*

//所指物是常量,所指的位置的值不能变


(3)

const面对函数声明时的应用:

1、const返回值 :避免返回值的赋值

2、const参数 :避免函数内部对这个参数的更改

3、const成员函数:避免更改任何的成员变量。注意:const对象只能调用const成员函数,而非const对象也可以调用const成员函数。


下面是const对象调用const成员函数的例子,返回的也是const

class TextBlock{

public:

    ... //两个成员函数如果只是常量性不一样是可以重载的。

    const char& operator[](size_t position) const  //for const对象

    {return text[position];}

    char& operator[](size_t position) //for -const对象

    {return text[position];}

private:

    string text;

};


TextBlock tb("Hello");

cout<<tb[0]; //调用 -const 函数

tb[0] = 'x'; //ok。注意:返回的是引用,如果不是引用,那就是返回一个副本,不是想要的行为。


const TextBlock ctb("World");

cout<<ctb[0]; //调用 const 函数

ctb[0] = 'x'; //错误! 企图对一个const 返回值的对象进行更改也是错误的!


如果是char* 而不是 string。这个例子是说const对象的成员变量如果是指针,指针所指的内容也可能被改变,编译器是不会报错的。

class CTextBlock{

public:

    CTextBlock(string s)

    {pText = &s[0];}

    

    char& operator[](size_t position) const  //for const对象!  这有一个很重要的点:如果返回 const char& ,那main函数里面的指针赋值也是错误的。 

    {return pText[position];}

    

    void pprint() const

    {

        char* p = pText;

        while ((*p) != '\0')

        {

            cout<<*p;

            p++;

        }

        cout<<endl;

    }

private:

    char* pText;

};


int main()

{

    const CTextBlock cctb("Hello");

    char* pc = &cctb[0]; //调用const成员函数取得一个指针

    *pc = 'J';   //cctb现在是“Jello”

    cctb.pprint(); //cctb是const对象,所以只能调用const成员函数!此处输出 Jello

    return 0;

}


C++中,mutable是为了突破const的限制而设置的。mutable 成员变量 在 const 成员函数也还可以被改变的

class CTextBlock{

public:

    ...

    size_t length() const;

    

private:

    char* pText;

    mutable size_t textLength; //mutable 成员变量 const 成员函数也还可以被改变的

    mutable bool lengthIsVaild;

};


size_t CTextBlock::length() const

{

    if (!lengthIsVaild)

    {

        textLength = strlen(pText);

        lengthIsVaild = true;

    }

    return textLength;

}


下面的例子是为了避免代码重复,设法让 非const成员函数调用const成员函数。而不是让const调用非const版本!

class CTextBlock{

public:

    CTextBlock(string s)

    {pText = s;}

    

    const char& operator[](size_t position) const  //for const对象!

    {return pText[position];}

    

    char& operator[](size_t position)  //for const对象为了避免代码重复,设法让 const成员函数调用const成员函数。而不是让const调用非const版本!

    {

        return

            const_cast<char&>(                              //返回值去掉const属性

            static_cast<const CTextBlock&>(*this)[position] //*this(当前对象)加上 const 再调用 const op[]

        );

    }

private:

    string pText;

};


int main()

{

    CTextBlock cctb("Hello");

}

    

条款4 确定对象被使用前已被初始化

(0)

永远在使用对象之前将它初始化。为内置型对象进行手工初始化,因为c++不保证初始化它们。

(1)

class PhoneNumber{...};

class ABEnter{

public:

    ABEnter(const string& name,

            const string& address,

            const list<PhoneNumber>& phones);

private:

    string theName;     //class的成员变量的初始化顺序是依照 声明 的次序

    string theAddress;

    list<PhoneNumber> thePhones;

    int numTimesConsulted;

};


//第一种构造函数:不使用成员初值列

ABEnter::ABEnter(const string& name,

                 const string& address,

                 const list<PhoneNumber>& phones)

{

    theName = name;             //这些都是赋值而不是初始化

    theAddress = address;       //C++规定:对象的成员变量的初始化动作发生在进入构造函数本体之前

    thePhones = phones;         //这个版本首先调用默认构造函数设初值,再立刻对他们赋予新值

    numTimesConsulted = 0;

}


//第二种构造函数:使用成员初值列

ABEnter::ABEnter(const string& name,

                 const string& address,

                 const list<PhoneNumber>& phones)

:theName(name),             //成员初值列, 现在这些都是初始化

theAddress(address),        //这些参数被直接作为构造函数的实参。

thePhones(phones),          //比如当前行,thePhonesaddress为初值进行copy构造

numTimesConsulted(0)

{}                          //构造函数本体不必有任何动作

构造函数一定是使用 成员初值列!而不是在本体中使用赋值操作。初值列列出的成员变量,其排序次序应该和它们在class中的声明次序相同

总在初值列中列出所有成员变量,以免还得记住哪些成员变量无需初值。


(2)解决初始化次序不确定性。

为免除“跨编译单元之初始化次序”问题,用local static 代替 non-local-static。调用可直接使用tfs(),

因为c++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。

class FileSystem{

public:

    ...

    size_t numDisks() const;

    ...

};

FileSystem& tfs() //如果不是一个函数而是直接 extern FileSystem tfs;,在另一个文件中的Directory类对象的构造函数需要这个文件中的tfs对象,但是这个tfs可能尚未初始化。

{ //因为是定义域不同编译单元内的non-local static对象。如何才能确定tfs在tempdir之前被初始化?是无法确定的。   

//所以说将non-lcoal static搬到自己的专属函数内(对象在此函数内被声明为static)。然后用户调用这些函数,而不直接指涉这些对象。是Singleton模式的一个常见手法。

    static FileSystem fs;   //static修饰局部变量。保证只有在第一次调用时才初始化创建。

    return fs;

}


//下面的内容在另一个文件中

class Directory{...};

Directory::Directory(param)

{

    ...

    size_t disks = tfs().numDisks();

    ...

}

Directory& tempDir()

{

    static Directory td;

    return td;

}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值