C++学习总结(一)

来源:互联网 发布:好的手机壁纸软件 编辑:程序博客网 时间:2024/06/07 15:04

new在创建类中的使用

new在创建类时确实有很多好处,但是也有局限性,比如在频繁调用时局部new类对象就显得效率很低。

new主要带来以下几点:

  • new创建类对象需要指针接收,一处初始化,多处使用;

  • new创建类对象使用完需要delete销毁;

  • new创建类对象之间使用堆空间,而局部不用new定义的类对象则使用栈空间;

  • new对象指针用途广泛,比如作为函数返回值、函数参数等

  • 频繁调用场合并不适用new

再谈抽象类

不用来定义对象而只作为一种基类型用作继承的类,称为抽象类(abstract class),凡是包含纯虚函数(pure virtual function)的类都是抽象类。

因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。

抽象类的作用是作为一个类族的共同基类,或者说是一个类族的一个公共接口。

如果在抽象类所派生出来的新类中对基类的所有纯虚函数进行了定义,那么这些函数就被赋予了功能,可以被调用。派生类就是具体类(concrete class)。如果在派生类中没有对所有纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。

虽然抽象类不能定义对象(或者说抽象类不能实例化),但是可以定义指向抽象类数据的指针变量。当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现多态性的操作。

类成员变量初始化总结

常用的有5种方法:

  1. 在无参数的构造函数中初始化;

  2. 带参数的构造函数中初始化;

  3. 直接给成员变量赋值;

  4. 调用成员函数来初始化成员变量;

  5. this指针。

针对不同的变量类型,在选择初始化方法时,有不同的优先顺序:

  • 普通的变量
    一般不考虑效率的情况下可以在构造函数中进行赋值。考虑效率的可以在构造函数的初始化列表中进行。

  • static 静态变量
    类外进行初始化。static变量属于类所有,而不属于类的对象,因此不管类被实例化了多少个对象,该变量都只有一个。在这种性质上理解,有点类似于全局变量的唯一性。

  • const 常量变量
    const常量需要在声明的时候即初始化,因此需要在变量创建的时候进行初始化,必须采用在构造函数的初始化列表中进行。

  • & 引用型变量
    引用型变量和const变量类似,需要在创建的时候即进行初始化,也是必须在初始化列表中进行。

  • const static integral 变量
    对于既是const又是static而且还是整形变量,C++是给予特权的。可以直接在类的定义中初始化。short可以,但float的不可以。

总结起来:

  1. 在类的定义中进行的,只有conststaticintegral的变量。

  2. 在类的构造函数初始化列表中,包括普通变量,const常量(不包含第一种情况)和引用变量(&)。

  3. 在类的定义之外初始化的,包括static变量。因为它是属于类的唯一变量。

  4. 普通的变量可以在构造函数的内部,通过赋值方式进行。当然这样效率不高。

  5. const数据成员(非static)必须在构造函数的初始化列表中初始化。

  6. 数组成员是不能在初始化列表里初始化的。

  7. const staticstatic const是一样的,这样的变量可以直接在类定义中初始化,也可以在类外。 说明了一个问题:C++里面是不能定义常量数组的!因为5和6的矛盾。

类对象的构造顺序是这样的:

  1. 分配内存,调用构造函数时,隐式/显示的初始化各数据成员 ;

  2. 进入构造函数后在构造函数中执行一般计算。

函数指针

函数指针:一个指向函数的指针,表示一个函数的入口地址,可以在运行时根据数据的具体状态来选择相应的处理方式。

在动态调用DLL的函数时会用到函数指针。最典型的是回调函数。

回调函数其实就是一个通过函数指针调用的函数!假如你把A函数的指针当作参数传给B函数,然后在B函数中通过A函数传进来的这个指针调用A函数,那么这就是回调机制。A函数就是回调函数,而通常情况下,A函数是系统在符合你设定条件的情况下会自动执行,比如Windows下的消息触发等等。那么调用者和被调用者的关系就被拉开了,就像是中断处理函数那样。

函数指针应该能够指向对应类型的任何变量。而函数的类型靠这几方面来确定:(1)函数的参数个数 (2)函数的参数类型(3)函数的返回值类型。

C语言中的定义:

返回类型 (*函数指针名称)(参数类型,参数类型……);

C++中的定义:

返回类型 (类名称::*函数成员名称) (参数类型,参数类型……)

Tips:
回调函数必须是全局函数或者静态成员函数,因为普通的成员函数会隐含着一个传递函数作为参数,也就是this指针。因此如果使用普通成员函数作为回调函数的话会导致函数参数个数不匹配,因此编译失败。这也是线程函数是多为静态函数的原因。
我们还注意到回调函数用CALLBACK修饰,我们可以在windef.h中发现:

