命名空间namespace
省略头文件的“.h”,改用命名空间。例如下面语句:
#include <iostream.h>
应写成
#include <iostream>
using namespace std;
注意早期版本的C++并不支持该方法。
初始化变量之()方法
int x(0); 等价于 int x=0;
int x(0),y(2); 等价于 int x=0,y=2;
事实上,这是构造函数的一个应用。
变量传引用方式
同“*”运算符类似,“&”可以表示变量的“传引用方式”,即实参与形参共享内存单元。
void swap(int& m, int& n)
{
int temp=m; m=n; n=temp;
}
以下述方式调用swap()函数:
int a=3,b=8;
swap(a,b);
可以将“&”理解为“引用运算符”。
函数重载
int addTo(int a, int &b) { return b+=a; } //传引用
void addTo(const int a[],int b[], int size) {
for (int i=0;i<size;i++) b[i]+=a[i];
}
允许定义两个同名函数,可以保证接口的通用性。
内联函数
inline int add2(int n) { return n+2; }
对于反复调用的小函数,可以向编译器申请成爲内联(inline)函数。这样函数会变成程序体的一部分,而不用每次调用动用内存指针。但如果含循环语句,编译器通常不予理会。
函数参数之缺省值
函数原型可写为如下形式:
int f(int a, char b, char c='Z', char *s="READY");
类(class)的基本格式
class <name_of_class> {
public:
<public variables and functions>
private:
<private variables and functions>
}
<Body of varibles and functions>
域运算符(::)
void Point::Setxy(int a,int b)
{ x=a; y=b; }
域运算符“::”标示Setxy是类Point的一个成员函数。
this指针
this指向正在操作的对象,注意是对象而不是类。
void Point::Setxy(int a, int b, (Point*)this)
{
this->x=a;
this->y=b;
}
一般来説,this可以省略,在函数原型中的声明亦非必要。因此上面的函数可以写成:
void Point::Setxy(int a, int b)
{
x=a;
y=b;
}
构造函数与初始化列表
编译器为对象创建了同名的默认构造函数(无参数)。形式为:
class_name::class_name(){ }
可以自己编写构造函数,此时编译器不再提供构造函数,因此需要至少有一个无参数的构造函数,或每次创建新对象时按照自己编写的构造函数来提供数据成员的初始值。
一个典型的构造函数为:
void Point(double x=0.0, double y=0.0):_x(x),_y(y) { }
冒号后面的“_x(x),_y(y)”称爲初始化列表。这个构造函数提供了_x、_y的初始值,均为0.0。
这样以后就可以按下面的方法创建一个Point类的对象:
Point A(25,42);
不可以显式调用构造函数。
拷贝构造函数
class_name obj1(25,52); //利用构造函数(假设已经写好)初始化
class_name obj2(obj1); //使用obj1的数据成员来初始化obj2
则obj2的变量值与obj1相同(同为25,52)。
拷贝构造函数的原型为:
class_name::class_name(class_name&)
采用传变量方式。爲了保证实参不被修改,可以加上const修饰:
class_name::class_name(const class_name&)
这样实参一旦被修改则导致错误。
可以自己写拷贝构造函数。如果不写,则使用编译器提供的默认拷贝构造函数,作用只是简单地复制类中的每一个数据成员。
一个自己写的、典型的拷贝构造函数为:
Test::Test(Test& t)
{ x=t.x; y=t.y; //more code... }
析构函数
析构函数在对象的生存期结束时自动调用。其原型为:
classname::~classname()
new和delete运算符与构造、析构函数
Test *ptr=new Test[2];
delete []ptr;
这样可以很好地控制对象的生存期。在使用new时会调用构造函数,delete前执行析构函数。
类的静态成员
类中的静态成员为所有同类对象所共享(不论对象多少,永远只有一个副本)。在成员前加上static标示即可。静态成员可以是函数,它没有this指针,只访问静态成员,不访问非静态成员。静态成员属于类,而不是具体的某个对象。
类的常成员与volatile对象
类的常成员包括常对象、常成员函数和常数据成员。标示为“const”。常对象定义时必须初始化且不能再修改。常成员函数只能读取本类中的数据成员而不能修改它们。比如
double GetX() const { return _x; }
可以起到保护作用。注意const所加的位置。常数据成员即常量,只能通过构造函数的初始化列表对其进行初始化,且不能再修改。比如:
class Point {
public:
Point(double x=0.0, double y=0.0):_x(x),_y(y),PI(3.1415927) { }
private:
double _x;
double _y;
const double PI;
}
volatile语法与const一致,但在编译器的认识外,认定该数据可以被改变。比如时间。因此每次需要该数据都必须重新读取。
类的友元
利用friend关键字,可以将类的私有和保护数据成员向特定的类或函数开放。在public段中声明一个友元函数的典型例子是:
friend void Print(Point &pt);
那麽就可以这样写:
void Print(Point &pt)
{
cout<<pt._x; //假设_x是Point的Private数据成员
}
同样,声明一个友元类可以这样写:
friend another_class_name;
友元的关系是单向的,且不能传递(A->B and B->C不能得到A->C)。
控制符(manipulators)之头文件
#include <iomanip>
using namespace std;
用于输入输出流格式控制符等。
派生类的定义
需要给出基类名称与继承方式。一个典型的例子是,Manger是Employee的一种:
class Manager : public Employee {
int level; //新增的数据成员,默认为private
public://….
private://….
}
无论采用public继承还是private继承,基类中的private成员均不对派生类开放。当public继承时基类的public和protected在派生类中仍然是public和protected成员。而private继承时基类的public和protected在派生类中变成private成员。
protected和private成员的区别也正因此体现出来:protected成员可以被派生类访问,而private成员无论如何只能被自己的类访问。也因此,protected成员往往只用在基类中。
派生类的构造函数与析构函数
一个典型的例子是,Rectangle由一点(类Point)和长宽构成,那麽在Rectangle的public段中这样写其构造函数:
Rectangle(int a, int b, int h, int w):Point(a,b) { H=h; W=w; }
也可以这样:
Rectangle(int a, int b, int h, int w):Point(a,b),H,W { }
析构函数:
~Rectangle(){ }
至于顺序方面,遵循:构造时,先基后对象后己;析构时,先己后对象后基。
多继承中的二义性与虚基类
在一个类继承不止一个类时,可能出现派生类对基类访问不唯一的情况。可以使用域运算符,或在派生类中重新定义同名成员对基类进行覆盖;或使用虚基类。应该注意,访问控制权限不同或类型不同不能解决二义性问题。
如果一个派生类(D)有多个直接基类(B、C),而这些直接基类(B、C)又有一个共同的基类(A),则在最终的派生类(D)中会保留该间接共同基类(A)数据成员的多份同名成员,而产生二义性问题。解决方法即使用虚(vitrual)基类,使得同名成员的数量减少。代码如下:
class A { };
class B : virtual public A { };
class C : virtual public A { };
class D : public B, public C { };
D称爲最派生类。
虚基类是在声明派生类时声明的,这是因爲一个基类对于不同的派生类可以作爲虚基类,也可以不作爲虚基类。
在初始化D类时,应该在D的public段中这样写:
D(int a, int b, int c, int d, float e):B(a,b),C(c,d),A(e) { }
也就是说,由A间接继承给D的成员需要直接调用A的构造函数(语句中的“A(e)”部分)来初始化。虽然A(e)在B(a,b),C(c,d)之后,但在编译中先执行,因爲虚基类构造函数编译优先权总是高于非基类。
虚函数定义与多态性
程序执行时的多态性通过虚函数体现,实现运行时多态性的机制称爲动态绑定;与编译时的多态性(通过函数重载、运算符重载体现,称爲静态绑定)相对应。
在成员函数的声明前加上virtual修饰,则声明之为虚函数。在派生类中可以重新定义从基类继承下来的虚函数。下面是一个例子,Rectangle是Shape的派生类,在类Rectangle中对Shape中的虚函数Draw()进行了重新定义:
class Shape {
public:
virtual void Draw() { }
};
class Rectangle : public Shape {
public:
virtual void Draw() { }
};
类Rectangle中的Draw()函数前面的virtual可不加,那麽它就不再可以被它的继承类重新定义。
虚函数的调用
要想调用一个在派生类中重定义的函数,必须使用多态调用,它必须使用指针来实现。下面是一个典型的例子:
void fun1(Shape s) { s.Draw(); } //非多态
void fun2(Shape &s) { s.Draw(); } //多态
void fun3(Shape *s) { s->Draw(); } //多态
int main()
{
Rectangle rr;
fun1(rr); //调用类Shape的Draw()
fun2(rr); //调用类Rectangle的Draw()
fun3(&rr); //调用类Rectangle的Draw()
}
只有使用指针和引用调用时,才能进行多态调用。
纯虚函数与抽象类
基类中必须靠派生类提供重定义版本的虚函数称爲纯虚函数。声明一个纯虚函数需要加上“=0”,具体格式如下:
class Shape {
public:
virtual void Draw() = 0;
}
没有派生类版本时,这个函数不能被调用(在派生类中仍然是纯虚函数)。
含有纯虚函数的类就是抽象类,它不能用于直接建立对象,通常处于一个类层次结构的顶端,作爲基类,为一组具体的派生类提供公共接口。只有抽象类中的所有纯虚函数都被具体定义了,才变成可以生成对象的非抽象类。
虽然抽象类不能用来定义对象,但可以定义抽象类的指针或引用。当派生类定义了所有纯虚函数后,就可以用这种指针或引用来指向派生类对象,然后通过指针或饮用调用虚函数,从而实现多态性的操作。可以参考这一段代码:
void fun1(Shape s) { s.Draw(); } //非多态,本句非法
void fun2(Shape &s) { s.Draw(); } //多态
void fun3(Shape *s) { s->Draw(); } //多态
int main()
{
Rectangle rr;
fun1(rr); //调用类Shape的Draw(),本句非法
fun2(rr); //调用类Rectangle的Draw()
fun3(&rr); //调用类Rectangle的Draw()
}
假设类Shape中的Draw()是纯虚函数,这就是一个典型的例子。
运算符重载
运算符重载的本质是函数重载。形式有两种,重载为类的友元函数或重载为类的成员函数。代码(都放置在public段中)分别为:
friend func_type operator operator_name(list_of_variables)
{ /…. }
func_type operator operator_name(list_of_variables)
{ /…. }
一般的,对于重载为类的友元函数,N(规定只允许N=1,2)元操作符的形参列表需要包含N个参数。而对重载为类的成员函数,由于包含了this指针,只需要N-1个参数。对于这N个参数中的第一个(当N-1个时应该理解为是this指针所指)必须要是新的(自定义的)数据类型,以便编译器同传统运算符加以区分。而返回值也为同一种新的(自定义的)数据类型。下面的函数原型都是合法的:
Value operator + (Value, int); //重载为类的友元函数,二元运算符
Myop& Myop::operator += (int) //重载为类的成员函数,二元运算符,函数返回为引用主要是//爲了将函数用在赋值运算符的左边(即可以改变它的值),也就是说可以将operator计算的//结果用在赋值运算符的左边
Myop Myop::operator += (int) //重载为类的成员函数,二元运算符
函数模板
函数模板允许一个函数中包含类型参数(当然也可以有传统的普通参数)。它具有以下形式:
template <typename T>
T max(T m1, T m2)
{ return(m1>m2)?m1:m2; }
(第一行可以等价地写成:template <class T>)
如果要继续写一个新的函数模板,则需要这样写:
template <typename T> //必须重写
T min( T m1, T m2)
{ return(m1<m2)?m1:m2; }
调用函数模板时,可以采用显式和隐式两种调用方式。分别举例如下:
cout<<max<double>(8.5,6);
cout<<max(8.5,6.0);
采用隐式方式时,数据类型一定要让编译器易于识别,如果这样调用:cout<<max(8.5,6);,那麽有一个double一个int类型,将无法识别,也不能通过编译,可以采用cout<<max(8.5,double(6));进行隐式调用。
如果要使用普通参数,那麽函数模板的第一行要这样写:
template <typename T, int Rows, int Cols>
在上面的基础上,我们可以进一步扩展max函数的功能。比如要求添加一个模板参数为指针的函数模板特例,那麽就写一个“专门化函数”:
template<> char *max<char*>(char *a, char *b) { return(strcmp(a,b)>=0?a:b); }
其中template<>是“专门化函数”前缀。而<char*>説明这个专门化要在模板参数是char*类型时使用。事实上这是显然的,可以简写为:
template<> char *max< >(char *a, char *b) { return(strcmp(a,b)>=0?a:b); }
或者进一步简写为:
template<> char *max(char *a, char *b) { return(strcmp(a,b)>=0?a:b); }
类模板
如果将类看作包含某些数据类型的框架,可以将数据类型分离出来,允许单个类处理通用的数据类型T,这就构成了类模板。一个典型的例子是:
template <class T> //可以用typename代替class
class TAnyTemp
{
T x,y;
public:
TAnyTemp(T X, T Y):x(X),y(Y) { } //构造函数
T getx() { return x };
T gety() { return y };
}
用上面的模板来定义对象的代码为:
TAnyTemp<int> iOjbect(321,556);
TAnyTemp<double> dObject(3.1416,5.1552);
TAnyTemp<int>与TAnyTemp<double>是完全不同的两个类,除了是同一模板所生成的外,没有任何关系。
同函数模板一样,类模板也可以有传统的普通参数。下面的例子同时説明如何使用普通参数以及如何在类体之外定义一个成员函数:
template <typename T, int size> //定义成员函数需要重新声明模板
void Box<T,size>::pickOut(const T& obj) //把Box<T,size>看成整体
{ /…. }
类模板也可以专门化。方法同函数模板。
C++流
要使用C++流,必须包含相应的头文件,包括:
iostream:使用cin、cout等标准I/O操作
fstream:使用文件流
strstream:字符串流(针对内存中的字符串空间的I/O操作)
iomanip:使用setw、fixed等操作
标准I/O个时的不常用控制包括:设置最小宽度setw(int n)、定小数点输出fixed、指数输出scientific、浮点输出resetiosflags、设定精度setprecision(int n)、左对齐left、右对齐right、内部对齐internal、小数点显示showpoint、小数点不显示noshowpoint、填充字符setfill(char c)。使用方法和常用的控制,比如endl是一样的。
对于文件流,类ifstream、类ofstream、类fstream分别用于输入、输出、二者皆可的情况。一个典型的例子是:
#include <iostream>
#include <fstream>
using namespace std;
void main(){
char ch[15],*p=”abcdefg”;
ofstream myFile;
myFile.open(“myText.txt”);
myFile<<p;
myFile<<”goodbye”;
myFile<<close();
ifstream getText(“myText.txt”);
for (int i=0;i<strlen(p)+8,i++)
getText>>ch[i];
ch[i]=’\0’; //设置结束标识符
getText.close();
cout<<ch;
}
常用的函数有:open、写字符put、写字符串write、bad()、清除错误clear()、eof()、错误fail()、正常good()、is_open()、提取字符get(与>>相比的好处在于,支持空白字符)、读到行结束getline(source,length)、读到文件结束read(source,length)、seekg(文件指针从0开始)、tellg、close、跳过一段字符irgore(length)、返回当前字符peek、退回输入流使成爲下一个输入putback。