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;
}
(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;
}
相关文章推荐
- Effective C++第一章 让自己习惯C++
- effective c++-让自己习惯c++
- Effective c++ 第一章 让自己习惯C++
- 读书笔记 Effective C++: 01 让自己习惯C++
- 《Effective C++ 3》01 让自己习惯C++ 条款:01-04
- 《Effective C++》 读书笔记(一) 让自己习惯C++
- Effective C++ --1 让自己习惯C++
- Effective C++ -- 让自己习惯C++
- effective c++之让自己习惯C++
- Effective C++(一)让自己习惯C++
- Effective C++(一)让自己习惯C++
- Effective C++ 3nd 读书摘要(一、让自己习惯C++ ; 二、构造,析构,赋值运算)Item1 - 12
- 《Effective C++》1-让自己习惯C++
- 【Effective C++】让自己习惯C++
- Effective C++之1 让自己习惯C++
- Effective C++第一章:让自己习惯C++
- 【Effective C++】条款01-让自己习惯c++
- 【读书笔记】Effective C++-1 让自己习惯C++(之三)
- 《Effective C++》让自己习惯C++:条款1-条款4
- 《Effective C++ 3/e》笔记(一):让自己习惯 C++