effective c++读书笔记(一)
来源:互联网 发布:神经网络算法能做什么 编辑:程序博客网 时间:2024/05/18 09:20
1.声明式(declaration): 告诉编译器某个东西的名称和类型,但略去细节;每个函数的声明揭示其签名式(signature),也就是参数和返回类型。
extern int x;std::size_t numDigits(int number);template <typename T>class GraphNode;
2.定义式(definition): 的任务是提供编译器一些声明式所遗漏的细节。对 对象而言,定义是是编译器为此对象拨发内存的地点。。对function或function template而言,定义是提供了代码本体。对class 或class template 而言,定义式列出他们的成员;
int x;std::size_t numDigits(int number){ std::size_t digitsSoFar = 1; while((numer /= 10) != 0) ++digitsSoFar; return digitsSoFar;}template<typename T>class GraphNode{public: GraphNode(); ~GraphNode(); //...};
3.初始化(initialization) 是“给予对象初值”的过程,对用户自定义类型的对象而言,初始化由构造函数执行。
class A{public: A(); //default constructor};class B{public: explicit B(int x = 0, bool b = true); //default constructor};class C{public: explicit C(int x); // not default constructor};//examplevoid doSomething(B bObject);B bObject1;doSomething(bObject1); //trueB bObject1(28); //true doSomething(28); //fault, from int to B ,no implicit type conversionsdoSomething(B(28)); //true ,explicit type conversions.
explitcit 可以阻止执行隐式类型转换,但是还是可以进行显示类型转换。
被声明为explicit的构造函数通常比其non-explicit更受欢迎,因为他们禁止编译器进行非预期的类型转换。
4.copy constructor function 被用来“以同类型对象初始化自我对象”, copy assignment operator 被用来“从另一个同类型对象拷贝其值到自我对象”:
class Widget{public: Widget(); Widget(const Widget & rhs); Widget& operator=(const Widget& rhs); //...};Widget w1; //default constructorWidget w2(w1); //copy constructor functionw1 = w2; // copy assignment operator
Widget w3 = w2; //copy constructor
区分copy constructor 和 copy assignment的区别,如果对象有一个新对象被定义,一定会有copy constructor called,不可能调用copy assignment function,如果没有新对象被定义,就不会有constructor函数被调用,应该是copy assignment function called。
copy constructor 定义一个对象如何passed by value:
bool hasAccepttableQuality(Widget w);//...Widget aWidget;if(hasAccepttableQuality(aWidget)) //...
w是以by value方式传递给hasAccepttableQuality,所以aWidget被复制到w体内。复制动作是有copy constructor完成的。passed by value意味调用copy constructor。passed by reference to const是一个较好的选择。
5. undefined behavior
int *p = NULL;std::cout<<*p; // undefined behaviorchar name[] = "Darla";char c = name[10]; //undefined behavior
Part1 accustoming yourself to c++
view c++ as a federation of languages
c++已经是一个多重范型编程语言,同时支持:过程形式(procedural), 面向对象形式(object-oriented), 函数形式(functional), 泛型形式(generic), 元编程形式(metaprogramming) 的语言。在某个此语言中,各种守则与通例都倾向简单,直观易懂,并且容易记住。
1. C。C++任是以C为基础。 blocks, statements, preprocessor, built-in data types, arrays, pointers 来自c。但是c没有template,exceptions,overloading等特性。
2. object-oriented c++。此部分包括:class, 封装(encapsulation), 继承(inheritance), 多态(polymorphism), virtual函数(动态绑定)。。。
3. template c++。 template metaprogramming。
4. STL。对容器(containers), 迭代器(iterator), 算法(algorithm)以及函数对象(functions objects) 的规约有极佳的紧密配合与协调。
对内置类型而言,pass-by-value通常比pass-by-reference高效,但是对于用户自定义类型而言pass-by-reference-to-const往往更好。对于STL而言,迭代器和函数对象是在C指针上塑造出来的,所以对于STL的迭代器和函数对象而言,旧式的pass-by-value再次适用
尽量以const,enum,inline替换 #define
- #define 不重视作用域,因为不能够称为class专属常量,不能提供任何封装性。enum 的行为某方面比较像#define而不像const。取enum的地址是不合法的,取define也是不合法的。enum和#define一样绝不会导致非必要的内存分配。
用常量替换#define时,有两个特殊情况:
1.定义常量指针(const pointers)。 由于常量定义式通常放头文件内,因此有必要将指针声明为const。
const char * const authorName = "Scott Meyers";const std::string authorName("Scott Meyers"); //string 对象通常比char*-based合适
- class专属常量。 为了将常量的作用域限制与class内,必须让它成为class 的一个成员,而为确保此常量最多只有一份实体,必须让他成为一个static成员:
class GamePlayer{private: static const int NumTurns = 5; //const declaration 而非定义 int scores[NumTurns]; //...};//只要不取它的地址,可以声明使用它,不许提供定义const int GamePlayer::NumTurns; // 但是如果取地址,或编译器必须看到定义时,使用此方法定义//因为声明的时候已经获得了初值,所以定义的时候不可以再设初值。
//如果上述方法编译器不支持,可以使用下面方法class CostEstimate{private: static const double FudgeFactor; //头文件内 //...};const double CostEstimate::FudgeFactor = 1.35; //实现文件内
//如果编译器不允许static整数型class常量完成in class 初始值设定,可以改用the enum hack做法class GamePlayer{private: enum{NumTurns = 5}; int scores[NumTurns]; //...};
尽可能使用const
const是一个语义约束,编译器会强制实施这个约束。const出现在*号左边,表示被指物是常量,如果出现在*右边,表示指针自身是常量,出现在两边,表示被指物和指针两者都是常量。
std::vector<int> vec;cosnt std::vector<int>::iterator iter = vec.begin(); //iter 的作用像个 T* const*iter = 10; //true ++iter; //false iter is conststd::vector<int>::const_iterator cIter = vec.begin(); //cIter的作用像个const T**cIter = 10; //false++cIter; //true
- 用 const修饰函数的返回值,可以避免因客户错误而造成的意外,又不至于放弃安全性和高效性。
class Rational{//...};const Rational operator*(const Rational& lhs, const Rational &rhs);Rational a,b,c;(a*b) = c;// false, 使用const修饰返回值,可以避免此操作
- const 成员函数,为了确认该成员函数可作用于const对象身上。两个理由:(1).使class接口比较容易理解,得知哪个函数可以改动对象内容,哪个不行。(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;};TextBlock tb("hello");std::cout<<tb[0];const TextBlock ctb("world");std::cout<<ctb[0];tb[0] = 'x'; //truectb[0] = 'x'; false
如果non-const operator[] 的返回类型是一个reference to char,不是char。如果返回一个char,下面的代码无法通过编译tb[0] = 'x';
,因为函数的返回类型是一个内置类型。改动返回值重来就不合法。即使合法c++以by value返回对象这一个事实,意味着被改动的是他的一本副本,不是自身,不是我们想要的行为。
3. bitwise const:成员函数只有在不更改对象的任何成员变量时才可以说是const。编译器只需寻找成员变量的赋值动作即可。不幸的是,许多成员函数虽然不十足具备const性质却能通过bitwise测试。
class CTextBlock{public: char & operator[] (std::size_t position)const{ return pText[position]; }private: char * pText;};//operator[] 不改变pText,可以通过编译器的bitwise检测const CTextBlock cctb("hello");char *pc = &cctb[0];*pc = 'J'; //cctb现在变成了Jello
上面这种情况,不应该出现错误,但是却改变了他们的值。因此提出了logical const
logical const:一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不到的情况下才得如此。
class CTextBlock{public: std::size_t length() const;private: char *pText; std::size_t textLength; bool lengthIsValid;};std::size_t CTextBlock::length()const{ if(!lengthIsValid){ textLength = std::strlen(pText); lengthIsValid = true; } return textLength;}
length的实现当然不是bitwise const,因为里面有值的改变。但是对于const CTextBlock对象是可以接受的。但编译器不同意,它们坚持bitwise constness。可以通过mutable来释放掉non-static成员变量的bitwise constness的约束。在变量textLength和lengthIsValid前面加上mutable。
4. 在const和non-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;};//代码重复,可以通过常量性转除//例如:char &operator[](std::size_t position){ return const_cast<char&>(static_case<const TextBlock &>(*this)[position]); //为了明确指出调用的是const operator[],这里将*this,从原始的TextBlock&转为const TextBlock&,第二次这是从const operator[]的返回值中移除const.}
不应该用const版本调用non-const版本来避免重复,const成员函数承诺绝不改变对象的逻辑状态,但是non-const成员函数却没有这样的承诺。
确定对象被使用前已被初始化
1.最佳的处理方法:永远在使用对象之前将它初始化,对于无任何成员的内置类型,你必须手动完成此事。
int x = 0;const char *text = "A C-style string";double d;std::cin>>d;
对于内置类型以外的任何其他东西,初始化责任落在构造函数身上。规则很简单:确保每一个构造函数都将对象的每个成员初始化.
不要混淆了赋值(assignment)和初始化(initialization).
class PhoneNumber{//...};class ABEntry{public: ABEntry(const std::string &name, const std::string &address, const std::list<PhoneNumber> &phones);private: std::string theName; std::string theAddress; std::list<PhoneNumber> thePhones; int numTimesConsulted;};ABEntry::ABEntry(const std::string &name, const std::string &address, const std::list<PhoneNumber> &phones){ theName = name; //赋值操作不是初始化 theAddress = address; thePhones = phones; numTimesConsulted = 0;}
c++规定对象的成员变量的初始化动作发生在进入构造函数之前。初始化应该发生在成员的default构造函数被自动调用之时(比进入构造函数本体的时间更早)
规定总是在初值列中列出所有成员变量,以免还得记住哪些成员变量可以无需初值,如果遗漏了内置类型的初始化,可能开启不明确行为的潘朵拉盒子。
2. 初始化对象的顺序不确定时的处理方法,(不同编译单元内定义的non-local static对象的初始化次序)
问题;某个编译单元内的某个non-local static对象的初始化动作使用了另一个编译单元内的某个non-local static对象,他所用到的这个对象可能尚未被初始化。
class FileSystem{public: std::size_t numDisks() const;};extern FileSystem tfs; //预备给客户用的对象class Directory{public: Director(); //...};Director::Director(params){ std::size_t disks = tfs.numDisks(); //使用tfs对象}Director tempDir(params); //如果tfs在tempDir之前未被初始化,会出现未定义行为//并且tfs和tempDir的初始化过程先后次序没有明确定义的。
解决这个问题的方法是将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static),然后返回一个reference指向它所含的对象。然后用户调用这些函数,而不是直接指向这些对象。
C++保证,函数内的local static对象会在函数被调用期间,首次遇上该对象的定义式时被初始化。
class FileSystem{//...};FileSystem& tfs(){ static FileSystem fs; return fs;}class Director{//....};Director::Director(params){ std::size_t disks = tfs().numDisks();}Director& tempDir(){ static Directory td; return td;}
这种结构下的reference-returning函数往往十分单纯:第一行定义并初始化一个local static对象,第二行返回它。但是这样做不是线程安全的。
任何一种non-const static对象,不论他是local或non-local的,在多线程环境下“等待某事发生”都会有麻烦,解决方法:在程序的单线程启动阶段手动调用所有reference-returning函数,可以消除与初始化有关的“竞速形势”。
- 《effective C++》读书笔记(一)
- 《effective c++》读书笔记【一】
- <<Effective C++>>读书笔记(一)
- 读书笔记之effective c++(一)
- Effective Objective-C 2.0 读书笔记 (一)
- 《More Effective C++》读书笔记一
- 《Effective C++》 读书笔记(一) 让自己习惯C++
- Effective Java 读书笔记(一)
- Effective C++读书笔记(一)
- effective C++ 读书笔记(一)
- Effective C++读书笔记(一)
- effective C++读书笔记(一)
- Effective Java读书笔记(一)
- Effective C++读书笔记(一)
- Effective C++读书笔记(一)
- Effective Java读书笔记(一)
- Effective C++ 读书笔记(一)
- effective c++读书笔记(一)
- DS18B20单总线技术在stm32f103上的实现
- 一起来学SpringCloud之
- @RequestBody和@RequestParam区别
- 跳槽之后的一点感想
- Highway Networks
- effective c++读书笔记(一)
- HowTo如何制作一个文字冒险游戏-里篇(2)逐条解析脚本文件
- Spring Boot干货系列:(七)默认日志框架配置
- Qt鼠标事件使用总结
- Java核心编程之异常的捕获及处理
- Android studio lambda环境配置
- 解决修改CSS文件后网页显示不生效问题
- Ubuntu 16.04 TLS Samba配置
- CentOS 7 vsftpd 搭建 FTP