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

c++ 基础知识总结

2011-03-01 22:15 393 查看
1:把程序员分成两类:一类是类创建者,一类是客户程序员。
(好比我开发了一套类库(当然我没那么牛),我封装了一些大部分的功能,对某一个具体的类而言,
我只向外面暴露借口,而具体的实现,我已经隐藏起来了--->这个时候我就是类的创建者。
而客户程序员,他要利用我写的那套类库去开发某一个新的产品(或者软件),他只需要调用我暴
露出来的借口去实现具体的功能,而根本不必要知道这里面到底是怎么实现的。这是类设计者和客户程序员
的最大区别。)

2:对虚函数的理解(virtual)
2.1:只有申明为virtual的函数才能动态绑定(late binding),缺省情况下函数不具有该特性。
这说明一个问题:静态绑定和动态绑定的区别
静态绑定是在程序编译时期就一定分配好了地址,绑定完毕。
动态绑定就是只有等到程序运行时刻执行到这一条语句的时候,才确定调用的哪一个函数。
而实现动态绑定,只有在有virtual的情形下才有意义。所以可以这么理解:virtual函数可以用来实现动态绑定;
动态绑定也只有在virtual存在的情况下才具有意义。

2.2:多态:允许子类的指针赋值给父类的指针。一种常见的情形就是父类的指针指向了派生类的对象。
所谓多态:必须用virtual来实现,这个是前提条件。而虚函数的作用,就是在执行期实现动态绑定。
多态----虚函数----动态绑定:三者的关系。

2.3:若基类定义了虚函数,则基类会生成一张虚函数表,这张表里保持着类中虚函数的地址。
当然,要操作这张虚函数表,就必须有一个虚函数指针来遍历这张表。而这虚函数表指针什么时候创建?
又是属于类,还是对象的呢?
虚函数表,是属于类的,因为只要有虚函数,类就会创建虚函数表。
而指向虚函数表的指针则是属于对象的,只有当一个对象创建时刻,在调用默认构造函数的时刻,才创建这个虚函数表
指针,这就解释了为什么虚函数可以用来实现多态。vptr是在运行时刻创建的(对象创建时)。
类对象包含有的东西:非静态的数据成员,以及(有虚函数时候)一个vptr。就这些东西。
那些类中的成员函数,都是在代码段,这些函数是借口,供外界调用。
类中可以有静态的数据成员,以及静态的方法:静态的数据成员,是在静态区(全部变量,static变量都放在那里,
还有字面值常量也在那里),静态方法只能调用静态的数据成员。这个在单件模式中,应用到了。
这里会有很多sizeof的问题,仔细分析应该没有任何问题。

2.4:基类和派生类的关系
首先是对基类和派生类都求sizeof()的问题




代码

(a):测试代码
#include <iostream>
using namespace std;

class A
{
public:
int m;
private:
int n;
};

class B : public A
{
public:
int k;
};

int main()
{
A a;
cout<<sizeof(a)<<endl;   //8
B b;
cout<<sizeof(b)<<endl;   //12
return 0;
}
sizeof(a)=8,很容易知道。
sizeof(b)=12,这就说明了基类的private成员变量也被继承到派生类中,只是派生类无法直接去访问
这些基类的private成员。但,仍然可以通过派生类的对象去调用从基类继承下来的成员函数,去访问这些
基类的私有成员变量。
(b):测试代码:说明上面这个问题---派生类对象去调用基类方法访问基类私有成员变量。
#include <iostream>
using namespace std;

class A
{
public:
int m;
A()
{
n=10;
}
void Count()
{
n++;
}
void Print()
{
cout<<n<<endl;
}
private:
int n;
};

class B : public A
{
public:
int k;

};

int main()
{
A a;
cout<<sizeof(a)<<endl;   //8
B b;
cout<<sizeof(b)<<endl;   //12
b.Count();
b.Print();         //11
return 0;
}


2.5:再谈虚函数问题
基类的虚函数,会派生到派生类的虚函数表中,而且在派生类虚函数表中的位置和这个虚函数在基类表中的
位置是一样的。若是派生类重写了基类的某个虚函数,则在派生类虚函数表中把重写的虚函数的地址放进去,
覆盖掉原来基类那个虚函数的地址,这就是当基类的指针指向派生类的对象的时候,会根据这个对象的虚函数表
指针找到这张虚函数表,去调用这个虚函数:若派生类改写,则调用的是派生类的虚函数,若未改写,则调用的
还是基类的虚函数。这就是多态的具体实现哦......(咳,不知道说清楚没有...)

2.6:纯虚函数
这个问题困扰了很久,主要是搞不清楚里面的实现机制是怎么回事!
纯虚函数,一般是在基类中才有纯虚函数,也只有这样才具有意义。
声明一个虚函数的时候把它设置为0,就是纯虚函数了。具有纯虚函数的基类,就是虚基类,这个类不能实例化,
只提供接口。为什么不能实例化呢?主要是纯虚函数的地址在虚函数表中无法确定,所以不能实例化。
一个经常性的用法:把基类的析构函数设置为纯虚函数。
这样的好处:可以防止内存泄露。当基类的指针指向派生类对象时,在程序结束时,delete p(p为基类指针),
接下来发生的事情是:首先会调用派生类的析构函数,清理派生类的这块内存,接着删除掉这块内存;
再调用基类的析构函数,清理基类的这块内存,在删除掉这块内存。这就保证了不发生内存泄露。
若:基类的析构函数未声明为纯虚函数,则在delete p的时候,就不会去调用派生类的析构函数去完成派生类
内存清理工作了,发生内存泄露。
当基类的析构函数声明为纯虚函数时,那派生类的析构函数也是虚函数。
构造函数不可以被声明为虚的,为什么呢?
举个例子:基类A和派生类B,B继承自A,A的构造函数声明为虚函数,则在A类的虚函数表中就登记了这个虚构造函数
的地址。好,现在A创建一个对象a,则要去调用构造函数完成初始化工作,问题出来了,这个构造函数已经登记在虚函
数表中,我没办法找打它,为什么呢?因为我要通过一个vptr去找遍历这个虚函数表,而这个时候vptr没有。为什么没有呢?
因为,vptr是在创建对象的时候,调用默认构造函数的时候才创建的。
这是一个死循环:我要调用构造函数,而构造函数在虚函数表中;那就需要一个vptr去访问这个虚函数表;而这个
vptr却是在默认构造函数中才创建的,而此刻这个构造函数却在虚函数表中。结果就是找都找不到这个构造函数。

2.7:封装

封装是如何定义的呢?封装就是将数据和行为进行结合,形成一个有机整体。

封装的目的:增加安全性,简化编程。

客户程序员(类的使用者)只需要调用类暴露出来的外部接口,而不必知道类中各个接口具体是如何实现的。

2.8:继承

继承是一种机制,这种机制可以利用已有的数据类型来定义性的数据类型(由基类到派生类),所定义的新的数据类型不仅拥有新定义的成员,还同时拥有旧成员(继承自基类的成员)。

子/父

Public

protected

Private

公有继承

Public

Protected

不可见

私有继承

Private

Private

不可见

保护继承

Protected

Protected

不可见

这张继承表格:

(1):基类的private部分,不管在哪种继承体系下,在派生类中都是不可见的。

(2):注意三种继承关系下,父类和子类的关系。

3:对C++中引用的理解

引用就是某一个变量的别名,对这个引用的操作和对变量的直接操作,效果是完全一样的。

声明一个引用的同时,要对其进行初始化。也就是说,要绑定要一个变量上。之后不可以修改,让这个引用又指向了其他的变量。保持:从一而终。

引用本身不占内存,仅仅是个别名而已,引用本身不是一种数据类型。

引用作为函数参数的特点:

传递引用给函数与传递指针的效果是一样的。

传递引用,在内存中并没有产生实参的副本。也就是说,调用某一个函数的时候,传递引用,则实参不需要拷贝一份,再传过去。而是直接把实参传过去,省去了拷贝产生副本的过程。从侧面来说,提高了效率,节省了空间。

引用比起指针来,更容易使用,语法,语意上也更加清晰。

常引用的目的:既想提高效率,又不愿意参数在函数体中被修改,所以采用常引用。

引用与多态的关系:

曾经测试过这样的关系,写过代码,用来实现表达的是:一个基类的引用可以指向其派生类的实例。

4:多态的作用

1) 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用。

2) 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性的正确使用。

