第一部分 基本语言
1. direct-initialization和copy-initialization之间存在着微妙的差别,但是理解这个差别可以加深C++的对象概念。
string str1("initialization"); //direct-initialization
string str2 = "initialization"; //copy-initialization
前者没有调用拷贝构造函数,而是调用了构造函数。
后者创建了一个temp对象,使用构造函数初始化后,在调用拷贝构造函数来初始化str2。
2.const是一个略为复杂的问题。
(1)const指针(引用) 是自认为指向const对象的。
通过const指针(引用)是不能修改其对象,但是并不代表其对象是const的。所以一个const指针(引用)可以指向非const对象,同时函数的const形参可以由非const实参调用。
(2)非const指针(引用)是不能指向const对象的。对于函数的形参同样如此。
(3)const指针和指针const
const int *iptr; //不能修改其对象的值
int *const ptr; //不能修改其对象
(4)仅当形参是引用或指针的时候,形参是否为const才有影响。并且才能根据const进行重载。
(5)类的常量成员函数
int cfun(...) const;
const对象的句柄只能使用常量成员函数。
3.任何动态增长操作都使得目前的迭代器失效。
4.int a[3][4]应该解释为3个元素的数组,每个元素为4个元素的数组。
5.typedef不是文本扩展,
(1)typedef string *pstring;
const pstring cstr;
cstr应该解释为其值不能改变的string *对象,也就是不能改变其指向的对象,string *const cstr;
(2)typedef int array[10]
array是类型名,代表10个元素的数组。
(3)typedef int (*pfun)(...)
pfun是函数类型
6.内联函数的定义放入头文件,预编译的时候在调用点展开。
7.重载函数应该放在同一作用域,里面的作用域的名字将屏蔽外面的作用域的名字。
8.默认形参只在定义时指定。
9.类型转换是一个稍显复杂的问题。
当形参是数组的引用的时候,会检查数组的长度,而不是将数组转换为指针。
...
第二部分 类和数据抽象
1.使用this指针的场合在于返回对象的引用。
return (*this);
一个非const成员函数返回该对象的const指针,而一个const成员函数返回限定为const的该对象的const指针。
基于是否常量成员函数可以重载。
2.每个类不仅定义了唯一的类型,而且定义了自己的作用域。
并不是在访问限定符上的公有或者私有成员,而是和对象句柄有关。
考虑
class Screen
{
public:
typedef std::string::size_type index;
void display();
index move(index here);
private:
std::string content;
index cusor;
};
(1)在成员函数外,类的成员由特定对象的句柄结合.或者->来访问。
(2)成员函数在定义的时候,限定了函数体的作用域为某一类,
void Screen::display()...
所以能够在该作用域后不使用特定句柄来访问成员。
在该限定符前的返回类型就需要完全限定符,
Screen::index Screen::move(index here)...
注:全局作用域为 ::...
3.构造函数初始化列表是一个特性,
在进入函数体后,就进入了计算阶段,这一阶段是赋值。而之前的初始化列表属于初始化阶段。
必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。
初始化顺序是由定义的顺序决定的,而不是初始化列表中的顺序。
4.默认实参可以减少默认构造函数的编写。
5.隐式类类型转换
构造函数:Sales_item(const std::string &book = "")...
item.same_isbn("0099...");
”0099..."将用于构造一个临时对象,然后再被same_isbn调用。
通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为explicit, 可以避免错误,并且当转换有用时,用户可以显式的构造对象。
explicit Sales_item(const std::string &book = "")...
item.same_isbn(Sales_item("0099..."));
6.友元函数可以在类体内定义,它的作用域将被扩展到::,即全局作用域。
7.static 类成员
对于特定类,可能需要全局的对象,例如统计这个类的对象实体数量。使用全局函数的缺点是破坏了封装,而使用static成员则可以声明为私有数据成员。
使用static数据成员,可以使用句柄,或者该类的作用域。
static成员函数没有this指针,不能声明为const,因为它不用承诺不修改特定对象的数据。不能声明为虚函数。
static数据成员应该在类定义体的外部定义,它不是在构造函数调用时初始化,而是在定义时候初始化。
class C
{
static int a;
};
int C::a = 0; //不加static
特性的const static成员可以在类定义体内初始化,但是仍然必须在类的定义体外定义。
class C
{
const static int a = 0;
};
int C::a; //不加static
static成员不是类对象的组成部分,可以被声明为该对象的类型(非static成员限定为对自身类型的指针或引用。也可以作为默认实参。
class Bar
{
...
static bar mem1;
bar *mem2;
bar mem3; //error
};
8.复制控制
当定义一个新类型的时候,需要显式或隐式的指定复制,赋值和撤销该类型的对象时会发生什么,这是通过定义特殊成员:复制构造函数,赋值操作符和析构函数来达到的。
复制构造函数
class Foo
{
public:
Foo();
Foo(const Foo&);
};
赋值操作符
Sales_item& operator=(const Sales_item &);
它通常返回*this.
析构函数
...
三者总是同时出现的。
9.创建指针成员,可以使用智能指针的方法,用一个计数类管理。或者将指针当着值对象,也就是说,赋值时,不是使用指针的值,即地址,而是其指向的地址的值。
10.重载操作符
c++重载操作符,通过成员函数(对象则为左操作数),友元函数实现。
(1)=赋值操作符
注:注意要防止给自身赋值
在初始化式的=并不是调用赋值操作符。而是复制构造函数。
(2)输入输出操作符
(3)下标操作符
注:一般需要定义两个版本,一个为非const成员并返回引用,另一个为const成员并返回const引用。
(4)算术,关系操作符
(6)成员访问操作符(*, ->)
(7)调用操作符()
(8)转换操作符
注:此时称为函数对象,在标准库里广泛运用。
不适合重载的操作符有:
1.取地址操作符(&), 逗号操作符(,)
2.具有短路求值的&&和||
第三部分 面向对象编程
面向对象编程的三个主要概念:
数据抽象,派生,动态绑定。
动态绑定只发生在对象的引用或者指针。基类的指针或者引用可以指向派生类对象。如果将一个派生类对象作为实参,那么发生sliced down。
为基类定义虚的析构函数是原则。
当派生类定义了自己的复制构造函数和赋值操作符时,应该在初始化列表里显示调用基类的构造函数。
名字是和作用域紧密相连的。
派生类的作用域覆盖了基类的作用域。当在派生类重载基类的函数的时候,你必须重定义这个函数的所有版本。使用using可以将基类的该函数的所有版本在派生类中被引用。只需要重定义需要重载的版本。
当一个类调用其成员的时候,名字查找首先在该类的作用域中,然后到该类的基类中。这和作用域嵌套是一样的。
第四部分 模板与泛型编程
C++中的对象是运行时的多态性,而模板是编译时多态性。
阅读(1005) | 评论(0) | 转发(0) |