Effective C++笔记 摘自 Cql_liliang's Blog

Effective C++笔记之一:const 、enum、inline 代替#define的小结

 

我已经学完了C++了, 但是这学期好像没有学到什么东西,觉得C++我还没有入门,我想通过记笔记的形式再来学习C++,其中会有我个人对于C++的想法和不懂的地方我都会写在这里(希望高手能帮我解决不懂的地方和指出我错误的地方,谢谢),今天写第一篇,我应该每天都会写一篇的。

 

一:const、enum 、inline 的用法总结:

 

在用于替换不带参数的#define 是请用const 和enum 不仅是因为比#define 的安全性要高(会进行类型检查),而#define 定义的宏没有参数类型,只是简单的替换,而且const可用于任何的作用域的对象、函数参数、函数的返回值类型、成员函数体(可以达到防止用户修改的目的).如果在有参数的时候请用inline 代替带参数的宏(可以避免歧义,和一些错误).  对于const 修饰的变量可用一句话来表示"近水楼台先得月(变量的数据类型不看)"

有趣的是既然你上面这么说的,好我就这样做

eg0:

const char* str = "Name" ;     //把char不看说明const修饰的是*str(指向常量的指针也就是说*str的内容不能变,但是它的地址可以变

str[0] = 'F'                                      //false

str = "Fame"                                 //true

而:

char* const str = "Name"    //把char不看说明const修饰的是str(常指针),内容可以变,但是地址不能变

char* const str = "Name" ;  //常指针

str[0] = 'F' ;                         //没有语法错误完全通过但是会出现数据冲突

                                           // Unhandled exception at 0x00d914d8 in Test.exe: 0xC0000005: Access violation writing  location 0x00d97838.这是由于什么引起的???

估计”Name"被当作常量放在只读段中,因此不能写

 

但是我用new 来给str分配5个空间再逐个赋值却没有问题,这是怎么回事???

char * const str = new char[5] ;

str[0] = 'F' ;

str[1] = 'a' ;

str[2] = 'm' ;

str[3] = 'e' ;

str[4] = '\0' ;

这时候str输出为: Fame(知道原因的请告诉我,谢谢)

用const比#define 要好

eg1:

#define MAX 5               //可用下面

const int cmax = 5 ;       //替换(编译器在编译时会进行类型检查)

 

若在class的申明内,而不能在声明内定义类型的初始值,但是却要确定数组的大小时,可用enum 或static const 来代替#define .

 eg2:

class CGamePlayers

{     

private:      

     enum { NumTurns = 5} ;             

     int m_Scores[NumTurns] ;

 };

或者

class CGamePlayers

{    

   private:   

          static const int NumTurns = 5 ;        //声明变量NumTurns,注意static 不能少,因为static 申明的变量在编译的

                                                                   // 时候就确定其大小,不然的话下面的这句就会出错             

          int m_Scores[NumTurns] ;

} ;

const int CGamePlayers::NumTurns  ;       //不能在定义其大小,因为NumTurn的大小为5不能变了。

 

 用inline代替带参数的宏,效率也很高

 eg3:

#define CALL_WITH_MAX(lhs , rhs)        Fn((lhs) > (rhs) ? (lhs) : (rhs))                 //假设函数Fn已定义, 记得带上"()"

int lhs = 5 , rhs = 0 ;

CALL_WITH_MAX(++lhs , rhs) ;          //a被累加两次输出lhs = 7

CALL_WITH_MAX(++lhs , rhs+10) ;    //a被累加一次输出lhs = 6

不信你去试一下!!为什么当第一个参数大于第二个参数的时候lhs会自增两次,反之lhs却自增一次,我没有明白(我以为在lhs > rhs 的时候他会调用Fn 函数两次,其实两个语句都是调用一次,但是为什么lhs的自增次数会受到rhs大小的影响我没有弄懂(知道的多谢指教)

若用inline却不存在这样有歧义的语句

template<typename T>

inline void Call_With_Max(const T& lhs , const T& rhs)

      Fn(lhs > rhs ? lhs : rhs) ;    //假设Fn函数已定义

}

再调用函数的那边lhs的值两次都是6.(符合我们的正常思维)。   

 



1.对于const修饰的成员函数分为两种观点: logic constness and bitwise constness.

bitwise constness 的观点就是说 :“它不更改对象的任一bit”, 这样一来 , 编译器只要成员变量有赋值的行为即可,这是对只读性的定义,因此,const 成员函数不可以更改对象中任一non- static 成员变量。

eg1:

class TextBlock

{

public:

    ............

    char& operator [] (std::size_t position) ;

    const char& operator [] (std::size_t position) const ;

private:

    std::string text ;

} ;

 

TextBlock  bt("Jack") ;

