您的位置:首页 > 编程语言 > C语言/C++

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

2017-12-21 10:40 429 查看
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
尽量以const、enum、inline替换#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;
//常量声明式。如果是int、char、bool,只要不取它们的地址,就可以只声明并使用它们无需提供定义式

    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地址是合法的,但是取#define和enum地址是非法的

    ...

};

(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),          //比如当前行,thePhones以address为初值进行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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: