C++语法-重点温习

1. 内存的使用
   自动变量,其内存在栈中分配
   外部变量,或称全局变量,其空间分配在数据段
   静态变量,
   - 只能由在本文件中定义的程序存取
   - 在一个函数中定义,要求他们的值在调用另一函数时能保存下来的局部变量,
   - 其只能赋值一次,并且存放在数据段,局部变量存放在栈中.
   用显式命令new分配的的数据放在堆中
   指针,*,&,指针通常指向堆上的变量.指针用于动态数组,链表等动态结构,给函数传入参数.

2. C++中const总结
一:对于基本声明
   1.const int r=100;
   //标准const变量声明加初始化,因为默认内部连接所以必须被初始化,其作用域
   为此文件,编译器经过类型检查后直接用100在编译时替换.
   2.extend const int r=100;
   //将const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行
   初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义.
   3.const int r[]={1,2,3,4};
   struct S {int a,b;};
   const S s[]={(1,2),(3.4)};
   //以上两种都是常量集合,编译器会为其分配内存,所以不能在编译期间使用其中
   的值,例如:int temp[r[2]];这样的编译器会报告不能找到常量表达式
二:对于指针
   1.const int *r=&x;
   //声明r为一个指向常量的x的指针,r指向的对象不能被修改,但他可以指向任何
   地址的常量.
   2.int const *r=&x;//与用法1完全等价,没有任何区别。
   3.int * const r=&x;
   //声明r为一个常量指针,他指向x,r这个指针的指向不能被修改,但他指向的地址
   的内容可以修改.
   4.const int * const r=&x;
   //综合1,3用法,r是一个指向常量的常量型指针.
三:对于类型检查
   可以把一个非const对象赋给一个指向const的指针,因为有时候我们不想从这个
   指针来修改其对象的值,但是不可以把一个const对象赋值给一个非const指针,
   因为这样可能会通过这个指针改变指向对象的值,但也存在使这种操作通过的合
   法化写法,使用类型强制转换可以通过指针改变const对象:
   const int r=100;
   int *ptr=const_cast<int*>(&r);//C++标准,C语言使用:int* ptr =(int*)&r;
四:对于字符数组
   如char * name = "china";
   这样的语句,在编译时是能够通过的,但是"china"是常量字符数组,任何想修改
   他的操作也能通过编译但会引起运行时错误,如果我们想修改字符数组的话就要
   使用char name[]="china";这种形式.
五:对于函数
   1.void Fuction1(const int r);
   //此处为参数传递const值,意义是变量初值不能被函数改变
   2.const int Fuction1(int);
   //此处返回const值,意思指返回的原函数里的变量的初值不能被修改,但是函数
   按值返回的这个变量被制成副本,能不能被修改就没有了意义,它可以被赋给任何
   的const或非const类型变量,完全不需要加上这个const关键字.但这只对于内部
   类型而言(因为内部类型返回的肯定是一个值,而不会返回一个变量,不会作为左
   值使用),对于用户自定义类型,返回值是常量是非常重要的,见下面条款3
   3.Class CX; //内部有构造函数,声明如CX(int r =0)
   CX Fuction1 () { return CX(); }
   const CX Fuction2 () { return CX(); }
   如有上面的自定义类CX,和函数Fuction1()和Fuction2(),我们进行如下操作时:
   Fuction1()=CX(1); //没有问题,可以作为左值调用
   Fuction2()=CX(1); //编译错误,const返回值禁止作为左值调用.因为左值
              把返回值作为变量会修改其返回值,const声明禁止这种修改.
   4.函数中指针的const传递和返回
   int F1 (const char * pstr);
   //作为传递的时候使用const修饰可以保证不会通过这个指针来修改传递参数的
   初值,这里在函数内部任何修改*pstr的企图都会引起编译错误.
   const char* F2();
   //意义是函数返回的指针指向的对象是一个const对象,它必须赋给一个同样是指
   向const对象的指针.
   const char* const F3();
   //比上面多了一个const,这个const的意义只是在他被用作左值时有效,它表明这
   个指针除了指向const对象外,它本身也不能被修改,所以就不能当作左值来处理.
   5.函数中引用的const传递
   void F1 (const X& px);
   //这样的一个const引用传递和最普通的函数按值传递的效果是一模一样的,他禁
   止对引用的对象的一切修改,唯一不同的是按值传递会先建立一个类对象的副本,
   然后传递过去,而它直接传递地址,所以这种传递比按值传递更有效.
   另外只有引用的const传递可以传递一个临时对象,因为临时对象都是const属性,
   且是不可见的,他短时间存在一个局部域中,所以不能使用指针,只有引用的const
   传递能够捕捉到这个家伙.
六:对于类
   1.首先,对于const的成员变量,只能在构造函数里使用初始化成员列表来初始化,
   试图在构造函数体内进行初始化const成员变量会引起编译错误.初始化成员列表
   形如:X::X(int ir):r(ir){} //假设r是类X的const成员变量
   2.const成员函数.提到这个概念首先要谈到const对象,正象内置类型能够定义
   const对象一样(const int r=10;),用户自定义类型也可以定义const对象
   (const X px(10);),编译器要保证这个对象在其生命周期内不能够被改变.如果
   你定义了这样的一个const对象,那么对于这个对象的一切非const成员函数的调
   用,编译器为了保证对象的const特性,都会禁止并在编译期间报错.所以如果你想
   让你的成员函数能够在const对象上进行操作的话,就要把这个函数声明为const
   成员函数.
   假如f()是类中的成员函数的话,它的声明形如:
   int f()const;
   //const放在函数的最后,编译器会对这个函数进行检查,在这个
   函数中的任何试图改变成员变量和调用非const成员函数的操作都被视为非法
   注意:类的构造和析构函数都不能是const函数.
   3.建立了一个const成员函数,但仍然想用这个函数改变对象内部的数据.这样的
   一个要求也会经常遇到,尤其是在一个苛刻的面试考官那里.首先我们要弄清楚考
   官的要求,因为有两种方法可以实现,如果要求不改变原来类的任何东西,只让你
  从当前这个const成员函数入手,那么你只有使用前面提到的类型强制转换方法.实例如下:
   //假如有一个叫做X的类,它有一个int成员变量r,我们需要通过一个const成员函
   数f()来对这个r进行++r操作,代码如下:
   void X::f()const
   {const_cast<X*>(this)->++r; } //通过this指针进行类型强制转换实现
   另外一种方法就是使用关键字:mutable.
   如果你的成员变量在定义时是这个样子的:mutable int r;
   那么它就告诉编译器这个成员变量可以通过const成员函数改变.编译器就不会再
   理会对他的检查了

3. 函数指针的使用
Example1:
#include "stdio.h"
typedef short SHORTFUNCTION(void);
short CalledFunction( void)
{
    printf("In the Called Function!!/n");
    return 1;
}
SHORTFUNCTION *CoreProgram;   
int main(int argc, char* argv[])
{
    CoreProgram = CalledFunction;
    (*CoreProgram)();
   
    getchar();
    return 0;
}
Example2:
#include "stdio.h"

typedef short SHORTFUNCTION(void);

short CalledFunction1( void)
{
    printf("In the Called Function 1!!/n");
    return 1;
}
short CalledFunction2( void)
{
    printf("In the Called Function 2!!/n");
    return 1;
}


typedef struct
{
    int  TypeId;
    SHORTFUNCTION *CallProgram;             
}SWITCHCASE_STRUCT;

const SWITCHCASE_STRUCT SwitchCaseTable[2] =
{
    {
        1,
        CalledFunction1   
    },
    {
        2,
        CalledFunction2
    }
};


SHORTFUNCTION* GetSwitchCaseFunction(int type)
{
    SHORTFUNCTION *pFunc;
    switch(type) {
    case 0:
        pFunc = SwitchCaseTable[0].CallProgram;
        break;
    case 1:
        pFunc = SwitchCaseTable[1].CallProgram;
        break;
    }

    return pFunc;
}

