《C++面向对象程序设计-基于Visual C++ 2010》读书笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011475134/article/details/80719702

数据类型与基本运算

  1. 字符串常量按字符书写顺序依次存储在内存中,并在最后存放空字符’\0’表示字符串常量的结束。ASCII字符在内存中占1个字节,而中文字符占2个字节

  2. 有名常量是指用关键字const修饰的变量。由于该变量只能读取,而不能被修改,所以 也称为常变量。有名常量必须在定义时进行初始化,之后不再允许赋值。例如:

const double PI=31415926;
const int Max=1000;

有名常量与变量一样,存储在程序的数据区中,可以按地址进行访问。变量在初始化之后还可以对其进行修改,但对有名常量的任何修改都会引发编译器报错。

使用有名常量的好处在于:

  • 增加程序的可读性——用具有实际含义的标识符代替具体的数值,程序的可读性大大增强;
  • 便于程序的维护——假设程序中多处用到圆周率,如果需要提高它的精度,则只需在有名常量的定义处修改即可。对于大型软件,程序的可读性和可维护性是两个极其重要的评价指标。

    1. 位运算的操作数只能是boolcharshortint类型数值,不能是floatdouble实型数。支持的运算有按位取反(~)、左移(<<)、 右移(>>)、按位与(&)、按位或(|)和按位异或(^)。有符号数时,向左移动n位,丢弃左边n位数据,并在右边填充0,同时把最高位作为符号位;向右移动n位,丢弃右边n位数据,而左边正数补0,负数补1。

    2. 不能用变量来定义数组大小

    3. 指针变量与整数相加或相减的结果是指针前移或后移若干个单元,单元大小为sizeof(type)

    4. 在输入输出语句中插入hex(十六进制)、oct(八进制)和dec(十进制)指明输入输出数据认定的格式。例如:

//以十六进制输入数据
//若输入f  11,则x和y的值分别为15和17
cin>>hex>>x>>y;
//hex为十六进制格式控制符,输出100
//设置过hex后,整数均以十六进制格式输出,除非用oct或dec重新设置
cout<<hex<<256<<endl;
  1. 对于字符数组,不能用等号运算符对其赋值。strcpy(参数1,参数2)是系统提供的函数,其功能是将参数2的内容复制到参数1所指定的字符数组中。例如:
//初始化的时候可以直接赋值
char str[20] = "";
strcpy(str, "星期日");

基本控制结构和函数

  1. 跳转语句

    • break:在循环语句中,break语句的作用是终止循环,流程跳转至循环语句之后。需要注意的是,对于循环嵌套语句,如果break语句是在内循环中,则其只能终止其所在的循环语句的执行,流程跳转至外循环。
    • continue:其功能是将流程跳转至当前循环语句的条件表达式处,判断是否继续进行循环。

continue语句与break语句的区别是:continue语句是终止本轮循环,而break语句是终止本层循环。此外,continue语句只能用在循环语句中。

  1. ^表示按位异或

  2. C++容许在函数定义时为形参指定默认值。默认值的指定遵守“从右到左连续定义”的规则。例如:

double max(double a, double b=0, double c=0);
  1. 某些情况下,我们需要修改实参的值,而某些情况下,我们不想修改实参的值:

    • 在C语言中:如果不想修改我们就直接传递实参,如果对象太大我们就传递指针,并且声明指针是指向const对象的;如果想修改实参的值,我们只能传递实参的指针,然而此时指针就不能用const修饰了。
    • 在C++中,我们仍然可以使用C中的方式,然而也可以使用“引用”方式:如果不想修改实参的值,我们使用const引用(就是“常量引用”)方式,这样就不可以通过引用修改被引用对象的值;如果想修改实参的值,则使用平常性的引用实参就可以了。
  2. 内联函数:通常inline限定符只用于那些非常小并且被频繁使用的函数。例如:

inline bool isNumber(char ch) {
    return ch>=’0’ && ch<=’9’?true:false;
}

类与对象

  1. 类与对象

