1、int (*pt)[3] 表示的意义是 定义了一个名为pt的指针变量,它可以指向每行有三个整数的二维数组。
Swich误区:
2、函数中的默认参数
默认值就是在没有提供值的情况下使用的值。例如 long myFunction (int x=50); 如果没有提供参数,那么x使用默认值50.
还可以写成 long myFunction (int =50); 因为函数声明中不要求有参数名称。
函数的任何或者全部参数都可以设置默认值,有一个限制:如果任何参数没有默认值,那么它前面的参数就不能有默认值。如果函数原型是这样:long myFunction (int param1,int param2,int param3); 那么只有param3设置默认值时才能为param2设置默认值。只有为param3和param2设置默认值时,才能为param1设置默认值。
3、函数的重载
C++允许使用相同的名称创建超过一个的函数。这些函数的参数列表必须是不同的,即参数类型或参数的数量不同,或者两者都不同。
4、内联函数
如果使用关键字inline(内联)定义函数,编译器就不会创建真正的函数,它直接把内联函数的代码拷贝到发出调用的函数中,不会进行跳转,就像把函数的语句写进了发出调用的函数一样。
例:inline int Doubler(int);
注意:内联函数可能带来巨大的开销,如果函数被调用10次,那么内联代码就会被拷贝10次到发出调用的函数中。一般用于一些调用比较少的函数。
5、类、构造器constructor 、析构器destructor、拷贝构造器
类与结构体的区别是类中可以包含函数,而结构体中不可以。
构造器初始化类成员,析构器释放空间内存,二者即创建和删除对象。
析构器总是没有参数,并且构造器和析构器都不返回值,甚至也不返回空。
构造是按照两个阶段创建的:初始化阶段,后面跟着构造器体。例:CAT::CAT():itsage(10),itsweight(10) { }
如果提供了任何构造器,编译器就不再提供默认构造器了。
例:
#include <string.h>
#include <iostream.h>
class Person
{
private:
char name[10];
int age;
int salary;
char tel[8];
public:
Person(char *xname, intxage, int xsalary, char *xtel);
void disp();
};
Person::Person(char *xname, int xage, int xsalary, char *xtel)
{
strcpy(name, xname);
age = xage;
salary = xsalary;
strcpy(tel, xtel);
}
void Person::disp()
{
cout<<endl;
cout<<" 姓名:"<<name<<endl;
cout<<" 年龄:"<<age<<endl;
cout<<" 工资:"<<salary<<endl;
cout<<" 电话:"<<tel<<endl<<endl;
}
void main()
{
Person mrzhang("张立三", 25, 850, "45672314");
mrzhang.disp();
}
当按值把对象传递给函数或者作为函数的返回值时,就会生成对象的临时拷贝,如果对象是用户定义的对象,就会调用类的拷贝构造器。所有拷贝构造器都使用一个参数:对相同类的对象的引用。使用常量引用是个好主意,因为这个构造器不会改动传递过来的对象。例如:Cat (const Cat & thecat); 如果要改动传过来的对象数值,就不能用常量const了。
创建深度拷贝允许把现有的值拷贝到新的内存中。拷贝后的对象改动对原有对象没有影响,这相当于一个新的对象。
浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。如果改变目标对象中引用型字段的值他将反映在原是对象中,也就是说原始对象中对应的字段也会发生变化。
深拷贝与浅拷贝不同的是对于引用拷贝的处理,深拷贝将会在新对象中创建和原是对象中对应值类型的字段并且赋值。浅拷贝不会创建新引用类型,会返回相同的类型引用。深拷贝会重新创建新对象,返回新对象的引用字。
问:如果没有自己进行删除操作,对象会在什么时候被删除(调用析构器)?
答:当退出对象的范围时,对象被自动删除。如果一个对象是在main()中创建的,前且程序员没有删除它,那么当main()退
时会调用析构器。
6、const(常量) new和delete
如果把一个成员函数声明为const,就承诺这个方法不会改变类的任何成员的值。例:void Function () const;
尽量使用const,只要成员函数不应该改变对象,就把它们声明为const。这使编译器能够帮助我们查找错误;这比我们自己进行查找要更快,并且代价更低。
运算符new和delete是C++新增的运算符,提供了存储的动态分配和释放功能。它的作用相当于C语言的函数malloc()和free(),但是性能更为优越。使用new较之使用malloc()有以下的几个优点:
1)new自动计算要分配类型的大小,不使用sizeof运算符,比较省事,可以避免错误。
2)自动地返回正确的指针类型,不用进行强制指针类型转换。
3)可以用new对分配的对象进行初始化。
【例如】:
int* p;
p=new int[10]; //分配一个含有10个整数的整形数组
delete[] p; //删除这个数组
int* p;
p=new int (100);//动态分配一个整数并初始化
7、指针
在大多数情况下,指针用于3种任务:
管理堆中的数据;
访问类成员数据和函数;
按照引用把变量传递给函数。
8、内存泄漏
当使用完内存区域时,必须对指针调用delete。delete把内存返回给堆。指针本身(和指针指向的内存相对)是局部变量。当在其中声明指针的函数返回时,这个指针就超出范围并且丢失。但是,通过new操作符分配的内存不会自动被释放,这段内存就成为不可用的,在程序需要时继续分配内存空间,但可用的内存会越来越少,这种称为内存泄漏。称为内存泄漏是因为在程序终止之前这段内存不能被恢复,这好像内存泄漏出了计算机。当删除指针时,实际进行的操作是释放指针中存储的地址所标记的内存。
重视泄漏
对于每次在程序中调用new,都应该相应地调用delete。跟踪哪个指针指向内存的区域并且确保当处理完成后把这段存返回给堆,这很重要。
9、this指针
每个类成员都有一个隐含参数:this指针。this指针的作用是指向其方法被调用的单独对象。需要访问对象本身或者可能要返回指向当前对象的指针时,这些情况下this指针会非常有帮助。
10、引用
引用reference是一个别名,它作为目标的替代名称,并且对引用做出的任何操作都会实际作用于目标。int &rSoRef=som;
引用不能为空,要初始化所有引用,这很重要。
任何对象都可以被引用,包括用户定义的对象。
11、指针使用的注意项
当程序在堆中分配内存时会返回指针,保存指向这段内存的指针是必要的,因为在指针丢失之后,就不能删除内存并且内存泄漏。
当在函数之间传递内存块时,一个函数会拥有这个指针。典型情况下,使用引用传递块中的值,并且创建内存块的函数就是删除它的函数。
无论如何,一个函数在内存中创建空间,而另一函数去释放它,这样做是危险的。函数拥有指针的歧义性可能导致两个问题:忘记删除指针或者两次删除同一个指针。其中任何一个问题都可能在程序中造成严重的错误。让函数自己删除它们自己创建的内存空间,这样更安全一些。
如果编写这样的函数:它需要创建内存块,然后把这个内存块传递回发出调用的函数,那要考虑改动接口。让发出调用的函数分配内存,然后按引用把它传递给函数,这样把所有内存管理工作转移到程序之外,让准备删除内存的函数负责所有内存管理工作。
12、指针数组和指向数组的指针
堆栈内存非常有限,而堆内存要大得多。可以在堆中声明每个对象,然后在数组中只存储指向对象的指针。这就用到指针数组。例:CAT * Family[500]; 用500个指针比用500个对象占用的内存要小得太多了。
在堆中声明数组
可以把整个数组都放在堆中,而不仅仅是创建的对象。通过调用new并使用下标操作符达到这个目的。结果得到指向保存数组的堆中区域的指针。例如:CAT *Family=newCAT[500]; 声明Family为指向具有500个CAT的数组中的第一个元素的指针。换句话说,Family指向Family[0],即具有Family[0]的地址。这种方式的优势在于,可以使用指针运算访问Family的每一个成员。例如:CAT *Family=newCAT[500];CAT * pCat =Family; pCat->SetAge(5); pCat++; pCat->SetAge(20);
Cat Familyone[500]; // Familyone是具有500个CAT的数组。
Cat *Familytwo[500]; //Familytwo是具有500个指向CAT的指针数组。
CAT *Familythree=new CAT[500]; //Familythree是指向具有500个CAT的数组的指针。
在堆中删除数组
当使用new在堆中创建项目时,一定要使用delete[]后面跟着数组的名称来删除这个项目并且释放它的内存。类似地,当使用new<class>[size]创建数组时,使用delete[]<class> 删除这个数组并且释放它的内存。空的括号提示编译器删除整个数组。
例如上面第三个要用delete [] Familythree; 来删除。
13、cin 输入字符串的问题
如果在cin的输入过程中输入了空格,那么cin会认为这是字符串的结束,并且停止写入缓冲区。为了解决这个问题,我们必须在cin上调用一个特殊的方法:get()。cin.get()带有3个参数:要填充的缓冲区,要获取的字符的最大数量,终止输入的定界符。默认的定界符是newline(新行符)。例:char buffer[80]; cin.get(buffer,79);
14、继承和派生
在已有的类上添加了新功能的类被称为派生于原始的类。原始的类被称为新类的基类。
派生的语法:当声明类的时候,在类的名称后面写上冒号,派生的类型(public或其他)和从哪个类进行派生,通过这种方式指出这个类派生于哪个类。例:class Dog : public Mammal Dog对象从Mammal类继承了变量,以及Mammal哦的的所有方法,除了拷贝操作符、构造器和析构器之外。
有关几类派生在实际中的应用:
#include<iostream> using namespace std; // class A //父类 { private: int privatedateA; protected: int protecteddateA; public: int publicdateA; }; // class B :public A //基类A的派生类B(共有继承) { public: void funct() { int b; b=privatedateA; //error:基类中私有成员在派生类中是不可见的 b=protecteddateA; //ok:基类的保护成员在派生类中为保护成员 b=publicdateA; //ok:基类的公共成员在派生类中为公共成员 } }; // class C :private A //基类A的派生类C(私有继承) { public: void funct() { int c; c=privatedateA; //error:基类中私有成员在派生类中是不可见的 c=protecteddateA; //ok:基类的保护成员在派生类中为私有成员 c=publicdateA; //ok:基类的公共成员在派生类中为私有成员 } }; // class D :protected A //基类A的派生类D(保护继承) { public: void funct() { int d; d=privatedateA; //error:基类中私有成员在派生类中是不可见的 d=protecteddateA; //ok:基类的保护成员在派生类中为保护成员 d=publicdateA; //ok:基类的公共成员在派生类中为保护成员 } }; // int main() { int a; B objB; a=objB.privatedateA; //error:基类中私有成员在派生类中是不可见的,对对象不可见 a=objB.protecteddateA; //error:基类的保护成员在派生类中为保护成员,对对象不可见 a=objB.publicdateA; //ok:基类的公共成员在派生类中为公共成员,对对象可见 C objC; a=objC.privatedateA; //error:基类中私有成员在派生类中是不可见的,对对象不可见 a=objC.protecteddateA; //error:基类的保护成员在派生类中为私有成员,对对象不可见 a=objC.publicdateA; //error:基类的公共成员在派生类中为私有成员,对对象不可见 D objD; a=objD.privatedateA; //error:基类中私有成员在派生类中是不可见的,对对象不可见 a=objD.protecteddateA; //error:基类的保护成员在派生类中为保护成员,对对象不可见 a=objD.publicdateA; //error:基类的公共成员在派生类中为保护成员,对对象不可见 return 0; }
派生类的三种继承方式。公有继承(public)、私有继承(private)、保护继承(protected)是常用的三种继承方式。
①公有继承:公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的。
②私有继承:私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
③保护继承:保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
15、公有的,私有的和保护的
公有的public不用说,对基类及派生类来说都是可见的。
私有的private对派生类来说是不可用的。
保护的protected处于公有和私有之间,对基类来说是可用的,对于派生类来说也是可见的,控制比私有来说要宽松些,但比公有要严格。类的成员函数可以访问它们继承的任何类的所有保护数据成员和函数。
16、关于派生类的构造器和析构器的运作方式
Dog对象是Mammal的对象。这是“是”关系的本质。当创建派生类Dog对象时,首先调用它的基类构造器创建一个Mammal。然后调用Dog构造器完成Dog对象的构造。在完全构造好之前,派生类对象是不存在的,这就是说它的Mammal部分和Dog部分都必须被构造。因此必须调用两个构造器。
当销毁派生类对象时,首先调用Dog部分的析构器,然后调用Mammal部分的析构器。也是必须调用两个析构器。
17、覆盖函数 重载和覆盖 隐藏基类方法
当派生类创建的成员函数具有与基类中的成员函数相同的返回类型和签名,但是具有新的实现,就称为覆盖这个方法。当覆盖函数时,它的返回值类型和签名必须和基类中的函数一致。签名是函数原型中除了返回值类型之外的部分:即名称、参数列表和关键字const(如果使用了的话)。
当重载函数时,是创建一个以上的方法,它们具有相同的名称,但签名不同。
当覆盖方法时,是在派生类中创建方法,它的名称和基类中的方法名称相同,而且有相同的签名。
如果Mammal具有重载的 Move()方法,并且Dog覆盖了这个方法,那么Dog的方法就会隐藏使用这个名称的所有Mammal方法。 如果Mammal把Move()方法重载为3个方法——一个没有参数,一个使有一个参数,最后一个使用一个整数和一个方向参数,而Dog只覆盖了没有参数的Move()方法,那么就不容易使用Dog对象访问另外两个方法。
解决办法:可以通过方法的全限定名调用它。使用基类名称,后面跟着两个冒号和方法名称来完成这一工作。例如:Mammal::Move(int)可以有下面的实现fido.Mammal::Move(10); ( fido为派生类的对象。)
问:为什么希望在派生类中隐藏基类方法?答:有时候派生类的行为 和基类有很大不同,以致一些基类方法不合适,因为不是总能够修改基类(比如,如果没有它的源代码),在这种情况下就可以使用这种机制。
18、多态性和派生类
多态性就是对待派生类的成员就像它们都属于基类类型的那样。多意味着很多;态意味着形式。
使用虚方法实现多态性(virtual):
我们可以声明一个指向Mammal的指针,并且把在堆中创建的Dog对象的地址赋值给它。因为Dog是Mammal,所以下面的代码完全合法:Mammal * pMammal = new Dog; (Mammal是基类,Dog是派生类) 它的意思是pMammal是一个指向Mammal的指针,但是它被赋值为新的Dog对象的地址。
当派生对象(如Dog对象)被创建时,首先调用基类的构造器,然后调用派生类的构造器。一个创建后的Dog对象包括Mammal部分和Dog部分。
不能用父类指针调用子类的方法及解决之道:
如果我们具有指向基类的指针,它被赋值为派生类对象,这是因为打算以多态方式使用这个对象,在这种情况下不应该试图访问派生类特有的方法。例如:如果Dog对象有一个方法WagTail(),它不在Mammal中,那么我们就不能使用指向Mammal的指针访问这个方法(除非把它的造型指向Dog的指针)。因为WagTail()不是虚函数,并且因为它不在Mammal对象中,所以不通过Dog对象或者Dog指针就不能访问它。
解决方法是使用dynamic_cast操作符。Mammal *zoo; Cat *pRealCat = dynamic_cast<Cat *> zoo; Cat类中有Mammal类不具有的特殊方法,所以要把指向Mammal的指针zoo转换为指向Cat类并赋值给pRealCat,但zoo本身指向Mammal类并没有改变。如果转换是正确的,新的Cat指针就没有问题,如果转换不正确或其实根本不拥有Cat对象,新的指针就为空(Null)。
限制:
虚函数技术只能应用于指针和引用,按值传递对象不能调用虚成员函数。
虚析构器:
如果析构器是虚拟的(它应该是虚拟的),就会发生正确的结果——调用派生类的析构器。因为派生类的析构器会自动调用基类的析构器,整个对象就会被适当地销毁。
经验法则:如果类中的任何函数是虚拟的,那么析构器也应该是虚拟的。
注意点:
当期望从类进行派生时要使有虚方法。
如果方法是虚拟的,就要使用虚析构器。
不要使构造器是虚拟的。
19、抽象数据类型及纯虚函数
抽象数据类型ADT表示一个概念,而不是一个对象。在C++中,ADT永远是其他类的基类,并且生成ADT的实例是非法的。
纯虚函数是必须在派生类中被覆盖的函数,通过初始化虚函数为0使它为纯的,例如:virtual void Draw ()=0;
带有一个或者更多纯虚函数的类是ADT,并且实例化ADT类的对象是非法的。试图这样做会导致编译时错误。把纯虚函数放在类中就对类的客户发出两个信号:不要生成这个类的对象,要从它进行派生;一定要覆盖纯虚函数。
从ADT派生出的任何类都把纯虚函数继承为纯的,所以如果希望实例化对象,就必须覆盖所有纯虚函数。因此如Rectangle继承Shape,并且Shape具有3个纯虚函数,那么Rectangle必须覆盖这3个函数,否则它也将成为ADT。
实现纯虚函数
通常,抽象基类中的纯虚函数永远都不用实现。因为永远都不会创建这种类型的对象,所以没有理由提供实现,并且ADT纯粹是作为从它派生的对象的接口的定义。但是可以提供纯虚函数的实现,然后从ADT派生的对象可以调用这个函数,这可以向所有覆盖的函数提供公共的功能。例如如果Cat、Dog等继承Mammal类,在基类有纯虚函数 virtual void Speak()=0;
在派生类中有对它的覆盖,但如果派生类有共同的函数需要调用或者有共同的语言需要输出,可以把它们这在基类中的Speak中,这样在派生类的Speak方法实现中可以加入Mammal::Speak();即可。
向上渗透功能是什么?
答:它指的是把共享的功能向上移动到公共基类中的概念。如果多个类共享一个函数,找到一个公共的基类,在其中可以存储这个函数是我们期望的方式。
向上渗透总是好的操作吗?
答:如果向上渗透共享的功能,那就是好的操作;如果移动的都是接口,那就是不好的操作。也就是说,如果不是所有的派生类都能使用这个方法,那么把它向止移动到公共基类中就是错误的。如果这样做就必须在决定是否能够调用函数之前切换对象的运行时类型。
为什么动态造型是有害的?
答:虚函数的指针是让虚表(而不是程序员)决定对象的运行时类型。程序员强行改变对象的运行时类型可能导致错误。
为什么要费力建立抽象数据类型?为什么不使它成为非抽象的并且避免创建这个类型的任何对象?
答:在C++中,很多约定的目的是使编译器能够帮助查找缺陷,以便避免交会给用户的代码中存在运行时缺陷。使类成为抽象的使编译器能够指出创建这种抽象类型的对象是错误的,这也意味着可以和其他应用或者开发人员共享抽象数据类型。
注意:
要使用抽象数据类型为数个相关的类提供公共功能。
要覆盖所有纯虚函数。
要使必须被覆盖的所有函数为纯虚的。
不要试图实例化抽象数据类型的对象。
20、内联函数
使用函数有利于代码重用,可以提高开发效率,增强程序的可靠性,也便于分工合作,便于修改维护。但是,函数调用也会降低程序的执行效率,因为调用函数时,需要保护现场和返回地址,然后转到子函数的代码地址去执行,子函数执行完之后,又要取出先去保存的返回地址和现场状态,在继续执行。这些都需要时间和空间的开销。因此对于一些功能简单、规模较小而又使用频繁的函数,可以设计为内联函数。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。这样就节省了参数传递、控制转移等开销。
内联函数在定义时使用关键字inline,语法形式为:
inline 类型 函数名(参数){};
使用内联函数时应注意:
①内联函数体内一般不能有循环语句和swith语句。
②内联函数的定义必须出现在第一次被调用之前。
③通常的内联函数应该是比较简单的函数,结构简单,语句少。如果将一个复杂的函数定义为内联函数,反而会造成代码膨胀,增大开销。尤其是在嵌入式系统的开发过程中,本来可用的空间资源就少,若果内联函数太大,反而会得不偿失。
21、链表
链表有3种基本形式,单向链表、双向链表、树型链表。链表不是链接在一起的数据,而是链接在一起的保存数据的结点。
头指针包含链表的头(或者说顶端、开始)。了解链表众哪里开始以便能够访问它,这是非常重要的!
next指针指向链表的下一个成员,我们可以把它看作链表其他部分的头,这实际是链表中的链接。
链表的优点在于,可以把需要的任何数据放到这个类的数据对象中。可以包含整数,也可以包含多种内置的类型,甚至是其他对象(包括其他的链表)。
面向对象程序设计的基本前提是,每个对象很好地完成一个工作并且把不属于它的核心任务的所有工作委托给其他对象。设计良好的程序几乎都是这样的,每个类只负责它自己的工作,但是它们协同完成更大的工作 。
22、标准输入输出流
标准输入流 () 对应 键盘
标准输出流 显示器
标准错误流 显示器
cout和cin是C++标准库中定义的两个输入输出流对象。在使用过程中,要在程序的开头包含相应的头文件:
#include <iostream.h> 或 #include <iostream>
在cout中还可以使用流控制符控制数据的输出格式,但使用这些流控制符时,要在程序中包含头文件iomanip.h。
常用的流控制符及其功能如下表所示:
设置域宽
所谓域宽就是被输出数据所占的输出宽度(单位是字符数)。设置域宽可以使用流控制符setw(n)和cout的方法cout.width(n)
其中n为正整数,表示域宽。但是,cout.width(n)和setw(n)二者都只对下一个被输出的数据有作用,若一个输出语句内有多个被输出的数据,而要保持一定格式域宽时,需要在每一输出数据前加上cout.width(n)或setw(n)。
此外,当参数n的值比实际被输出数据的宽度大时,则在给定的域宽内,数据靠右输出,不足部分自动填充空格符;若被输出数据的实际宽度比n值大时,则数据所占的实际位数输出数据,设置域宽的参数n不再起作用。
例 :
cout<<setw(TITLESIZE+1)<<setiosflags(ios::left)
<<cur->title<<cur->rating<<endl;
cin>>LISTSIZE;
cin.get();//用于存储换行符
如果想要使程序停留在最后一步而不是关闭,则可以用下面的语句:
#include <windows.h>
在程序最后加上system("pause");
23、运算符重载
在类中可以使用两种方法对运算符进行重载:(1)重载为类的成员函数(2)重载为类的友元函数
#include <iostream.h>
class Complex
{
private:
double r; //实部
doublei; //虚部
public:
Complex operator+(constComplex &c);
Complex operator+(double d);
……
};
Complex Complex::operator+(const Complex &c)
{
Complex t;
t.r = r + c.r;
t.i = i + c.i;
return t;
}
Complex Complex::operator+(double d)
{
Complex t;
t.r = r + d;
t.i = i;
return t;
}
void main()
{
Complex a(3.0, 4.0), b(10.5,20.5), c, d, e;
c = a.Add(b);
d = a + b;//运算符左侧的对象是调用对象,右侧的对象是函数参数。
e = a + 10.5;
d =a.operator +(b);
e = a.operator +(10.5);
c.Print();
d.Print();
e.Print();
}
运算符重载的规则:
1、不能定义新的运算符。
2、大部分C++运算符可以被重载,除了下面的运算符:.、.*、::、?:、sizeof
3、运算符的重载既不会改变原运算符的优先级和结合性,也不会改变使用运算符的语法和参数个数。
4、重载运算符的函数不能包含有默认值的参数。
5、重载的运算符必须和自定义类型的对象一起使用,要求其参数至少有一个是自定义类型的对象。
6、用于自定义类型对象的运算符一般必须重载,但是“=”和“&”不必用户重载。
系统已为“=”提供默认的重载函数实现对象间的浅拷贝
“&”获得对象的内存地址
7、应当使重载的功能与标准的功能类似,以避免混乱。
8、当重载为类的成员函数时,运算符重载函数的形参个数要比运算符操作数个数少一个;若重载为友元函数,则参数 个数与操作数个数相同。
9、下面的运算符只能通过成员函数重载。“=”、“()”、“[ ]”、“->”
24、标准库之string类
处理字符串的两种方法
(1)使用string.h中声明的字符串处理函数。
>>包含头文件string.h或cstring
(2)使用C++标准类库中的string类。
>>包含头文件 string
#include <string>
>>封装了字符串数据,提供了字符串操作的成员函数。
构造函数 string() string(const string &rs) string(const char *s) string(unsigned n, char c) ……
运算符 +、=、+=、==、!=、>=、<=、<、>、[ ]、<<、>>
字符串对象的定义、初始化和赋值 string str1 = “C++”; string str2; str2 = “Programming”; string str3(“Language”); string str4 = str1; 字符串数组 string strarr[5] = {“abc”, “def”};
| 成员函数 string &append(const char *s) string &append(const char *s, unsigned n) unsigned find(const string &s, unsigned pos=0) const int compare(const string &s) const unsigned length() const …… 输入和输出 cin>>str4; cout<<str4; 运算 下标运算 str1[0] = ‘c’; 加法运算(连接) string str6; str6 = str1 + “ ”; str6 += str2; 关系运算 if (str6 < str1) cout<<str1; else cout<<str6;
|
25、派生类
(1)在基类的基础上定义其派生类的形式为:
class 派生类名 : 继承方式 基类名
{ 派生类中的新成员 }
其中:
派生类名由用户自己命名。
在冒号“:”后的部分告诉系统,这个派生类是从哪个基类派生的,以及在派生时的继承方式。
继承方式即访问方式,有以下三种:
为public方式时,这种继承称为公有继承;
为protected方式时,这种继承称为保护继承;
为private方式时,称为私有继承,是默认方式。
基类名必须是程序中一个已有的类。
大括号内的部分是派生类中新增加的成员。
(2)派生类不仅拥有属于自己的数据成员与成员函数,还拥有从基类继承来的数据成员与成员函数。
(3)通过继承方式对基类成员的访问属性进行控制
基类的私有成员和不可访问成员在派生类中不可访问。
公有继承
基类中的public和protected成员的访问属性在派生类中不变。
保护继承
基类中的public和protected成员在派生类中具有protected访问属性。
私有继承
基类中的public和protected成员在派生类中具有private访问属性。
(4)派生类的构造函数
派生类的构造函数要负责调用基类的构造函数。
派生类的构造函数除初始化新增的数据成员外,还必须对基类中的数据成员进行初始化。
对基类的数据成员的初始化由基类的构造函数完成,以避免类与类之间相互干扰。
派生类的构造函数的定义格式如下:
派生类构造函数名(总参数表) : 基类构造函数名(参数表)
{ 派生类中新增的数据成员初始化语句 }
总参数表中包含基类构造函数所需的参数和对新增的数据成员初始化所需的参数。
(5)派生类的析构函数
在执行派生类的析构函数时,系统会自动调用基类的析构函数。
(6)多重继承
派生类可以有多个直接基类
多重继承类的定义格式如下:
class 派生类名 : 继承方式 基类名1, 继承方式 基类名2, ……
{
……
};
多重继承下,派生类必须负责所有基类构造函数的调用。
派生类名(总参数表) : 基类1名(参数表), 基类2名(参数表), ……
{
派生类中新增的数据成员初始化语句
};
(7)赋值兼容规则
在公有继承的情况下,一个派生类的对象可以作为基类对象来使用。
派生类的对象可以赋值给基类对象。
派生类的对象可以赋值给基类的引用。
派生类对象的地址可以赋值给指向基类对象的指针变量。
例:
void main()
{
Shapes(1, 1), &rs=s, *ps=&s;//基类
Circle c(3, 2, 2), &rc=c, *pc=&c;//派生类
……
Shape sc=c, &rsc=c,*psc=&c;
……
}
26、多态性
(1)同样的消息(调用同名成员函数)被类和其子类的不同对象接收时导致完全不同的行为。
(2)多态性是面向对象程序设计中的三个重要机制之一。
多态性
封装
继承
(3)C++语言通过虚函数实现运行时的多态性。
虚函数
基类指针或基类引用
(4)声明虚函数的说明
虚函数声明只能出现在类声明中的成员函数原型声明中。
只有类的普通成员函数才能声明为虚函数,全局函数及静态成员函数不能声明为虚函数。
虚函数可以在一个或多个派生类中被重复定义,因此它属于函数重载的情况。但是,这种函数重载与一般的函数重载是不同的。
虚函数在派生类中重新定义时必须与基类中的原型完全相同(函数名、参数个数、参数类型、参数顺序、返回值类型)。
当一个成员函数被声明为虚函数后,其派生类中的同名函数(与虚函数的原型完全相同)都自动成为虚函数。派生类中的虚函数加 virtual 是可选的。
(5)通过指向派生类对象的基类指针来调用虚函数的重载函数,实现运行时的多态性。
纯虚函数
在定义一个表达抽象概念的基类时,有时可能会无法给出某些成员函数的具体实现。这时,就可以把这些函数声明为纯虚函数。
纯虚函数的声明格式:
virtual 函数原型 = 0;
纯虚函数没有函数体。
抽象类
包含纯虚函数的类。
主要作用是通过它为一个类族建立一个公共的接口,使它们能更有效的发挥多态性。
使用纯虚函数和抽象类的注意事项
抽象类只能用作基类来派生新类,不能声明抽象类的对象,但可以声明抽象类的指针变量和引用。
抽象类中可以定义纯虚函数和普通函数。
如果抽象类的派生类没有定义基类中的纯虚函数,则必须再将该函数声明为纯虚函数,那么此派生类也是一个抽象类。
27、模板和STL
函数模板的定义
用来实现从一个模板生成多个函数。
定义函数模板的格式:
template<类型形式参数表>
函数返回值类型 函数模板名(形式参数表)
{ 函数体 }
类型参数:
class 参数名 或者 typename 参数名
class表示该形式参数接收一个类型名称,实现类型参数化。
举例
template<class T>
T xmax(T a, T b)
{ return (a>=b)?a:b; }
非类型参数:
类型说明符 参数名
这种形式参数只能接收一个常量。
举例
template<class T, int N>
void InitList(T *a, T b)
{
for (int i=0; i<N; i++)a[i] = b;
}
类模板的定义
定义类模板的格式:
template<类型形式参数表>
class 类模板名
{ …/*类定义体*/… };
举例
template<class T, int N>
class AClass
{
…
};
类模板的成员函数的定义
可以放在类模板的定义体中,其格式与普通类中成员函数的定义格式相同。
也可以在全局区域中定义,其格式如下:
template<类型形式参数表>
函数返回值类型 类模板名<类型名表>::成员函数名(形参表)
{ 函数体 }
举例
template<class T, int N>
void AClass<T, N>::Show()
{
for (int i=0; i<N; i++)
cout<<"values["<<i+1<<"]="<<values[i]<<endl;
}
类模板的说明
类模板不是具体的类,只是对类的描述。
要根据类模板定义对象必须分两步走:
类模板实例化生成类,即模板类
模板类实例化得到对象
类模板名<实参表> 对象名;
举例
AClass<int, 5> a1;
程序设计中遇到若干个程序结构有同一种模式时就可以使用模板。
模板是一种高度抽象的结构形式。
函数模板是一类函数的抽象,代表了一类函数。
这些函数具有相同的功能,但返回值和/或形参的类型可能不同。
模板函数是一个具体的函数,可以被调用。
类模板是对类的抽象,代表一类类。
这些类具有相同的功能,但数据成员的类型、成员函数的返回值和/或形参的类型可能不同。
模板类是类模板的实例,是一个具体的类,可以实例化。
标准模板库(Standard Template Library, STL)
是C++标准库的重要组成部分。
主要包括三部分内容:
容器(container)
迭代器(iterator)
算法(algorithm)
体现了通用(范型)程序设计(Generic Programming)的思想。
- 容器
是指一种存储了有限个同类型元素的数据结构。
7个基本容器:
-
名称
描述
所在头文件
vector
向量,连续存储的元素。
<vector>
deque
双端队列,连续存储的指向不同元素的指针所组成的元素。
<deque>
list
列表,由节点组成的双向链表,每个节点包含一个元素。
<list>
set
集合,由节点组成的红黑树,每个节点包含一个元素,节点之间以某种作用于元素对的谓词排列,两个不同元素不能有相同的次序。
<set>
multiset
和set基本相同,但允许存在两个次序相同的元素存在。
<multiset>
map
由{键,值}对组成的集合,以某种作用于键对上的谓词排列。
<map>
multimap
允许键对有相同的次序,即一个键可以对应多个值。
<multimap>
容器
顺序容器
将元素按照严格的线性形式组织起来
对元素的访问可以采用顺序方式或随机方式
包括vector、list、deque
关联容器
将元素按照非线性形式组织起来
根据一组索引来快速的访问元素
包括set、multiset、map、multimap
vector
内部用动态数组来存放元素,长度可变。
是一个类模板,元素类型任意。
常用的成员函数
vector()
创建一个长度为0的空向量
vector(size_type n, const T &val=T())
创建一个长度为n的向量,每个元素的初始值为val。
size_type size() const
返回当前容器中元素的个数
size_type capacity() const
返回容器当前可存放元素的个数
bool empty()
如果容器为空则返回true,否则返回false
void push_back(const T &x)
在容器末尾添加元素x。
void clear()
删除容器中的全部元素
void reserve(size_type n)
重新分配内容,使其可存放n个元素
迭代器
是一种通用指针,用来对容器进行定位和操作。
每个容器都有自己的迭代器。
vector的与迭代器相关的常用成员函数
iterator insert(iterator it, const T &x)
在迭代器it所指的位置前插入元素x
iterator erase(iterator it)
删除迭代器it指向的元素
iterator begin()
返回指向第一个元素的迭代器
iterator end()
返回指向最后一个元素后一个位置的迭代器
STL的算法库
提供了大约70个通用算法。
这些算法都被设计成函数模板。
#include <algorithm>