int main(int argc, char* argv[])
{
    SHORTFUNCTION *pFunction;

    pFunction = GetSwitchCaseFunction(0);
    (*pFunction)();
    pFunction = GetSwitchCaseFunction(1);
    (*pFunction)();
   
    getchar();
    return 0;
}
4. 函数名重载
函数名可以相同,通过参数不同来实现重载,扩展了名字空间的利用.跟C语言中函数名必须唯一来说是个很大的进步.现在编译之后一个函数要依赖于参数个数和参数类型了,不依赖于参数的名称和返回值.
函数的使用规范
-尽量使用参数而不是定义全局变量
-按值传递简单的输入参数,按引用传递输出参数,对结构类型或类或数组,对输入参数使用const修饰符


5. 多态=多继承

使用多继承的复杂性难度超过其优越性,所以多使用复合或继承的复合.

1.JAVA里没有多继承,一个类之能有一个父类。


而继承的表现就是多态。一个父类可以有多个子类,而在子类里可以重写父类的方法(例如方法print()),这样每个子类里重写的代码不一样,自然表现形式就不一样。这样用父类的变量去引用不同的子类,在调用这个相同的方法 print()的时候得到的结果和表现形式就不一样了,这就是多态,相同的消息(也就是调用相同的方法)会有不同的结果。举例说明:


//父类
public class Father{
//父类有一个打孩子方法
public void hitChild(){}
}
//子类1
public class Son1 extends Father{
//重写父类打孩子方法
public void hitChild(){
System.out.println("为什么打我?我做错什么了!"); }
}
//子类2
public class Son2 extends Father{
//重写父类打孩子方法 public void hitChild(){
System.out.println("我知道错了,别打了!"); }
}
//子类3
public class Son3 extends Father{
//重写父类打孩子方法 public void hitChild(){
System.out.println("我跑,你打不着!"); }
}
//测试类
public class Test{
 public static void main(String args[]){
 Father father;
 father = new Son1();
 father.hitChild();
 father = new Son2(); 
 father.hitChild();
 father = new Son3();
 father.hitChild();
 }
}

都调用了相同的方法,出现了不同的结果!这就是多态的表现!


2.JAVA中没有多继承,而用接口实现了多继承!一个类或是可以同时实现多个接口!(就相当于C++里一个类同时继承了多个类!)例如:


public class Son implements Father1,Father2,Father3{

}

接口有利于代码功能的扩展(增加新的功能)!而继承则有利于代码功能的修改(把旧的功能改成新的功能)!这里旧功能的修改和新功能的增加前提是不修改旧的功能,旧的功能仍然可以使用!旧类的代码也不做修改!


接着上面的继承举例:上面例子中的子类我们统称为一代子类


1.用继承修改旧的功能。


现在如果一代Son1的功能打孩子,也就是方法hitChild()里边的功能实现有问题,我们要把具体实现修改掉,那么我们可以重新写一个新的二代子类Son11继承自一代子类Son1,并重写其hitChild()方法。


//二代子类
public class Son11 extends Son1{
//重写父类打孩子方法
public void hitChild(){ System.out.println("我是二代子类对此方法的新实现");
 }
}

2.用接口实现增加新的功能。


现在类Son1已经有了‘打孩子’功能(也就是方法),而我们有了新的需求,我们需要它还要有一个新的功能‘宠孩子’(新方法pet();)。而旧的代码不能动!那么我们可以定义一个新的接口(PetInterFace),接口里定义这个方法pet()的声明。再重新写一个二代子类Son11,让它先继承类 Son1,同时实现接口 PetInterFace!并实现接口里的 pet()方法!


//新接口
public interface PetInterFace{
//新功能方法 public void pet();
}
//二代子类
public class Son11 extends Son1 implements PetInterFace{
 //实现接口方法
public void pet(){
System.out.println("父亲很爱我!");
}
}

这样这个二代子类即有了老的方法,又有新的功能!


6.虚函数和纯虚函数
在面向对象的C++语言中,虚函数(virtual function)是一个非常重要的概念。因为它充分体现了面向对象思想中的继承和多态性这两大特性,在C++语言里应用极广。比如在微软的MFC类库中,你会发现很多函数都有virtual关键字,也就是说,它们都是虚函数。难怪有人甚至称虚函数是C++语言的精髓。

那么,什么是虚函数呢,我们先来看看微软的解释:

虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。

——摘自MSDN

这个定义说得不是很明白。MSDN中还给出了一个例子,但是它的例子也并不能很好的说明问题。我们自己编写这样一个例子:

