您的位置:首页 > 编程语言 > C语言/C++

C++ Primer阅读心得(第七章)

2008-01-21 22:09 246 查看
1. 数据抽象与封装:数据抽象是一种依赖于接口和实现分离的编程技术,用户只需要了解接口就可以使用,而不用去关心实现细节。封装实现了类的接口和实现的分离,它使得用户只能使用接口而无法访问实现部分。

2. this指针的传入:在类的成员函数中,有一个this指针(注意它是常量指针,不允许改变指向),它是编译器这样传入的:
class A
{
int function(int para);
}

int A::function(int para)  //我们看到的函数定义
{ ...... }

int A::function(A* const this, int para) //编译器的函数定义
{ ...... }

A a;
a.function(100); //我们看到的函数调用
A::function(&a, 100); //编译器的函数调用
3. const成员函数:在const成员函数中不允许修改对象的数据,也不允许调用非const成员函数(因为这样会间接的修改对象的数据,所以被C++封了),这样的函数是如下声明的:
class A
{
int function(int para)  const; //我们看到的const成员函数
int function(const A *this, int para) //编译器的const成员函数
}

编译器是通过const A *this来控制const成员函数的行为的,因为this是指向const的,所以不能通过this来改变对象的数据。但是this隐藏之后,const无处可放,所以被放在了整个声明的最后...

4. 类单独成为一个作用域,同一个作用域的成员(数据或者成员)可以直接访问,而无需在意它们之间声明的先后顺序。这是由编译类代码时编译器的特殊行为决定的:编译器先编译类成员声明,之后再编译成员函数体。对于类外部定义的成员函数:参数列表和函数体内部处在类作用域中,返回值则不处在类作用域中,如果使用需要额外声明。成员函数只能重载成员函数,不能重载类之外的函数,因为作用域不同。类成员函数中变量的查找顺序是:函数内->类内->全局
class A
{
public:
typedef int pos;
pos func(pos p);
};

A::pos A::func(pos p){}; //ok,返回值类型不包含在函数名的那个域作用符之内,必须另外声明


5. 不能将构造函数定义为const的,因为在构造函数运行时,这个对象还不存在,也就无法让它获得const属性。

6. 默认构造函数:不需要任何参数即可调用的构造函数(无参数的是,所有参数都有默认值的也是!)被称为默认构造函数,因为在对象被默认初始化时它会被调用。如果一个类没有任何构造函数,那么编译器会为它合成一个默认构造函数。合成的默认构造函数优先使用类内初始值初始化成员变量,如果没有类内初始值则默认初始化成员变量。在c++11中,我们可以在参数列表的后面使用=default来要求编译器生成默认行为的构造函数。

class A
{
A(){} //可以无参调用,默认构造函数
A(int a=1, double b=1.0){} //可以无参调用,默认构造函数
A() = default; //要求编译器生成默认行为的构造函数
};
注意:默认行为的构造函数(=default或者合成默认构造函数),对于指针成员会造成“浅拷贝”问题,对于没有类内初始值的成员会初始化为未定义的值,一定要注意。

7. 初始化列表:初始化列表的行为是在构造函数体执行之前初始化写在里面的成员变量。所以成员变量的初始化过程是:

                      初始化列表 -> 类内初始值 -> 默认初始化(前两步都没有的成员变量将被默认初始化)

所以构造函数体内部的成员变量都是被初始化过的,你只能对它们进行赋值。初始化列表的初始化顺序是按照成员变量在类内部的声明顺序初始化的,所以在初始化列表中使用一个成员变量初始化另外一个时要特别注意声明顺序的问题。初始化列表相对于构造函数:

对于某些复杂的类,执行效率更高。因为在构造函数中,成员变量先被初始化再被赋值,执行两次操作。
当成员变量是没有默认构造函数的类的对象时,只能使用初始化列表初始化。因为这个成员变量无法被默认初始化,所以到不了构造函数就会出错。
对于const成员变量和引用成员变量,也只能使用初始化列表初始化。因为它们只能被初始化一次,默认初始化完全不能达到我们的目的。

8. 除了默认构造函数之外,编译器还会为我们自动合成拷贝构造函数、=号操作符和析构函数。注意:一旦我们定义了任何一个构造函数,编译器都不会为我们再合成默认构造函数了。

9. C++的类通过访问控制符提供数据抽象和封装,public标识了类的接口定义了数据抽象,private封装了类的实现细节。在类的定义中,同一个访问控制符可以出现多次,其作用范围从出现到下一个控制符或者类定义的结尾为止。

10. C++中class与struct的区别只有一点:那就是struct中成员默认为public,而class中默认为private。仅此一点区别,其他特性一模一样,struct也可以包含函数,也可以进行继承、多态。