std::cout<<bt[0] ;                         //true , read a non-const TextBlock

bt[0] = 'T' ;                                     //true , write a non-const TextBlock

const TextBlock cbt("Rose") ;    

std::cout<<cbt[0] ;                     //true , read a const TextBlock

cbt[0] =  'L' ;                                //false , cannot write a const TextBlock !!

char *ptr = &ctb[0] ;                

*ptr = 'T' ;                                     //true , not change the adress of ctb[0] , but the content has changed!!

 

而logical constness的观点是可以改变对象内的某些bits ,例如eg1中的*ptr = 'T' ; 这样还是让ctb中的pText 值改变了!!

 

 

若类的常成员函数有non-const 数据成员,并且要进行赋值操作, 那只能用mutable 来修饰non-const 数据成员了

eg2:

class TextBlock

{

    public:

            TextBlock(char * str = "\0" ) : pText(str) , textLength(0) , lengthIsValid(false)

            {}

            std::size_t length() const ;

    private:

           std::size_t textLength ;

           bool lengthIsValid ;

} ;

std::size_t TextBlock::length() const

{

          if(!lengthIsValid)

         {

                   textLength = std::strlen(pText) ;

                  lengthIsValid = true ;

         }

         return textLength ;

}

 

2.在const 与non-const 成员函数避免重复,这时候需要进行解除const的只读性和加上const 的只读性。

eg3:

class TextBlock

{

public:

TextBlock(string name = "\0") ;

const char& operator [] (std::size_t position ) const ;   //常成员函数

char& operator [](std::size_t position) ;                           //普通成员函数

private:

std::string m_Name ;

} ;

 

 

TextBlock::TextBlock(string name):m_Name(name)

{}

const char& TextBlock::operator [] (std::size_t position) const

{

if(position >= m_Name.length() || position < 0)         //边界检查

{

throw 0 ;                                                                     //抛出异常

}

else

{

return m_Name[position] ;

}

}

 

 

 

 

//利用普通成员函数去调用常成员函数

char& TextBlock::operator[] (std::size_t position)

{

//static_cast<const TextBlock&>类型转换非常对象变成常对象,以此调用常成员函数

//cosnt_cast是把常operator[]的返回常量去掉

//若不这样进行两次转换,会出现无限的递归调用non-const operator[] 函数

return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]) ;

}


1.在同一个编译单元中对象的初始化.

class PhoneNumber{.....} ;

class Info

{

    public:

        Info(const std::string name , const std::string address , const std::list<PhoneNumber> phonenum) : m_Name(name) , m_Address(address) ,                                                                                                                                                                                                                                                m_PhoneNum(phonenum)

        {}  

    private:

        std::string m_Name ;

        std::list<PhoneNumber>  

         std::string m_Address ;

} ;

 (1). 注意若用赋值的方法逐个的赋值给相应的数据成员,这不是初始化,仅仅是赋值,并且这样的话,这个构造函数进行了两次赋值,传参的时候也要一次,所以这样的话导致效率低。

(2).要注意数据成员的初始化的顺序, 它是按照数据成员在类中的声明的顺序来初始化的,与你所写的初始化列表中的顺序无关!!

(3).如果有多个不同的构造函数的时候,并且,它们之间有很多的相同的数据成员时,可调用共同的私有的成员函数Init(),来进行初始化!!

 

2.若对象在不同的编译单元, 若在一个non-local static 对象要使用另外一个位于不同的编译单元的对象,这个时候就不能保证这个对象被使用前已被初始化,其实不同编译单元的对象之间的初始化顺序是不确定的。这样我们使用designe pattern 的Singleton模式的方法来解决,即:将每一个non-local static对象搬到自己的专属函数内(该对象被声明为static),此类函数返回一个reference指向它所含的对象,这时候当用户使用这些对象时,而不直接涉及这些对象,这时候调用的就是local static 对象了。

eg1:

class FileSystem          //文件系统class

{

    public:

        ..........

        std::size_t numDisk() const ;

} ;

FileSystem  tfs ;

假设客户建立一个管理系统文件的类

class Directory

{

        Directory(params) ;

} ;

Direcoty::Directory(params)

{

    std::size_t  disks = tfs.numDisks() ;      //此时使用的tfs对象可能还未初始化,所以有很大的漏洞

}

所以我们用上面的所说的方法来解决此问题:

class FileSystem{.....} ;  //类同上

FileSystem& tfs()

{

    static FileSystem fs ;

    return fs ;

}

class Directory{....}  //同上

Directory& DirecTemp()

{

    static Directory dirtemp ;

    return dirtemp ;

}




你可能感兴趣的:(Effective C++笔记 摘自 Cql_liliang's Blog)