#include "stdio.h"
#include "conio.h"

class Parent
{

public:

char data[20];

void Function1();

virtual void Function2(); // 这里声明Function2是虚函数

}parent;

void Parent::Function1()

{

printf("This is parent,function1/n");

}

void Parent::Function2()

{

printf("This is parent,function2/n");

}



class Child:public Parent

{

void Function1();

void Function2();

} child;

void Child::Function1()

{

printf("This is child,function1/n");

}

void Child::Function2()

{

printf("This is child,function2/n");

}



int main(int argc, char* argv[])

{

Parent *p; // 定义一个基类指针

if(_getch()=='c') // 如果输入一个小写字母c

p=&child; // 指向继承类对象

else

p=&parent; // 否则指向基类对象

p->Function1(); // 这里在编译时会直接给出Parent::Function1()的

入口地址。

p->Function2(); // 注意这里,执行的是哪一个Function2?

return 0;

}

用任意版本的Visual C++或Borland C++编译并运行,输入一个小写字母c,得到下面的结果:

This is parent,function1

This is child,function2

为什么会有第一行的结果呢?因为我们是用一个Parent类的指针调用函数Fuction1(),虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Parent类的函数来理解并编译,所以我们看到了第一行的结果。

那么第二行的结果又是怎么回事呢?我们注意到,Function2()函数在基类中被virtual关键字修饰,也就是说,它是一个虚函数。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。如果我们在运行上面的程序时任意输入一个非c的字符,结果如下:

This is parent,function1

This is parent,function2

请注意看第二行,它的结果出现了变化。程序中仅仅调用了一个Function2()函数,却可以根据用户的输入自动决定到底调用基类中的 Function2还是继承类中的Function2,这就是虚函数的作用。我们知道,在MFC中,很多类都是需要你继承的,它们的成员函数很多都要重载,比如编写MFC应用程序最常用的CView::OnDraw(CDC*)函数,就必须重载使用。把它定义为虚函数(实际上,在MFC中OnDraw不仅是虚函数,还是纯虚函数),可以保证时刻调用的是用户自己编写的OnDraw。虚函数的重要用途在这里可见一斑。

再看下面的
-----------------------------------------------------------
摘自:C++中虚函数和纯虚函数的概念,差别和分别存在的原因


首先:强调一个概念
定义一个函数为虚函数,不代表函数为不被实现的函数
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数
定义一个函数为纯虚函数,才代表函数没有被实现
定义他是为了实现一个接口,起到一个规范的作用,规范继承这个
类的程序员必须实现这个函数。

对继承的影响:
普通的类(没有虚函数,纯虚函数)就可以被继承,而且工作的相当好

关于这个问题有以下疑问:
纯虚函数难道就是为了实现接口?接口存在的意义?
我实在弄不懂,我干嘛要预先定义好?未来的事情本难料
就等有一天我的类中需要使用某个函数,在添加一个函数
不久可以?

关于实例化一个类:
有纯虚函数的类是不可能生成类对象的,如果没有纯虚函数则可以。比如:
class CA
{
public:
virtual void fun() = 0; // 说明fun函数为纯虚函数
virtual void fun1();
};

class CB
{
public:
virtual void fun();
virtual void fun1();
};

// CA,CB类的实现
...

void main()
{
CA a; // 不允许,因为类CA中有纯虚函数
CB b; // 可以,因为类CB中没有纯虚函数
...
}

---------------------------------------------------------------
虚函数在多态中间的使用:
多态一般就是通过指向基类的指针来实现的。

dog mydogwangwang;
mydogwangwang.born();
一定是返回“dog”

那么
horse myhorsepipi;
myhorsepipi.born();
一定是返回“horse”

也是多态呀?
/
有一点你必须明白,就是用父类的指针在运行时刻来调用子类:
例如,有个函数是这样的:
void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}

参数maybedog_maybehorse在编译时刻并不知道传进来的是dog类还是horse类,所以就把它设定为animal类,具体到运行时决定了才决定用那个函数。
也就是说用父类指针通过虚函数来决定运行时刻到底是谁而指向谁的函数。

//用虚函数
#include <iostream.h>