11. 可以将类、类的成员函数或者普通函数声明为一个类的友元,特许它们访问自己的全部成员(包括私有成员)。注意友元没有传递性,另外,如果如果想把一组重载的函数声明为友元,必须每一个都进行声明(显然,因为在编译器看起来它们是不同的函数啊~~)。

12. 在类的内部还可以定义类型的别名(typedef或者using ),这个别名是属于类的,它可以使用public等等访问限制符进行限制。另外,与其他成员不同,别名必须先声明后使用,它不具备无视声明位置的待遇。

class A
{
public:
typedef int pos1;

pos2 b = 2; //错误,pos2未定义
using pos2 = int;
private:
pos1 a = 1; //ok
};

pos1 aa = 1; //错误,pos1不可见
A::pos2 bb = 2; //ok
13. 被定义在类内部的成员函数是自动inline的。

14. 可变数据成员:将一个数据成员标记为mutable,则这个数据成员就成为了可变数据成员,在const成员函数中也可以修改该数据成员。

15. 不完全类型:只声明或者没有完成定义(在定义中)的类被称为不完全类型,不完全类型只能用来定义引用或指针。没有定义或者没有定义完的类型,编译器不知道它的对象需要多少内存,所以无法定义该类型的对象。但是地址的大小是确定的,所以可以定义指向它的对象的指针或者引用。例:

class screen;  //不完全类型

class screen
{
...
screen* ps;  //在这里,screen也是不完全类型,因为没有定义完
...
};
一个类只能包含自身的指针或者引用,因为在类的定义完成之前,它是不完全类型。(原来如此!)

16. 委托构造函数:c++11中新增了委托构造函数的机制,一个构造函数可以使用其他的构造函数来完成初始化过程。

class A
{
public:
A(int a, int b, int c):m_a(a),m_b(b){m_c=c}
A(int a):A(a,0,0){cout<<"Delegating done!"<<endl;} //委托构造函数
private:
int m_a;
int m_b;
int m_c;
}
委托构造函数的执行顺序:被委托构造函数的初始化列表 -> 被委托构造函数的函数体 -> 委托构造函数的函数体

17. 隐式类类型转换:具有一个参数的构造函数,又被称为“转换构造函数”,因为编译器会根据情况调用它隐式的把一个实参对象转换成类的对象。注意:隐式类类型转换只会进行一步。

string str1 = "abc";
string str2 = str1 + "bcd"; //隐式类型转换,"bcd"先被隐式转换成string类型再与str1做加法
有时候,隐式类型转换会偏离我们的目的,这时可以使用explicit关键词加以抑制。使用explicit关键词修饰的单参构造函数无法被隐式的调用,但是使用static_cast可以显示的调用它。
class A
{
public:
explicit A(string s){}
}

A a = string("abc"); //错误,不能隐式调用
A a = static_cast<A>(string("abc")); //ok,显式调用
18. 聚合类:c风格的struct被称为“聚合类”。c++11允许使用列表初始化聚合类,而不需要编写对应的构造函数。(编译器的特殊照顾?)
struct A
{
int b;
string s;
};

A a = {1,"abc"} //ok
19. constexpr构造函数:构造函数不可以是const的,但是它可以是constexpr的。constexpr表示这个函数返回的是一个常量表达式,只要这个类的对象是个字面值就能够满足要求,所以和构造函数本身的定义并不矛盾。另一方面,constexpr要求函数体只有一个return语句,构造函数又要求函数体没有return语句,所以constexpr构造函数的函数体只能为空(使用=default也可以的),我们只能使用初始化列表初始化数据成员。constexpr构造函数必须初始化所有的数据成员,否则生成的对象将不满足字面值对象的要求。
class A
{
public:
constexpr A(int a, double b, bool c):m_a(a),m_b(b),m_c(c){}
private:
int m_a;
double m_b;
bool m_c;
}

总结一下constexpr:constexpr让我们能够定义在编译期就确定其值的常量、常量函数,编译器保证它们不会被修改,因而constexpr 表达式是一般化的,受保证的常量表达式。constexpr作用于变量就得到了constexpr常量,constexpr作用于函数就得到了constexpr函数,constexpr作用于构造函数就得到了字面值常量类(具有constexpr构造函数、成员变量都是字面值(内置类型或者字面值常量类)、只有默认析构函数)。

20. static成员属于类本身,并不属于任何一个类的对象。static数据成员不可以在类定义中初始化,只能在类定义外面初始化,constexpr static数据成员可以具有类内初始值(但是注意还是要在类外定义一下)。static成员函数只能访问static数据对象和其他static成员函数,不可以访问非static得数据对象和成员函数,因为在static中无法知道它们能不能使用(类对象可能未初始化),同理static成员函数中也没有this指针,也不能被声明为const。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++