#define CALLBACK __stdcall  

CALLBACK其实就是__stdcall,还记得上篇讲过的函数调用约定吗?

虚函数表解析

编译器会为每个有虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。在有虚函数的类的实例中分配了指向这个表的指针。

虚函数表的指针存在于对象实例中最前面的位置。通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

虚函数与重载函数的区别

  • 重载函数在类型和参数数量上一定不相同,而重定义的虚函数则要求参数的类型和个数、函数返回类型相同;

  • 虚函数必须是类的成员函数,重载的函数则不一定是这样;

  • 构造函数可以重载,但不能是虚函数,析构函数可以是虚函数。

例:

class A{public:    virtual int f(unsigned char ch) {return --ch;}};class B : public A{    int f(char ch) {return ++ch;}   //此为函数重载};void main(){    A* p=new B;    int n=p->f(40);        //调用基类的 f()    cout<<" the result is : "<<n<<endl;}

运行结果:the result is : 39

class A{public:    virtual int f(unsigned char ch) {return --ch;}};class B : public A{    int f(unsigned char ch) {return ++ch;}      //此为虚函数};void main(){       A *p=new B;    int n=p->f(40);        //调用派生类的 f()    cout<<" the result is : "<<n<<endl;}

运行结果:the result is : 41

重载、重写、覆盖

一、重载(overload)
指函数名相同,但是它的参数表列个数或顺序,类型不同。但是不能靠返回类型来判断。

(1)相同的范围(在同一个作用域中) ;

(2)函数名字相同;

(3)参数不同;

(4)virtual 关键字可有可无。

(5)返回值可以不同;

二、重写(也称为覆盖 override)
是指派生类重新定义基类的虚函数,特征是:
(1)不在同一个作用域(分别位于派生类与基类) ;

(2)函数名字相同;

(3)参数相同;

(4)基类函数必须有 virtual 关键字,不能有 static 。

(5)返回值相同(或是协变),否则报错;

(6)重写函数的访问修饰符可以不同。尽管 virtual 是 private 的,派生类中重写改写为 public,protected 也是可以的

三、重定义(也称隐藏)
(1)不在同一个作用域(分别位于派生类与基类) ;

(2)函数名字相同;

(3)返回值可以不同;

(4)参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载以及覆盖混淆) 。

(5)参数相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 。

基类与派生类对象的指针赋值

派生类对象也基类对象,但两者不同。

派生类对象可以当做基类对象,这是因为派生类包含基类的所有成员。

但是基类对象无法被当做成派生类对象,因为派生类可能具有只有派生类才有的成员。

所以,将派生类指针指向基类对象的时候要进行显示的强制转换,否则会使基类对象中的派生类成员成为未定义的。

基类指针和派生类指针指向基类对象和派生类对象的方法:

  1. 基类指针指向基类对象,只需要通过基类指针简单地调用基类的功能。

  2. 派生类指针指向派生类对象,只需要通过派生类指针简单地调用派生类功能。

  3. 基类指针指向派生类对象是安全的,因为派生类对象“是”它的基类的对象。但是要注意的是,这个指针只能用来调用基类的成员函数。
    如果试图通过基类指针调用派生类才有的成员函数,则编译器会报错。
    为了避免这种错误,必须将基类指针强制转化为派生类指针。然后派生类指针可以用来调用派生类的功能。这称为向下强制类型转换,这是一种潜在的危险操作。
    注意:如果在基类和派生来中定义了虚函数(通过继承和重写),并通过基类指针在派生类对象上调用这个虚函数,则实际调用的是这个函数的派生类版本。

  4. 派生类指针指向基类对象, 基类对象并不包含派生类才有的成员,这些成员只能通过派生类指针调用。

extern关键字

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字。它告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。

  1. 对于extern变量来说,仅仅是一个变量的声明,其并不是在定义分配内存空间。如果该变量定义多次,会有连接错误;

  2. 通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。也就是说c文件里面定义,如果该函数或者变量与开放给外面,则在h文件中用extern加以声明。所以外部文件只用include该h文件就可以了。

  3. extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。

内联函数 inline

定义:内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。

作用:内联扩展是用来消除函数调用时的时间开销。它通常用于频繁执行的函数。 一个小内存空间的函数非常受益。

建议把inline函数的定义放到头文件中。在每个调用该inline函数的文件中包含该头文件。这种方法保证对每个inline函数只有一个定义,且程序员无需复制代码,并且不可能在程序的生命期中引起无意的不匹配的事情。

推荐编程风格:

// 头文件class A{public:    void Foo(int x, int y);}// 定义文件inline void A::Foo(int x, int y){} 

本文来自我的独立博客,欢迎访问。

0 0