class animal
{
public:
animal();
~animal();
void fun1(animal *maybedog_maybehorse);
virtual void born();

};

void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}




animal::animal()
{
}
animal::~animal()
{
}
void animal::born()
{
cout<< "animal";
}

class dog: public animal
{
public:
dog();
~dog();
virtual void born();
};

dog::dog()
{
}
dog::~dog()
{
}
void dog::born(){
cout<<"dog";
}

class horse:public animal
{
public:
horse();
~horse();
virtual void born();
};

horse::horse()
{
}

horse::~horse()
{
}
void horse::born(){
cout<<"horse";
}

void main()
{
animal a;
dog b;
horse c;
a.fun1(&c);
}

//output: horse

/
//不用虚函数

#include <iostream.h>

class animal
{
public:
animal();
~animal();
void fun1(animal *maybedog_maybehorse);
void born();

};

void animal::fun1(animal *maybedog_maybehorse)
{
maybedog_maybehorse->born();
}




animal::animal()
{
}
animal::~animal()
{
}
void animal::born()
{
cout<< "animal";
}

class dog: public animal
{
public:
dog();
~dog();
void born();
};

dog::dog()
{
}
dog::~dog()
{
}
void dog::born(){
cout<<"dog";
}

class horse:public animal
{
public:
horse();
~horse();
void born();
};

horse::horse()
{
}

horse::~horse()
{
}
void horse::born(){
cout<<"horse";
}

void main()
{
animal a;
dog b;
horse c;
a.fun1(&c);
}


output: animal



---------------------------------------------------------------

有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。
---------------------------------------------------------------

定义纯虚函数就是为了让基类不可实例化化,
因为实例化这样的抽象数据结构本身并没有意义.
或者给出实现也没有意义
实际上我个人认为纯虚函数的引入,是出于两个目的,
1.为了安全.因为避免任何需要明确但是因为不小心而导致的未知的结果.
提醒子类去做应做的实现.
2.为了效率,不是程序执行的效率,而是为了编码的效率.

一、引入原因:
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

二、纯虚函数实质:
类中含有纯虚函数则它的VTABLE表不完全,有一个空位,所以,不能生成对象(编译器绝对不允许有调用一个不存在函数的可能)。在它的派生类中,除非重载这个函数,否则,此派生类的VTABLE表亦不完整,亦不能生成对象,即它也成为一个纯虚基类。

三、虚函数与构造、析构函数:
1、构造函数本身不能是虚拟函数;并且虚机制在构造函数中不起作用(在构造函数中的虚拟函数只会调用它的本地版本)。
想一想,在基类构造函数中使用虚机制,则可能会调用到子类,此时子类尚未生成,有何后果!?。
2、析构函数本身常常要求是虚拟函数;但虚机制在析构函数中不起作用。
若类中使用了虚拟函数,析构函数一定要是虚拟函数,比如使用虚拟机制调用delete,没有虚拟的析构函数,怎能保证delete的是你希望delete的对象。
虚机制也不能在析构函数中生效,因为可能会引起调用已经被delete掉的类的虚拟函数的问题。

四、对象切片:
向上映射(子类被映射到父类)的时候,会发生子类的VTABLE 完全变成父类的VTABLE的情况。这就是对象切片。
原因:向上映射的时候,接口会变窄,而编译器绝对不允许有调用一个不存在函数的可能,所以,子类中新派生的虚拟函数的入口在VTABLE中会被强行“切”掉,从而出现上述情况。

五、虚拟函数使用的缺点
优点讲了一大堆,现在谈一下缺点,虚函数最主要的缺点是执行效率较低,看一看虚拟函数引发的多态性的实现过程,你就能体会到其中的原因。



六、重载与覆盖的区别
重(zhong)载是说一个方法要完成多项任务(同名),返回类型可以不一样,参数也可以不同。
覆盖是说对方法的加强,方法名字和参数必须一样(包括顺序),
我的理解是重载是同一类中的方法,方法名相同,参数不同。
而覆盖是子类对父类或类对接口方法的实现。
重载和覆盖的相同点是方法名相同
重载和覆盖的不同点是重载时传递的参数类型或者个数不同,而覆盖时传递的参数类型和个数必须相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值