5:什么情况下只能使用成员初始化列表,而不能用赋值?

1) 类中有const,reference成员变量,对他们进行初始化的时候,只能是在成员初始化列表中。为什么呢?const,reference在定义的时候,都必须初始化,以后都不可更改,正因为有这样的属性,若是在类的构造函数中对他们进行初始化,则就有被修改的可能。所以,对他们的初始化放在初始化列表中,在未进入构造函数的时候,就完成初始化工作。

2) 派生类构造函数调用基类的构造函数的时候都要使用初始化列表。

理解:派生类定义对象的时候,首先要调用基类的构造函数进行初始化,再是派生类自己的构造函数进行初始化工作。而在派生类的构造函数中,使用初始化列表的形式,去调用基类的构造函数,完成初始化操作。

面试宝典上一道题的分析:(多态)




代码

#include <iostream>

using namespace std;

class A

{

public:

A(int data=0)

{

m_data=data;

}

int GetData()

{

return DoGetData();

}

virtual int DoGetData()

{

return m_data;

}

protected:

int m_data;

};

class B: public A

{

public:

B(int data=1)

{

m_data=data;

}

int DoGetData()

{

return m_data;

}

protected:

int m_data;

};

class C:public B

{

public:

C(int data=2)

{

m_data=data;

}

protected:

int m_data;

};

int main()

{

C c(10);

cout<<c.GetData()<<endl;        // 1

cout<<c.A::GetData()<<endl;     // 1

cout<<c.B::GetData()<<endl;     // 1

cout<<c.C::GetData()<<endl;     // 1

cout<<c.DoGetData()<<endl;     // 1

cout<<c.A::DoGetData()<<endl;   // 0

cout<<c.B::DoGetData()<<endl;   // 1

cout<<c.C::DoGetData()<<endl;   // 1

return 0;

}


分析:

c.GetData():这个动作,C的对象调用从基类继承下来的成员函数GetData(),此刻的GetData()还是属于A的,

为什么呢?因为,类的一般的成员函数,只有一份,系统只维护一份。接着这个GetData()函数里面又去调用

另外一个方法,而这个方法是虚方法,问题就来了--->到底调用哪个虚方法呢?这就是多态,此时,对象c,

会去根据这个对象的vptr,遍历C类的虚函数表,找到个虚方法(虚方法都放在虚函数表中)。所以,调用的是

B类的虚方法,因为C继承自B,而C类中未改写B类的这个虚方法,所以是B的虚方法。

c.A::GetData():这个动作,编译的时候静态绑定调用的是A::GetData(),明确说了,是A的成员函数,但里面

调用的却是一个虚方法,需要动态绑定。最终:结果和上面分析的一样。

c.B::GetData(),c.C::GetData():情形都和上面的一样。

c.DoGetData():这个动作,直接调用虚方法。对象c根据自己的vptr,去vtbl中找到这个虚方法来,调用它,就OK。

c.A::DoGetData()--->这个动作,是在编译的时候,就静态绑定了,调用哪一个虚函数,此时调用的是A的虚函数,

则要到A类的虚函数表中去找这么一个虚函数,为什么能够找到呢?因为,C c的时候,要先调用基类的构造函数

进行初始化,再是自己的构造函数。在调用基类构造函数的时候,就会为基类的虚函数表生成一个vptr(虚函数表

指针),所以,在c.A::DoGetData()这种情形下,能够顺利的通过基类的vptr,找到那个虚函数,调用它。

c.B::DoGetData(),c.C::DoGetData():情形和上面的一样。

6:重载,覆盖,隐藏的比较分析

重载:

同一个类中谈论重载才具有意义

函数名字相同,但参数个数不同,或者类型不同

virtual,可有可无

覆盖:(子类覆盖基类)

不同类中,才谈论覆盖;在父类和子类中才存在这种关系

函数名字相同,参数相同

最重要的一点:必须是virtual函数,只有虚函数存在的前提下,才谈论覆盖的问题。

隐藏:

这个比较生疏,接触少些。

也是在不同类中,基类和派生类中

派生类和基类有同名函数,但参数不同,不管有无virtual,基类的这个函数将会被隐藏。

派生类和基类有同名函数,且参数相同,但不是virtual,基类函数被屏蔽。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: