读书笔记-Effective C++中文版-1~3

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/aaron1996123456/article/details/102708230

读书笔记-Effective C++中文版 -1~3

C++为语言联邦

C++是一个多重泛型编程语言,支持面向过程心事,面向对象行使,函数形式,泛型行使,元编程形式的语言。

C。C++以C为基础,区块(blocks),语句,预处理器,内置数据类型,数组,指针,高效编程守则C语言没有模板,异常,重载。。

Object-Oriented C++。classes, 分装,继承,多态, virtual函数。。。面向对象编程。

template C++, 泛型编程。

STL,STL是个template程序库,它对容器,迭代器,算法以及函数对象的规约有几家紧密配合和协调。

C++并不是一个带有一组守则的一体语言,四个次语言组成的联邦政府。

尽量以const, enum, inline替换#define

宁可以编译器替换预处理器,因为define不被视为预言的一部分。

#define ASPECT_RATIO 1.653

记号名称ASPECT_RATIO也许从未被编译器看见;也许编译器在开始处理源代码之前被预处理器移走。这个ASPECT_RATIO可能没有进入记号表(symbol table),运用此常量获得编译错误时,错误信息为1.653 而不是ASPECT_RATIO,若其在一个非你写的头文件,来自何处毫无概念,会为追踪他而浪费时间。这个问题也可能存在在几好食调试器。