C++编译器在生成程序时是将反映对象特征的数据成员分开,独立保存于程序的数据存储区域,而在程序的代码区仅保存一份成员函数。也就是说,物理上对象的数据成员和成员函数是分离的,并且成员函数是分享的。

程序在生成过程中,在类的成员函数形参表的最前端,编译器为其添加一个指向对象的指针,并命名该形参名为this,称为this指针。当通过对象调用成员函数时,系统将对象的地址传递给所调用成员函数的this指针,从而实现对象与成员函数的正确绑定。

类的对象在逻辑上是相互独立的。在物理上,对象的数据是独立的,不同的对象拥有不同的数据,但是,类的成员函数却只有一份,为类的所有对象共有。

  1. 如果有参的构造函数的所有形参都指定了默认值,那么该构造函数即可充当默认构造函数的角色。此时,不需要(其实也不能)再定义默认构造函数。如果再定义默认构造函数,同样会被视为类中有两个默认构造函数,引发调用匹配错误。

  2. 拷贝构造函数的形参只能说明为类的对象的引用,如

Student(Student&);

拷贝构造函数是在对象被复制时被调用,如果拷贝构造函数是以传值方式传递实参,由于在调用类的拷贝构造函数时,实参要被复制给形参,这种复制的结果就是导致再一次调用该类的拷贝构造函数,产生无穷的递归调用。

为避免在拷贝构造函数中不小心改变了原对象中的数据成员,通常在拷贝构造函数的形参前加上const修饰符。

//类中声明
Student(const Student&);
  1. 构造函数的调用顺序与定义顺序一致。对象调用析构函数的顺序正好与构造函数的调用顺序相反。这是由于这些对象都存储在程序的栈区,先定义的对象先被压栈,而销毁的过程与对象从栈中弹出的顺序一致。

  2. 类的复用技术——组合

Class Point { 
private:
    int x;
    int y;
};

Class Circle {
private:
    double radius;
    Point center;
};

从构造函数Circle(int a,int b,double r):center(a,b),radius(r){}的设计可知,成员初始化列表不仅能对成员对象进行初始化,而且也可以用于对普通数据成员赋初值。对于普通的数据成员即可以在构造函数的函数中对其赋值,也可以在成员初始化列表中完成,但对于对象成员却只能在列表。对象成员构造函数的调用先于类的构造函数。

Circle(int a,int b,double r) {
    center=Point(a,b);
    radius=r;
}

上面的构造函数虽然也完成了对象的拷贝功能,但由于其没有在成员初始化列表中明确成员对象的赋值,因此先调用了成员对象的默认拷贝函数对其赋初值。实际完成赋值任务的语句是center=Point(a,b);,该语句先生成一临时无名对象,再调用系统提供的赋值功能把临时无名对象的数据成员值拷贝给对象的对象成员center,之后临时无名对象被销毁。

含有对象成员的类在对其对象初始化时,构造函数是先调用对象成员的构造函数对成员对象初始化。调用顺序与成员对象在初始化列表中的次序无关,与其在类中声明的次序一致。

  1. 类中的静态成员

类的静态数据成员与全局变量相比具有两个优点:

  • 不存在与程序中其他全局名字冲突的可能性;
  • 类中的数据成员可设置为私有,实现信息隐藏。

对于非静态数据成员,每个类的对象都有自己独立的数据部分,而静态数据成员对类的所有对象只有一份,保存在程序的数据区。

无论类的对象定义与否,类的静态数据成员都存在。在类中,静态数据成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏数据隐藏的原则,保证了数据的安全性。

静态数据成员是在类定义中用static关键字修饰的数据成员,静态数据成员的初始化与一般数据成员初始化不同。

类的静态数据成员属于类。即使在程序中没有定义类的对象,类的静态数据成员也会在数据区生成并被初始化,因此无论类的对象是否已定义,类的静态数据成员在程序加载时生成。

class staticMemberExample {
private:
    int no;
    static int total;
};

//在类外对静态数据成员进行初始化,注意:前面不加static
int staticMemberExample::total=0;