解决方法用一个常量代替宏(#define):

const double AspectRatio 1.653			//大写名称常用于定义宏,这里改变写法

作为语言常量,AspectRatio肯定会被编译器看到,会进入记号表。此外对于浮点常量而言,使用常量可能比使用#define导致较小的码,因为预处理器盲目将ASPECT_RATIO替换为1.653,可能导致目标码出现多芬1.653,改用常量AspectRatio不会出现。

以常量替换#defines,有两种特殊情况:

  1. 定义常量指针(constant pointers)。由于常量定义式通常被放在头文件内(一边被不同的源码含入),因此有必要将指针(不只是所指之物)声明为const。Example:文件内定义一个常量(不变的)char*-based字符串,必须写两次const。

    const char* const authorName = "HX";
    

    string对象通常比char*-based合宜。

    const std::string authorName("HX");
    
  2. class 专属常量为了将常量的作用域限制于class内,你必须让他成为class的一个成员(member);为了确保常量至多有一份实体,必须让他成为一个static成员。

    class GamePlayer{
    private:
        static const int NumTurns = 5;			//常量声明式
        int scores[NumTurns];					//使用该常量
    };
    

    NumTurns的声明式而非定义是。通常C++要求你对你所使用的任何东西提供定义式,但如果它是class专属常量有时static且为整数类型,则需特殊处理。只要不去他们的地址,可以只声明并使用无需提供定义式。但是如果去某个专属敞亮的地址或者编译器坚持看到定义式,必须提供定义式。

    const int GamePlayer::NumTurns;
    

    这个式子放在实现文件而非头文件,由于class常量一再声明时获得初值,可以不再设初值。

    #define无法创建一个class专属常量,因为#defines并不注意作用域(scope),一旦宏定义,在其后的编译过程就有效(除非在某处被#undef)。所以#define不仅不能够用来定义class专属常量,而且不提供任何封装性,没有private。

    若是编译器不支持上述定义:

    class CostEatimate{
    private:
        static const double FudgeFactor;			//static class 常量声明位于头文件内
    };
    
    const double CostEstimate::FUdgeFactor = 1.35;	//static class 常量定义位于实现文件内
    

    在上述数组声明式中(在编译过程中必须知道数组大小),编译器(错误的)不允许使用”static整数型class常量“完成”in class 初值设定“。可以改用” the enum hack“,理论基础:”一个属于美剧类型的数值可权充ints被使用“

    class GamePlayer{
    private:
    	enum { NUmTurns = 5 };
        
        int scores[NumTurns];
    }
    

    enum hack:

    1. enum hack行为比较像#define而不是const。取const地址是合法的,而取enum和#define的地址通常是不合法的。不想pointer或者reference指向某个整型常量,enum可以实现这个约束。优秀的编译器不会为”整数型const对象“设定另外的存储空间,不够优秀的编译器可能如此,enum和#defines不会导致非必要的内存分配。

    2. 实用主义,其是元模板编程基础技术(template metaprogramming)

第二种常见的#define 误用的情况是以他实现宏。宏看起来像函数,但是不会招致函数调用(function call)带来的额外开销。下面这个宏夹带着宏实参,调用函数f():

//以a和b的较大值调用f
#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被累加一次
template<typename T>
inline void callWithMax(const T& a, const T& b)
{
	if(a > b) ? a : b;
}

template 产出一整群函数,每个函数接受两个同型对象,其中较大者调用f()函数,不必考虑参数加上括号,参数被核算多次。

虽然有了const, enum和inline,对于处理的要求降低了,但是并没有完全消除,#include仍是必需品,#ifdef/#ifndef继续扮演控制编译的重要角色。

remember:

  1. 对于单纯的常量,最好以从const对象或者enums替换#defines。
  2. 对于形似函数的宏(macros),最好改用inline函数代替#defines。

尽可能使用const

const允许制定一个语义约束(制定一个不该被改动的对象),编译器会强制执行这项约束。

const可以用在class外部修饰global或者namespace作用域的常量,或者修饰文件、函数、区块作用域中被声明为static的对象,可以修饰class内部的static和non-stactic成员变量;可以是指针,指针指向的对象,或者两者都不是。

char greeting[] = "hello";				
char* p = greeting;						//non-const pointer, non-const data;
const char* p = greeting;				//non-const pointer, const data;
char* const p = greeting;				//const pointer, non-const data;
const char* const p = greeting;			//const pointer, const data;

关键字const出现在*星号左边,表示被指物是常量,出现在*星号右边表示指针自身是常量。出现在两边表示两边都是常量。

如果被指物是常量,把const写在类型之前,写在类型之后,意义相同。

void f1(const Widget* pw);
void f2(Widget const * pw);
//f1获得一个指针,指向一个常量的Widget对象
//f2相同

STL迭代器系以指针为根据塑模出来,多以迭代器的作用就像一个T*指针,声明迭代器位const声明指针为const,表示迭代器不得指向不同的东西,指向对象的数据可以改动。

std::vector<int> vec;

const std::vector<int>::iterator iter = vec.begin();			//iter ~ T* const
*iter = 10;														//改变所指之物可以
++iter;															//指针是常量,不可以改变

std::vector<int>::const_iterator cIter = vec.begin();			//cIter相当于const T*
*cIter = 10;													//错误,被指物不可以改变
  ++CIter;														//指针可以改变

const最具威胁的用法是面对函数声明是的应用。在函数声明式内, const可以和函数返回值,各参数,函数自身(如果是成员函数)产生关联。

函数返回一个常量值,可以降低错误而造成的意外,不至于放弃安全性和高效性。

class Rational{
};
const Rational operator* (const Rational& lhs, const Rational& rhs);

这样可以避免因为比较‘==’,误操作为‘=’造成赋值。

Rational a, b, c;
(a * b) = c;

const成员函数

从上图作用于成员函数,1.是为了确认该成员函数可以作用于从上图对象。2.是的操作const对象成为可能。

两个成员函数如果只是常量性不同,可以被重载。

class TextBlock{
public:
	const char& operator[] (std::size_t position) const
    { return text[position]; }
    char& operator[] (std::size_t position)
    { return text[position]; }
 private:
    std::string text;
};

//TextBlockde operator[]可以被这样调用
TextBlock tb("hello");
std::cout << tb[0];

const TextBlock ctb("World");
std::cout << ctb[0];

//返回时,char& 才可以对值进行修改,值传递改变的只是副本,对于const类型则不能进行修改

成员函数是const意味着什么?两个流行概念:bitwise constness(又称 physical constness)和logical constness。

  1. 成员函数只有再不更改对象的任何成员变量时,才是const。bitwise constness正是C++对常量性的定义,因此const成员函数不可以更改对象内任何non-static成员变量

    但是很多成员函数不具备从上图性质通过bitwise测试。例如:一个更改了”指针所指物“的成员函数虽然不能算是const,但是如果只有指针(而非所指物)隶属于对象,成此函数为bitwise const不会引发编译器异议。

    class C'Text'Block
    {
    public:
    char& operator[] (std::size_t position) const
    { return pText[position]; }
    
    private:	
    	char * pText;
    };
    

    在下面这段代码里面:

    const CTextBlock tb("hello");
    char* pc = &tb[0];
    *pc = 'J'
    

    创建一个常数对象并设移某值,对其调用const成员函数,但是改变了它的值。

    logical constness认为修改所处理的对象内的某些bit,只有在客户端检测不出来的而情况下。但是编译器会报错。

    解决方法:利用C++的一个与const相关的摆动场:mutable(可变的)。mutable释放non-static成员变量的bitwise constness约束。

class CTextBlock{
public:
    std::size_t length() const;
private:
    char* pText;
    //下面这两个成员变量可能总是会被改变即使在const成员函数内
    mutable std::size_t textLength;
    mutable bool lengthIsValid;
    
};

std::size_t CTextBlock::length() const
{
	if(!lengthIsValid)
    {
		textLength = std::strlen(pText);
        lengthIsValid = true;
    }
    return textLength;
}

const和non-const成员函数中避免重复

上述的operator[]函数返回的不只是reference指向某个字符,也执行一系列边界检验之际数据访问,检验数据完整性等等,要把同时放入const和non-const operator[]中,导致代码冗余。(可能成为隐喻式的inline函数)

class TextBlock{
public:
    ...
    const char& operator[](std::size_t position) const
    {
       ...			//边界检验
       ...			//志记数据访问
       ...			//检验数据完整性
       return text[position];
	}
    
    char& operator[](std::size_t position)
    {
       ...			//边界检验
       ...			//志记数据访问
       ...			//检验数据完整性
       return text[position];
	}
   
   private:
    std::string text;
};

实现其中一个调用另一个,必须进行常量性转除。本代码中const做了和non-const相同的事情,就是返回类型多了一个const修饰,这里对返回值const转除是安全的。

class TextBlock{
public:
    ...
    const char& operator[](std::size_t position) const
    {
       ...			//边界检验
       ...			//志记数据访问
       ...			//检验数据完整性
       return text[position];
	}
    
    char& operator[](std::size_t position)
    {
        //将op[]返回值的const转除,并为*this加上const,并调用const op[]
      return 
          const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
	}
   
   private:
    std::string text;
};

const成员函数承诺不改变其对象的逻辑状态,non-const成员函数没有。所以一般不从const调用non-const成员函数,这是一种错误行为。

remember:

  1. 将某些东西声明为const可以帮助编译器侦测出跟多的错误。const可以被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体。
  2. 编译器强制实施bitwise constness,但你编写程序应该使用概念上的“常量性”
  3. 当const和non-const成员函数有的实质等价实现时,零non-const版本调用const可以避免代码重复。

下面是复制的一篇博客

const类成员变量,const成员函数

const类成员变量,const成员函数
网址: https://www.cnblogs.com/cthon/p/9166715.html

const修饰变量一般有两种方式:const T *a,或者 T const a,这两者都是一样的,主要看const位于的左边还是右边。

类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变。

在设计类的时候,一个原则就是对于不改变数据成员的成员函数都要在后面加 const,而对于改变数据成员的成员函数不能加 const。所以 const 关键字对成员函数的行为作了更加明确的限定:

  1. 有 const 修饰的成员函数(指 const 放在函数参数表的后面,而不是在函数前面或者参数表内),只能读取数据成员,不能改变数据成员;没有 const 修饰的成员函数,对数据成员则是可读可写的。
  2. 除此之外,在类的成员函数后面加 const 还有什么好处呢?那就是常量(即 const)对象可以调用 const 成员函数,而不能调用非const修饰的函数。
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
class A
{
public:
    void f()
    {
        cout<<"non const"<<endl;
    }
    void f() const
    {
        cout<<" const"<<endl;
    }
};
 
int main(int argc, char **argv)
{
    A a;
    a.f();
    const A &b=a;
    b.f();
    const A *c=&a;
    c->f();
    A *const d=&a;
    d->f();
    A *const e=d;
    e->f();
    const A *f=c;
    f->f();
    return 0;
}

C++的const类型成员函数(解释为什么非const成员函数不能访问const类对象的数据成员)

1. 在C++中只有被声明为const的成员函数才能被一个const类对象调用。

在C++中,只有被声明为const的成员函数才能被一个const类对象调用。

如果要声明一个const类型的类成员函数,只需要在成员函数列表后加上关键字const, 例如:

class Screen {
    public:
        char get() const;
};

在类体之外定义const成员函数时,还必须加上const关键字,例如:

char Screen :: get() const {
    return _screen[_cursor];
} 

若将成员函数声明为const,则不允许通过其修改类的数据成员。 值得注意的是,如果类中存在指针类型的数据成员即便是const函数只能保证不修改该指针的值,并不能保证不修改指针指向的对象。例如:

class Name {
public:
void setName(const string &s) const;
private:
    char *m_sName;
};
void setName(const string &s) const {
    m_sName = s.c_str();      // 错误!不能修改m_sName;
for (int i = 0; i < s.size(); ++i) 
    m_sName[i] = s[i];    // 不好的风格,但不是错误的
}
2. const成员函数可以被对应的具有相同形参列表的非const成员函数重载,例如:
class Screen {
public:
char get(int x,int y);
char get(int x,int y) const;
};int main(){const Screen cs;Screen cc2; char ch = cs.get(0, 0);  // 调用const成员函数  ch = cs2.get(0, 0);     // 调用非const成员函数 
}

在这种情况下,类对象的常量性决定调用哪一个函数:

  • const成员函数可以访问非const对象的非const数据成员,const数据成员,也可以访问const对象内的所有数据成员;
  • 非const成员函数只可以访问非const对象的任意的数据成员(不能访问const对象的任意数据成员);(上述原因可详见C++Primer(5th)231页。 在默认情况下,this的类型是指向类类型非常量版本的常量指针,例如 Screen类中,this类型为 Screen *cosnt。当在成员函数的后面加上const关键字时,隐式的将this指针修改为 const Screen *const 即指向类类型常量版本的常量指针。根据初始化原则,我们不能将一个常量指针赋值给一个非常量指针)
  • 作为一种良好的编程风格,在声明一个成员函数时,若该成员函数并不对数据成员进行修改, 应尽可能将该成员函数声明为const成员函数。

2. const修饰的是谁?

const成员函数的写法有两种

1、void fun(int a,int b) const{}

2、void const fun(int a,int b){}

这两种写法的本质是:void fun (const 类 *this, int a,int b);

const修饰的不是形参a和b;const修饰的是属性this->a和this->b。与const所写的位置无关。

为什么?

因为c++对类的this指针做了隐藏,本质上,const指针修饰的是被隐藏的this指针所指向的内存空间,修饰的是this指针。

总结:

1)const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员;

2)非const成员函数可以访问非const对象的非const数据成员、const数据成员,但不可以访问const对象的任意数据成员;

3)作为一种良好的编程风格,在声明一个成员函数时,若该成员函数并不对数据成员进行修改操作,应尽可能将该成员函数声明为const 成员函数。

4)如果只有const成员函数,非const对象是可以调用const成员函数的。当const版本和非const版本的成员函数同时出现时,非const对象调用非const成员函数。

补充:

类中的const成员变量都要放在初始化列表之中进行
const数据成员
引用数据成员
对象数据成员(内置类)

const成员函数

void print() const => const 类名 * const this
在其内部是不能修改数据成员
只能调用const成员函数,不能调用非const成员函数
const对象只能调用const成员函数,必须要提供一个const版本的成员函数

const成员函数和成员变量这一块的逻辑容易混乱!

猜你喜欢

转载自blog.csdn.net/aaron1996123456/article/details/102708230
今日推荐