对象的静态数据成员和普通数据成员存储在不同的区域,前者在数据区,后者在栈区。从sizeof(object)的值为4可知,对象object中仅存储了对象的no信息,因为int类型数据的大小就是4。对象的静态数据成员与普通数据成员在内存中的存储位置不同,前者与全局变量相同,在数据区,后者可在栈或自由存储区。

  1. 静态成员函数

类的静态函数成员属于类,与类的对象无关。即使在程序中没有定义类的对象,也可以通过类名直接调用静态成员函数。静态成员函数无法访问类的非静态数据成员,也不能直接调用类的非静态成员函数,只能访问静态数据成员和调用其他的静态成员函数。若要访问类中非静态的成员时,必须通过函数参数传递类的对象给静态成员函数,通过对象才能访问非静态成员(数据成员和成员函数)。

<类名>::<静念成员函数名>(实参表);
<类的对象>.<静态成员函数名>(实参表);

类的静态成员函数提供了一种访问静态数据成员的方式。此外,它还避免使用全局函数,为函数设置了一个类域的访问权限。

类的静态成员函数可以在类内定义,也可以在类外定义。在类外定义时,不能再用static关键字作为其前缀。

  1. 类的友元

类的友元不是类的成员,但如同类的成员函数一样,它可以访问类的私有或保护的成员。类的友元分为友元函数和友元类。

友元函数是类中用关键字friend修饰的非成员函数,该函数可以是普通函数,也可以是另一个类的成员函数。其在类中的声明格式如下:

friend <返回类型> [<类名>::]<函数名>([形参表]);

C++中,友元函数的主要用途是重载运算符和生成迭代器类,以及用友元函数同时访问两个或多个类的私有数据,使程序的逻辑关系更清晰,其余情况应慎用友元函数。

Point& Point::setX(double a) {
    X=a;
    //便于“瀑布式”调用
    return*this;
}

Point类中的setXsetY函数均返回了*this,即对象自身。在主函数中利用该设计实现了所谓的“瀑布式”调用point2.setX(1).setY(20);

在类中声明另一个类是该类的友元类,则友元类中的所有成员函数都是该类的友元函数,可以访问类的所有成员。

friend class<类名>;

类的友元关系是单向的,不具有传递性。类A是类B的友元类,并不意味着类B一定是类A的友元类,除非在类A中也声明B是友元类。同样,如果类A是类B的友元类,类B又是类C的友元类,并不能确定类A也是类C的友元类,友元关系不传递。类的友元关系不被继承,也就是说派生类不继承类的友元关系。

  1. 成员函数实现运算符重载
<返回类型> <类名>::operator<运算符>(<形参表>){<函数体>}

对于双目运算符,<形参表>中应有一个形参,以当前对象作为左操作数,而形参为右操作数。对于单目运算,是以当前对象为操作数,<形参表>中无形参。

对于“++”和“--”运算符,分为前置和后置运算。为能正确区别二者,C++规定用无参成员函数格式表示实现前置运算,用带一个int形参的函数表示实现后置运算,这里的形参不起任何作用。

由于成员函数隐含的第1个参数是this指针,运算符重载函数的左操作数只能是对象,不可能是实数,解决方法是采用友元函数重载运算符。

实际上,形参const Complex&complex&虽然仅一字之差,但实现方式是不一样的。Complex&形参是引用传递,实现时系统是将引用对象的地址压入调用堆栈中。const Complex&由于是const引用,禁止修改被引用对象。为防止修改,编译器在实现const引用时,生成无名临时对象供调用函数访问。事实上,系统在引用const对象时,访问的是一个由系统产生的复制品。

  1. 友元函数实现运算符重载

在引用const对象时,系统会调用构造函数生成无名临时对象。利用该技术可以把实数传给const引用复数类型形参,再由构造函数生成临时复数对象。

friend Complex operator+(const Complex&c1,const Complex&c2);

后置++运算符重载函数的返回类型声明为const Complex&,加const的目的是阻止对对象的连续后置++操作。

  1. 特殊运算符

赋值运算符=、类型转换运算符<类型>()、下标运算符[]和函数调用运算符(),它们都只能重载为成员函数。

Merchandise myGood2=myGoodl;中的等号并不调用赋值运算符重载函数,它等价于Merchandise myGood2(myGoodl);,是通过调用拷贝构造函数实现对象复制。

  1. 多文件结构与编译预处理

文件包含指令的作用是用指定的文件内容替换该指令,其中尖括号<>表示在系统目录的include子目录下寻找该文件,而双引号""表示先在当前文件所在的目录下查找,如果找不到,再到系统指定的文件目录下寻找。

C++的编译预处理指令主要有文件包含指令(#include)、宏定义(#define)以及条件编译指令。所有预处理指令都以“#”开头,以回车符结束,且每条指令单独占一行。

#defime max(a,b)  (((a)>(b))?(a):(b))

需要注意的是,宏替换可能产生错误。如果将上式写成#define max(a,b)a>b?a:b,则语句10+max(x,y)+5被替换成10+x>y:?x:y+5,结果错误。

#undef<宏名>的含义是:取消某个宏名的定义,其后的<宏名>不再进行替换和不再有宏定义。

条件编泽主要用于编译预处理器根据某个条件满足与否来确定某一段代码是否参与编译。常用的条件编译指令包括#if#else#elif#ifdef#ifndef#endif等。

数组、指针及动态内存

  1. int* ap改为int ap[]含义相同,系统视其为int*类型
void show(int* ap,int size){}
  1. C++程序在运行时,其所占用的内存空间被划分为4个区域:代码区、全局数据区、栈区和自由存储区。函数中使用的局部变量多数都分配在栈区,静态变量和全局变量被存储在全局数据区。自由存储区又称为堆区,是由程序员根据需要进行分配和释放的内存区域。

  2. delete运算符中的方括号[]是告诉编译器回收整个数组所占用的内存空间,并且方括号中不需要填写数组的元素数。例如:

int *ptr,n;
//一维数组的动态分配。数组元素的个数可以是变量
ptr=new int[n];
//ptrB是指向int[20]数组类型的指针
int (*ptrB)[20];
//ptrB指向行数为n,列数为20的二维数组
ptrB=new int[n][20];
//回收一维数组int[n]的内存空间
delete []ptr;
//回收二维数组int[n][20]的内存空间
delete []ptrB;

ptr在栈中,所指向的单元在堆中。

  1. 深复制与浅复制

这里写图片描述

对于没有提供拷贝构造函数或赋值运算符重载函数的类,系统将提供一个默认的对应函数。系统所提供的函数只是简单地实现两个对象数据成员的复制,对于没有使用堆区的类,这样的函数能很好地完成复制任务。对于使用了自由存储区的类,如果仅是简单地对指针变量进行赋值操作,则会导致两个甚至更多对象中指向堆区的指针指向同一块内存,即所谓的浅复制。

深复制是复制一个完整且独立的对象的副本,其实质是每个对象都应拥有自己独立的堆空间,并且通过复制保持两个内存区域的内容一致。

  1. const修饰符在C++程序中用途较广,其主要作用是防止对变量或对象的修改操作。const Array& operator=(const Array&)函数形参中的const是防止传递的对象被修改,函数返回类型中的const是禁止修改函数返回的对象。函数bool operator==(const Array&) const后面的const是指该成员函数不能修改类中的任何数据成员,其实质是为成员函数中由编译器为之增加的隐式形参this指针添加const修饰。

  2. 由于递归在实现中存在大量的函数调用,而函数调用会带来参数压栈和弹栈的开销,因此,递归函数在运行过程中的内存空间占用和机时开销高于非递归方式,代码的运行效率相对较低。

  3. 函数指针

如同数组名是数组的首地址一样,函数名代表的是该函数的首地址,也就是函数执行代码的入口地址。

void sort(int n,double array[]);
void (*funPtr)(intdouble[])=sort;

函数指针funPtr指向sort函数。这里,所指函数的形参和函数返回类型需要完全匹配。在函数首地址赋给函数指针时,既可在函数名前添加取地址运算符“&”,也可以不加。

函数本身不能作为函数的形参,但函数指针可以。利用函数指针可以将函数作为实参传递给另一个函数。

  1. typedef关键字含义是“类型定义”,作用是将一种数据类型定义为某一个标识符,在程序中可使用该标识符来实现相应数据类型变量的定义。typedef能简化较为复杂类型的声明,用有明确意义的标识符代替,增强程序的可读性。

类的继承

  1. 继承方式有3种:公有继承(public)、私有继承(private)和保护继承(protected)。默认的继承方式是私有继承,即不指明继承方式等同于私有继承。
class<派生类名>:[<继承方式1>]<基类名1>,...,[<继承方式n>][<基类名n>]{<派生类的成员>};
  1. C++中下列特殊的成员函数不被派生类所继承:

    • 构造函数;
    • 析构函数;
    • 私有函数:
    • 赋值运算符(=)重载函数。

私有的成员函数不能被继承的原因十分自然,因为它仅属于基类,在派生类中也不能直接访问它,否则破坏了基类的封装性(友元类与友元函数是以牺牲封装性为代价换取性能)。构造函数、析构函数和赋值运算符重载函数不被继承的主要原因是基类的对应函数不能处理在派生类引入的新的数据成员,不能完全正确地完成相应的功能(只能正确地处理基类的数据成员)。

  1. 对继承到派生类中基类成员的修改包括两个方面:

    • 基类成员的访问方式问题,这由派生类定义时的继承方式来控制;
    • 对基类成员的覆盖,也就是在派生类中定义了与基类中同名的数据成员或成员函数,由于作用域不同,于是发生同名覆盖(Override)。
  2. 无论采用什么样的继承方式,基类中的所有成员均被派生类所继承,派生类对象一定含有基类的数据成员和成员函数。继承方式仅仅影响到基类成员在派生类中的访问控制属性。对于在派生类中不可直接访问的私有成员,正确的方法是通过基类提供的公有成员函数进行问接的访问。

这里写图片描述

  1. 成员函数的同名覆盖与隐藏

在派生类中重新定义基类的同名成员函数后,基类中的同名成员函数将被同名覆盖(Override)或隐藏(Hide)。

同名覆盖是由于派生类与基类的同名成员函数的函数签名相同,派生类对象在调用同名成员函数时,系统调用派生类的同名成员函数,而基类的相应函数被遮盖。

隐藏是由于派生类与基类的同名成员函数的函数签名不同(即函数名相同而形参不同),派生类对象在调用同名成员函数时,系统只往派生类中查找,不再深入到基类。派生类的同名成员函数阻止了对基类中同名函数的访问。

函数重载只能出现在同一个类中,基类与派生类的同名函数之问不存在重载关系。

编泽器调用类的成员函数的方法是:根据函数名(不是函数签名)沿着类的继承链逐级向上查找相匹配的函数定义。

如果在类层次结构的某个类中找到了同名的成员函数,则停止查找,否则沿着继承链向上继续查找。派生中的同名函数阻止编译器到其基类继续查找,这就是出现同名覆盖和隐藏现象的原因。

(1)在派生类中没有找到成员函数,再到基类中查找。如果在基类中找到并且实参与形参正确匹配,则函数调用成功,否则出错。

(2)在派生类中找到了同名的成员函数,不再到基类中查找。此时又有两种情况:

  • 函数调用实参与形参正确匹配,则调用派生类中的同名成员函数(同名覆盖);
  • 实参与形参匹配不成功,编译器报告错误(隐藏)。

在派生类中可利用作用域标识符(::)直接调用基类的同名成员函数。

  1. 派生类与基类的赋值兼容

派生类对象向基类对象、指针或引用赋值满足以下兼容规则:

  • 派生类对象可以赋值给基类对象,它是把派生类对象中从对应基类中继承来的成员赋值给基类对象;
  • 派生类对象的地址可以赋给指向基类的指针变量,即基类指针可以指向派生类对象。但通过该指针只能访问派生类中从基类继承的成员,不能访问派生类中的新增成员;
  • 派生类对象可以代替基类对象向基类对象的引用进行赋值或初始化。但它只能引用包含在派生类对象中基类部分的成员。

在派生类中定义正确的转换构造函数或赋值运算符重载函数,则能确保将基类对象赋给派生类对象语句通过编译。此时,派生类对象中数据成员的内容与所定义的构造函数或赋值运算符重载函数相关。

用强制类型转换运算符转换基类对象为派生类对象并赋给派生类指针或引用,格式如下:

<派生类对象指针> = static_cast<派生类*>(&<基类对象>);
<派生类>&<派生类引用> = static_cast<派生类&>(<基类对象>);

static_cast运算符能实现类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。这种转换分为“上行”和“下行”两种。上行转换是指把派生类指针或引用转换成基类指针或引用,是安全的;下行转换是指把基类指针或引用转换成派生类指针或引用,是不安全的。

  1. 在创建派生类对象时,构造函数的调用顺序为:

    • 按照在派生类定义时的先后次序调用基类构造函数。
    • 按照在类定义中排列的先后顺序依次调用成员对象的构造函数。
    • 执行派生类构造函数中的操作。

派生类对象在撤消时是按照构造函数调用相反的次序调用类的析构函数。

  1. 虚基类

这里写图片描述

商品类的数据成员price分别通过手机类和播放器类派生给音乐手机类,同样的数据成员在音乐手机派生类对象中将出现两个,并且存储地址也不相同。这样不仅浪费存储空问,而且还会因为需要维护数据的一致性增加额外的开销。

C++语言通过引入虚基类(Virtual Base Class)来支持派生类对象在内存中仅有基类数据成员的一份拷贝,以消除钻石继承所产生的数据重复存储问题。

MusicPhone类的构造函数在定义时需要显式地说明调用虚基类Merchandise的构造函数Merchandise(n,p),否则myObj对象中nameprice成员将不赋初值。虽然Merchanaise类的直接派生类MobilePhoneMusicPlayer的构造函数均包含了对Merchandise构造函数的调用,但在MusicPhone类对象构造时不调用基类MobilePhoneMusicPlayer构造函数中说明的虚基类Merchandise的构造函数。若Merchandise不是虚基类,则Merchandise构造函数将被调用二次(通过MobilePhoneMusicPlayer类的构造函数)。此时MusicPhone类的构造函数定义时不再需要直接说明对Merchandise类构造函数的调用。

多态性

  1. 从程序实现的角度,多态可分为两类:

    • 编译时的多态;
    • 运行时的多态。

编译时的多态性是通过静态绑定实现的,而运行时的多态性则是在程序运行过程中通过动态绑定实现的。

这里的绑定(Binding,又称联编)是指函数调用与执行代码之间关联的过程。静态绑定(Static Binding)是在程序的编译与连接时就已确定函数调用和执行该调用的函数之间的关联。在生成的可执行文件中,函数调用所关联执行的代码是已确定的,因此静态绑定也称为早绑定(Early Binding)。函数重载(含运算符重载)就属于编译时的多态。编译器在判定应当调用多个重载函数中哪一个时,是根据源程序中函数调用所传递的实参类型查找到与之相匹配的重载函数并连接。动态绑定(Dynamic Binding)是在程序运行时根据具体情况才能确定函数调用所关联的执行代码,因而也称为晚绑定(Late Binding)。

  1. 虚函数的使用应注意:

    • 在派生类中重定义的虚函数要求函数签名和返回值必须与基类虚函数完全一致,而关键字virtual可以省略。在类的层次结构中,成员函数一旦在某个类中被声明为虚函数,那么在该类之后派生出来的新类中它都是虚函数;
    • 虚函数不能是友元函数或静态成员函数;
    • 构造函数不能是虚函数,而析构函数可以是虚函数;
    • 基类的虚函数在派生类中可以不重新定义。若在派生类中没有重新改写基类的虚函数,则调用的仍然是基类的虚函数;
    • 通过类的对象调用虚函数仅属于正常的成员函数调用,调用关系是在编译时确定的,属于静态绑定。动态绑定(动态多态性)仅发生在使用基类指针或基类引用调用虚函数的过程中。
  2. VC++处理动态绑定的基本方法是:编译器为拥有虚函数的类创建一个虚函数表,在对象中封装vfptr指针,用于指向类的虚函数表‘vftable’。虚函数表中存储了该类所拥有的虚函数的入口地址,即函数指针。如果派生类重新定义了基类的虚函数,那么虚函数表中保存的是指向该类虚函数的指针,否则保存的是其父类的对应虚函数指针。

这里写图片描述

  1. 在基类中定义其析构函数是虚函数,其所有派生类中的析构函数将都是虚函数,尽管它们的名称并不相同。如果对一个基类指针应用delete运算符显式地销毁其类层次结构中的一个对象,则系统会依次调用派生类和基类的虚析构函数撤消各自创建的对象。

  2. 纯虚函数与抽象类

virtual <返回值> 函数名([(<形参表>)]=0;

含一个或多个纯虚函数的类称为抽象类(Abstract Class)。

尽管无法实例化抽象类的对象,但是程序可以定义抽象基类的指针或引用,并通过它们访问以其为基类的继承层次结构中的所有派生类的对象。

抽象基类中声明的纯虚函数是所有派生类的公共接口,在类继承层次结构中,不同层次的派生类可以提供不同的具体实现,但使用这些函数的方法则是一致的(用抽象基类的指针或引用访问)。

模板与标准模板库

  1. 函数模板

用函数模板可以实现一个不受数据类型限制的具有良好通用性的函数设计,其方法是在函数模板的形参表中用无类型的参数代替形参的数据类型,用函数模板生成可执行函数的过程是在程序编译时,编译器用具体的数据类型置换模板类型的形参,并对其进行严格的类型检查。

template <模板形式参数表> 返回类型 函数名(形式参数表){函数体}

模板形式参数表的参数可以有多个,它们之间用逗号分隔。

模板类型形参是由关键字typenameclass加标识符组成的。模板非类型形参的声明与普通函数的形参的声明相同。

template<typename T>T max(T ary[],int size);

模板类型形参T的作用是指代任何系统内置的数据类型或用户定义类型。

函数模板的实例化(Instantiation)是指编译器根据函数调用时所传递的实参数据类型生成具体函数的过程。实例化的结果是生成能处理某种特定数据类型的函数实体,这种函数称为模板函数(Template Function)。

运算符重载在C++程序设计中非常重要。如果Student类不支持“>”和“<<”运算符重载,则系统就不能应用函数模板于用户自定义的类类型,只能处理系统内置的数据类型,函数模板的应用范围将受到限制。

函数模板与函数模板重载。多个函数模板的函数名相同,但每个函数模板具有不同的形式参数。

函数模板与非模板函数重载。非模板函数与函数模板同名,但具有不同的函数形式参数。

编译器如果能匹配到普通函数完成一个函数的调用,则不再寻找函数模板来实例化一个模板函数实现函数调用。

  1. 类模板

类模板是设计线性表、栈、队列等基本数据结构的工具。

template<模板形式参数农> class类名{类成员;};

类模板中的形式参数表可以为形参指定默认值,如此可避免每次实例化时都要显式地给出实参。例如:

template<typename T=intint Row=5int Col=8>

类模板的成员函数可以是函数模板,也可以是普通函数。

可用typedef声明模板类为程序引入的新的数据类型,提高程序的可读性。

typedef TwoDimensionalArray<double,5,10> TwoDimArrayDouble;
TwoDimArrayDouble myTDArray;

猜你喜欢

转载自blog.csdn.net/u011475134/article/details/80719702