一、函数的重载:
在C++程序中,为了方便,可以给多个功能相同的函数起相同的名字,由系统来决定应该调用哪个函数,这样就减轻了编程的负担,不必为同一个功能函数起很多个名字。系统是根据参数个数的不同或者参数类型的不同来加以区分。
1、 函数参数类型重载
重载函数abs(),求int、float、和double类型数据的绝对值。
#include<iostream>
using namespace std;
int abs(int x)
{
if(x<0)return -x;
else return x;
}
float abs(float x)
{
if(x<0)return -x;
else return x;
}
double abs(double x)
{
if(x>=0)return x;
else return -x;
}
int main()
{
int a=-21;
float b=33.23;
double c=-3.45;
cout<<abs(a)<<endl<<abs(b)<<endl<<abs(c)<<endl;
system("pause");
return 0;
}
2、 函数参数个数重载(VC环境下)
求若干个参数当中的最大值,根据参数个数的不同调用不同的max()函数:
#include<iostream>
using namespace std;
float max(float x,float y)
{
if(x<y)
return y;
else
return x;
}
float max(float x,float y,float z)
{
float tmp;
tmp=max(x,y);
tmp=max(tmp,z);
return tmp;
}
int main()
{
cout<<max(2,3)<<endl;
cout<<max(1,20,5)<<endl;
//system("pause");
return 0;
}
二、封装:
封装是一种信息隐藏技术。在面向对象程序设计中,通过封装,可以将一部分属性和操作隐藏起来,不让使用者访问,另一部分作为类的外部接口,使用者可以访问,这样可以对属性和操作的访问权限进行合理的控制,减少程序之间的相互影响,降低出错的可能性。
五、类:
在C++中类也是一种数据类型,是程序员可以用声明语名说明的数据类型。
1、类定义的一般形式:
class<类名>{
private:
<私有的数据和函数>
protected:
<保护的数据和函数>
public:
<公有的数据和函数>
};
例:
class Date{
private:
int Year;
int Month;
int Day;
public:
void Set(int y,int m,int d); //成员函数;
void Display(); //成员函数;
};
2、类的数据成员:
类的数据成员是类的一个重要组成部分。类的数据成员与结构体struct中的数据是一致的。
3、类的成员函数:
成员函数实现对类中数据成员的操作,它描述了类的行为。由于对象的封装性,类的成员函数是对类私有数据成员进行操作的唯一途径。
4、成员函数的实现
成员函数的声明只是说明类中需要这样一个成员函数,具体这个成员执行什么操作,现在还不知道,需要进一步定义这个成员函数,来实现它的操作功能。
其定义结构:
<类型><类名>::<成员函数名>(<参数表>)
{
<成员函数体>
}
例:(假如类如上面例中定义了一个Data类,对其中成员函数Set和Display定义其操作功能)
void Date::Set(int y,int m,int d)
{
Year=y;
Month=m;
Day=d;
}
void Date::Display()
{
cout<<”日期为:”<<endl;
cout<<”\t”<<Year<<”年”;
cout<<Month<<”月”;
cout<<Day<<”日”<<endl;
}
5、类成员存取权限:
公有的(public):定义了外部接口,只有公有成员才能被用户程序直接访问。
私有成员(private):定义了类的内部使用的数据和函数,私有成员只能被自己所属类的成员函数访问。
保护成员(protected):存取权限介于公有成员和私有成员之间,它在类的继承中使用。
6、类的具体使用:
例1:
#include<iostream>
using namespace std;
//定义一个日期的类:
class Dates{
private :
int Year,Month,Day;
public:
void Set(int y,int m,int d);
void Display();
}; //在定义类时最后的这个分号不能省略;
//下面定义各成员函数的功能:
//Set函数和Display函数都不需要返回值,所以都为void类型;
void Dates::Set(int y,int m,int d)//类型为void;
{
Year=y;
Month=m;
Day=d;
//return 0;
}
void Dates::Display() //类型为void;
{
cout<<"现在日期为:"<<endl;
cout<<"\t"<<Year<<"年";
cout<<Month<<"月";
cout<<Day<<"日"<<endl;
}
/****************主函数***********************/
int main()
{
int year,month,day;
Dates riqi;//定义一个Date类的数据类型;
cout<<"请输入日期:"<<endl;
cin>>year>>month>>day;
//通过Date类的公有成员函数Set来修改它的私有数据成员:
riqi.Set(year,month,day);
//通过Date类的公有成员函数Display来访问它的私有数据成员:
riqi.Dates::Display();
//system("pause");//(dev-C++环境下时,使用该语句,程序执行完使其暂停)
return 0;
}
例2:设计一个含有4个整数的类,要求能够求出这4个数的最大值和最小值:(dev-c++环境下)
#include<iostream>
using namespace std;
//定义一个能求最大最小的类
class MaxMin4
{
private:
int a,b,c,d;
int Max2(int,int);
int Min2(int,int);
public:
void Set(int,int,int,int);
int Max4();
int Min4();
};
//类中成员函数的实现:
void MaxMin4::Set(int aa,int bb,int cc,int dd)
{
a=aa;b=bb;c=cc;d=dd;
}
int MaxMin4::Max2(int x1,int x2)
{
if (x1<x2) return x2;
else return x1;
}
int MaxMin4::Min2(int x1,int x2)
{
if(x1<x2)return x1;
else return x2;
}
int MaxMin4::Max4()
{
int x,y;
x=Max2(a,b);
y=Max2(c,d);
return Max2(x,y);
}
int MaxMin4::Min4()
{
int x,y;
x=Min2(a,b);
y=Min2(c,d);
return Min2(x,y);
}
/**************主函数********************/
int main()
{
int x1,x2,x3,x4;
int max,min;
MaxMin4 MAX;
cout<<"please 4 numbers:"<<endl;
cin>>x1>>x2>>x3>>x4;
MAX.Set(x1,x2,x3,x4);
max=MAX.Max4();
min=MAX.Min4();
cout<<"Max number is :"<<max<<endl;
cout<<"Min number is :"<<min<<endl;
system("pause");
return 0;
}
例3:成员函数重载:
#include<iostream>
using namespace std;
//定义一个能求最大最小的类
class MaxMin4
{
private:
int a,b,c,d;
int Max(int,int);
int Min(int,int);
public:
void Set(int,int,int,int);
int Max();
int Min();
};
//类中成员函数的实现:
void MaxMin4::Set(int aa,int bb,int cc,int dd)
{
a=aa;b=bb;c=cc;d=dd;
}
int MaxMin4::Max(int x1,int x2)
{
if (x1<x2) return x2;
else return x1;
}
int MaxMin4::Min(int x1,int x2)
{
if(x1<x2)return x1;
else return x2;
}
int MaxMin4::Max()
{
int x,y;
x=Max(a,b);
y=Max(c,d);
return Max(x,y);
}
int MaxMin4::Min()
{
int x,y;
x=Min(a,b);
y=Min(c,d);
return Min(x,y);
}
/**************主函数********************/
int main()
{
int x1,x2,x3,x4;
int max,min;
cout<<"please 4 numbers:"<<endl;
cin>>x1>>x2>>x3>>x4;
MAX.Set(x1,x2,x3,x4);
max=MAX.Max();
min=MAX.Min();
cout<<"Max number is :"<<max<<endl;
cout<<"Min number is :"<<min<<endl;
system("pause");
return 0;
}
六、对象:
对象是C++语言程序设计的基本单位,类描述了一类问题的共同属性和行为,对象是类的实例,对象是由类作为类型定义的变量,如上例中main函数里MaxMin4 MAX这条语句,是以MaxMin4作为类型定义了一个MAX变量。
1、对象的定义:
<类名><对象名>;
2、访问对象的公有成员的语法:
<对象名> .<公有数据成员>;
或 <对象名>.<公有成员函数名>(<参数表>);
对象是类的实例,是由类生成的变量。
七、构造函数:
要创建一个对象,一般要将对象中的数据成员进行初始化和为对象申请必要的存储空间。在声明对象的同时可以指定数据成员的初始值,对象如同普通变量一样,在声明后立即将指定的初始值写入。
构造函数的作用就是在对象被创建时利用特定的值构造对象,将对象初始化为一个特定的状态,使此对象具有区别其他对象的特征。
构造函数也是类的一个成员函数,除了具有一般成员函数的特征之外,还有一些特殊的性质。构造函数的函数名与类名相同,而且不能有任何返回类型,也不能标为void类型。构造函数一般被声明为公有函数,构造函数也可以重载。构造函数是在声明对象时由C++系统自动调用。
构造函数和解析函数都是自动执行的。
例:
#include<iostream>
using namespace std;
//定义一个时间的类:
class Date
{
private:
int Year,Month,Day;
public:
Date(int,int,int);//构造函数,用来对数据成员进行初始化;
void Set(int,int,int);
void Display();
} ;
//实现类的成员函数:
Date::Date(int x1,int x2,int x3)//这是构造函数:
{
Year=x1;
Month=x2;
Day=x3;
}
void Date::Set(int x1,int x2,int x3)
{
Year=x1;
Month=x2;
Day=x3;
}
void Date::Display()
{
cout<<"\t"<<Year<<"年"<<Month<<"月"<<Day<<"日"<<endl<<endl;
}
/***************主函数*********************/
int main()
{
int x1,x2,x3;
//定义日期类对象myDate,并设初始值为 2008年9月1日:
Date myDate(2008,9,1);
cout<<"初始值为:"<<endl;
myDate.Display();
cout<<"请输入你要设置的日期:"<<endl;
cin>>x1>>x2>>x3;
//设置日期:
myDate.Set(x1,x2,x3);
cout<<"您设置的日期为:"<<endl;
myDate.Display();
system("pause");
return 0;
}
七、(附)C++类构造函数
大部分对象在使用之前没有正确的初始化是C++出错的主要领域引入类的构造函数是正确的初始化类的对象一般什么时候触发并调用类的构造函数呢?
答案是:当我们用类来定义一个类变量的时候, 如:
class demo{.....}; 声明并定义好完整的类
//当我们用类去建立一个对象时,它首先调用类的构造函数
demo d; //调用类的无参数的构造函数
demo d1(参数1,..) //按参数个数不同,调用类中不同的构造函数。
2.定义
构造函数名和类名完全一样,可以根据不同的参数来实现重载不同的构造函数构造函数是没有任何返回值的,它默认的是public,inline函数。
2.1定义格式
构造函数可以重载,可以是无参数,有参数,有默认参数)
声明三个构造函数
代码如下
class item{
public:
item(std::string& book=\"\" ); //带默认形参的构造函数
item(std::string& ); //带形参的构造函数
item(); //无形参的构造函数
};
2.2构造函数的两种初始化成员变量的方法
如下面的类
class demo(){
public:
//构造函数在下面添加
private:
int x;
int y;
std::string name;
};
构造函数初始化成员有两种方法
A.使用构造函数的初始化列表进行初始化
格式:funname(参数列表):(初始化列表){}
初始化列表: 成员名1(形参名1),成员名2(形参名2),成员名n(形参名n)
代码:
demo(int a=0,int b=0,std::string s=\"\"):x(a),y(b),name(s){}
B.使用构造函数的函数体进行初始化
格式:funname(参数列表){函数体内赋值}
它和传统的C函数差不多
代码:
demo(int a=0,int b=0,std::string s=\"\"){
x=a;
y=b;
name=s;
}
3.基类与派生类
3.1在基类用构造函数初始化类的成员
\"默认形参的方式+初始化列表\"来初始化基类,而参数顺序不是主要的代码如下:
class item_base(
public:
//两个构造函数
item_base():(isbn(\"\"),price(0.0){}
item_base(std::string& book=\"\",double s_price=0.0) //带默认形参
:isbn(book),price(s_price){} //初始化列表
......
private:
std::string book;
double price
);
3.2派生类
由于初始化顺序是从基类到派生类的
基类的构造函数负责初始化基类与派生类的构造数负责初始化派生类,
在MFC中N层继承类库中,都是不同层中的类负责初始化自己本身和调用上一级构造函数进行初始化
如何在派生类初始化从基类中继承来的protected成员和基类的private成员呢?
答案是:
在派生类的构造函数间接的调用基类构造函数来实现,派生类的初始化列表必须明确指出基类的初始化式
[Page]
1.无参数的构造函数
格式:构造函数名():(基类构造函数(),派生类成员d1(值),d2(值),n(值){}
2.有参数的构造函数
格式:
构造函数名(基类参数...派生类参数...)
:(基类构造(b1(参数),b2(参数)),派生类成员d1(参数),d2(参数),n(参数){}
class bulk_item: public item_base{
public:
//1.派生类无参数的构造函数
bulk_item():(item_base(),int_qty(0),discount(0.0){}
//2.派生类有参数的构造函数
bulk_item(std::string& book=\"\",double s_price=0.0,int qty=0,double dis=0.0)//函数参数
:item_base(book,s_price),int_qty(0),discount(0.0){} //初始化列表
private:
int min_qty;
double discount;
};
4.设计指导
4.1初始化方式选择:
无参数的构造函数:主要用初始化列表来初始化成员变量
有参数的构造函数:默认形参+初始化列表的构造函数
4.2派生类的初始化顺序
由于初始化顺序是从基类到派生类的,在初始化列表中应该先初始化基类,然后再是派生类本身
如果要求程序高性能,使用初始列表是C++的生产首选,对于习惯于C的程序员,在函数体初始化成员也是可行。
八、析构函数:
析构函数(destructor) 与构造函数相反,当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。
析构函数是对象的生命期结束时要执行的一段程序,用来完成对象被删除前的一些清理工作,析构函数的名称和类名相同,在类名前面加上一个波浪号“~”。析构函数同构造函数一样,不能有任何返回类型,也不能有void类型。析构函数是无参函数,不能重载,一个类只能有一个析构函数。
以C++语言为例:析构函数名也应与类名相同,只是在函数名前面加一个波浪符~,例如~stud( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数(即使自定义了析构函数,编译器也总是会为我们合成一个析构函数,并且如果自定义了析构函数,编译器在执行时会先调用自定义的析构函数再调用合成的析构函数),它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。
特点:
(1)、析构函数是特殊的成员函数,该函数的名字为“~”后面跟着类名,该函数不能指定返回类型,也不能有参数;
(2)、一个类只能定义一个析构函数;
(3)、析构函数在对象生命期结束时被直接调用,程序中一般不要调用析构函数;
对象的作用域同变量的作用域一样,在它所属的最小程序块中有效,也就是说,对象的作用域不能出花括号,但可以进花括号。
/************************************
* 析构函数
************************************/
#include<iostream>
using namespace std;
//定义一个圆类Circle:
class Point
{
private :
float x,y;
public :
Point();
Point(float ,float );
void set(float ,float );
void display();
~Point();
};
//成员函数实现:
Point::Point()//无参构造函数;
{
cout<<"run Point() function \n";
x=0;y=0;
}
Point::Point(float a,float b)//有参构造函数;
{
cout<<"run Point(float ,float ) function\n";
x=a;y=b;
}
void Point::set(float a,float b)
{
x=a;y=b;
}
void Point::display()
{
cout<<"x="<<x<<" y="<<y<<endl;
}
Point::~Point()
{
cout<<"进入析构函数!"<<endl;
}
/*********主函数*********************/
int main()
{
Point a(4,6);
a.display();
{
cout<<"进入程序块\n";
Point b(3,5);
a.display();
b.display();
}
cout<<"退出了程序块!\n";
a.display();
//b.display(); 这一句就不能使用了,因为b对象的作用域不能出花括号;
system("pause");
return 0;
}
九、复制构造函数:
复制构造函数是一个特殊的构造函数,具有一般构造函数的所有的,它只有一个参数,参数类型是本类对象的引用。其功能应该设计为将已知对象的值复制到正在定义的新的同类型对象。
一般形式:
<类名>(<类名>&<对象名>);
例:
/************************************
* 析构函数
************************************/
#include<iostream>
using namespace std;
//定义一个圆类Circle:
class Point
{
private:
float x,y;
public:
Point();
Point(float a,float b);
Point(Point &obj);
~Point(){cout<<"执行析构函数!\n";}
void Set(float a,float b);
void Display();
};
Point::Point()
{
x=0;y=0;
cout<<"run Point() function!\n";
}
Point::Point(float a,float b)
{
x=a;y=b;
cout<<"run Point(float a,float b) function!\n";
}
Point::Point(Point &obj)//通过引用来复制数值;
{
x=obj.x;
y=obj.y;
}
void Point::Set(float a,float b)
{
x=a;y=b;
}
void Point::Display()
{
cout<<"点的位置是:("<<x<<','<<y<<")\n";
}
/***********主函数*************/
int main()
{
Point a(53,24);
a.Display();
Point b(a);//把a的值复制给b;
b.Display();//b的值为(53,24);
a.Set(16,28);//a的值重设;
a.Display();
Point c(a);
c.Display();
c.Set(3.23,5.2);//重设c的值;
c.Display();
cout<<"现在依次输出a,b,c的值:\n";
a.Display();
b.Display();
c.Display();
system("pause");
return 0;
}
复制构造函数的参数类型必须是自己所属类的引用。
应用复制构造函数定义新对象也可以写作:<类名><新对象名>=<老对象名>
如上面程序:Point b(a) 可以写作Point b=a;
类的定义如上,函数的参数是对象:
Void f1(Point x) //不过要是用void f1(Point & x)则更好;
{
cout<<”f1函数在运行”<<endl;
x.Display();
cout<<”f1函数结束运行”<<endl;
}
void main()
{
Point a(3,6.5);
cout<<”准备运行f1函数”<<endl;
f1(a);
cout<<”回到了主程序”<<endl;
}
十、重载赋值运算符(查看相应资料补充)
C++语言引入了运算符重载机制,程序员可以按照需要重新定义大部分的运算符,如“+”、“-”、“*”、“=”、“!”等,替换类中的成员函数,完成特定的功能。
例如,针对复数类complex,重载加、减、乘、除运算符,分别代替加法成员函数Add(),减法成员函数Sub()、乘法成员函数Mul()和除法成员函数Div()。
运算符重载只能适用于类类型和枚举类型,而不能重载基于内嵌类型的运算符,更不能定义一种不存在的运算符。运算符重载也不能改变现有运算符的优先级、结合性、语法结构和操作数的个数。
运算符重载从本质上说,也是一种函数的重载。
1. 成员函数形式
运算符重载的声明形式有两种:一是成员函数形式,二是友元函数形式。
在成员函数形式中,运算符重载函数等同于一个类成员函数,在类体中被声明,格式如下:
<返回类型> operator <运算符> (<参数表>)
其中,operator是关键字,表明该成员函数是一个运算符重载函数。引用该运算符重载函数的类对象为第一操作数,参数表中的参数是第二操作数,因此对于单目运算符来说,参数表应该为空;对于双目运算符来说,参数表中只有一个参数──第二操作数。
在编写程序时,要进行对象赋值,不能直接使用赋值符号“=”,应该对赋值去处符“=”进行重载。
/************************************
* 重载赋值运算符
************************************/
#include<iostream>
using namespace std;
class String
{
private :
char str[32];
public :
String(){str[0]='\0';}
String(char* s){strcpy(str,s);}
String(String &s){strcpy(str,s.str);}
String & operator=(String &);
String & operator=(char *);
void display(){cout<<str<<endl;}
};
String & String::operator=(String &s)
{
if(this==&s)return *this; //this 是C++的关键字,表示“自己”;
strcpy(str,s.str);
return * this;
}
String & String::operator=(char *s)
{
strcpy(str,s);
return * this;
}
int main()
{
String s1;
cout<<"开始的s1:\n";
s1.display();
s1="C++是最好的计算机语言!\n";
cout<<"用字符串赋值后的s1:\n";
s1.display();
String s2("面向对象程序设计真棒!");
cout<<"开始s2:\n";
s2.display();
s2=s1;
cout<<"用s1去赋值后的s2:\n";
s2.display();
system("pause");
return 0;
}
十一、指针
指针是一种数据类型,是一种特殊的数据类型,具有指针类型的变量称为指针变量,指针变量存放其它变量或对象地址,它可以有效地表示数据之间复杂的逻辑关系。
(1)指针变量是专门用来存放地址的。
(2)指针变量的类型是所指向的变量的类型,而不是指针本身数据值的类型。
(3)指针与整数的加减运算:指针p加上或减去整形数n,其意义是指针当前指向位置的前方或后方第n个数据的地址。
(4)指针的++和—运算:当指针的递增符被激活,它检查变量的类型,然后选择一个合适的增量值。
(5)指针的加减运算,必须是有意义的,也就是得到的存储单元一定是定义过的。
例:
int array[100];
int *pi;
pi=array;
array,pi,&array[0]都是指向数组array的起始地址。
array[k]、*(pi+k)和pi[k]是等价的,都表示数组array第k+1元素。
十二、对象指针
一个对象一旦被创建,系统就给它分配一个存储空间,该存储空间的起点可以像数据对象的地址一样,使用指针变量操作。对象初奴化后,占用内存空间,可以使用指针变量指向对象的起始地址,称为对象指针。
定义形式:
<类名>*<对象指针名>;
用对象指针,可以使用成员访问符“->”来引用对象成员。通过指针访问对象成员的形式为:
<对象指针>-><成员名>;
/************************************
* 对象指针应用
************************************/
#include<iostream>
using namespace std;
class Location
{
private:
float x,y;
public:
Location(float a=0,float b=0)
{x=a;y=b;}
float getx(){return x;}
float gety(){return y;}
void print()
{cout<<"(x,y)=("<<x<<','<<y<<")\n";}
};
int main()
{
Location A(12.1,44.3);
Location *ptr;
ptr=&A;
ptr->print();
float x,y;
x=ptr->getx();
y=ptr->gety();
cout<<x<<","<<y<<endl;
system("pause");
return 0;
}
十三、new和delete函数
new和delete是C++动态申请存储单元和删除存储单元的的函数。
int *p=new int[length]; //申请一个长度为length的整型数组
如果用new创建对象数组,那么只能使用对象的无参数构造函数。
没有无参数构造函数的类不能生成对象数组。
由new申请的对象,运行结束时,必须由delete 删除。在用delete释放对象数组时,注意不要丢了符号“[]”。例如:
delete []objects;
此部分更多参考new 和 delete 的用法,或参考:
十四、this指针
this指针是隐含在对象内的一种指向自己的指针。当一个对象创建了之后,它的每一个成员函数都可以使用this指针。
当一个对象调用成员函数时,系统先把对象的地址赋给this指针,然后调用成员函数,成员函数对成员数据进行操作时,隐含使用了this指针。
关于this指针的一个经典回答:
当你进入一个房子后, 你可以看见桌子、椅子、地板等, 但是房子你是看不到全貌了。 对于一个类的实例来说, 你可以看到它的成员函数、成员变量,
但是实例本身呢?this是一个指针,它时时刻刻指向你这个实例本身。
例:使用this指针复制数据:
#include<iostream>
using namespace std;
class Obj
{
private:
int a,b;
public :
Obj(int x=0,int y=0)
{a=x;b=y;}
void copy(Obj &);
void display()
{cout<<"a="<<a<<",b="<<b<<endl;}
};
void Obj::copy(Obj &aObj)
{
if(this==&aObj)return ;
this->a=aObj.a;
this->b=aObj.b;
}
int main()
{
Obj x1(12,11),x2(23,14);
cout<<"x1:";x1.display();
cout<<"x2:";x2.display();
x1.copy(x2);
cout<<"x1:";x1.display();
system("pause");
}
十五、引用
(1)、&在定义时出现在赋值运算符的左边表示是“引用”,否则是取址符。
(2)、一个对象一旦有了别名,此别名就不能再作为别的对象的别名,所以声明时必须进行初始化。
(3)、有了别名的对象,不管是对真名还是对别名进行操作,都是对此对象操作。
(4)、一个被声明成引用的变量,并不另外再占有存储空间。
引用格式:
<类型标识符>&<引用标识符>=<变量标识符>
例如:
int i,a[100];
int &ii=i,&aa=a[10];//ii是变量i 的引用,aa是数组元素a[10]的引用;
引用必须初始化,在声明语句中为引用提供的初始值必须是一个变量或另一个引用。
无初始化的引用是无效的,但是可以对用new创建的无名实体建立一个引用。
例如:float &r=*new float(5.23);
r是对无名实数变量的引用,初值是5.23;
指针也是变量,可以对指针变量进行引用。
1、例如:对指针变量的引用
#include<iostream>
using namespace std;
int main()
{
int a=100;
int *p1=&a; int * &p2=p1;
cout<<"a="<<a<<",*p1="<<*p1
<<",p2="<<*p2<<endl;
system("pause");
}
运行结果为:
a=100,*p1=100,*p2=100
引用的使用语法结构同对象或变量完全一样。
2、例:用引用作为函数的参数
void change(int &a,int &b)
{
int m;
m=a;a=b;b=m;
}
int main()
{
int a=11,b=22;
cout<<"a="<<a<<",b="<<b<<endl;
change(a,b);
cout<<"a="<<a<<",b="<<b<<endl;
}
引用作为函数的参数的优点是起到了指针的作用,但并不进行参数间的传递。由于引用是“别名”,实参与形参之间不是值传递,而是一种“映射”,所以对形参的改变,实际上就是对实参的改变。
3、 引用返回值
若函数的返回值类型为引用,可以通过对函数赋值,实现对函数返回的引用的赋值。
/**********VC环境下***************/
#include<iostream>
float temp;
float & max(float a,float b)
{
if (a>b)temp=a;
else temp=b;
return temp;
}
int main()
{
float x=max(12,13);
cout<<"x="<<x<<endl;
cout<<"temp="<<temp<<endl;
//实现对max()函数的返回变量temp的赋值
max(12,13)=327.56;
cout<<"temp="<<temp<<endl;
}
十六、使用指针和引用作为参数,设计一个函数,参数a,b,c为整型变量,完成a<=b<=c
#include<iostream>
using namespace std;
void f1(int *pa,int *pb,int *pc)
{
int x;
if(*pa>*pb){x=*pa;*pa=*pb;*pb=x;}
if(*pb>*pc){x=*pb;*pb=*pc;*pc=x;}
if(*pa>*pc){x=*pa;*pa=*pc;*pc=x;}
cout<<"test"<<endl;
}
void f2(int &a,int &b,int &c)
{
int x;
if (a>b){x=a;a=b;b=x;}
if(b>c){x=b;b=c;c=x;}
if(a>c){x=a;a=c;c=x;}
}
int main()
{
int x1,x2,x3;
int &p1=x1,&p2=x2,&p3=x3;
cout<<"please input three numbers:"<<endl;
cin>>x1>>x2>>x3;
cout<<"the number is :"<<endl;
cout<<"x1="<<x1<<",x2="<<x2<<",x3="<<x3<<endl;
//以指针作为参数
f1(&x1,&x2,&x3);
cout<<"以指针作为参数,执行结果:"<<endl;
cout<<"x1="<<x1<<",x2="<<x2<<",x3="<<x3<<endl;
//通过引用给变量重新赋值
p1=54;p2=34;p3=51;
cout<<"重新赋值:"<<endl;
cout<<"x1="<<x1<<",x2="<<x2<<",x3="<<x3<<endl;
f2(p1,p2,p3);
cout<<"以引用作为参数执行结果:"<<endl;
cout<<"x1="<<x1<<",x2="<<x2<<",x3="<<x3<<endl;
system("pause");
return 0;
}
不过在上在的程序当中,使用指针函数时不能达到预期的效果如输入3,2,1。所以注意调试,此问题留待后面解决。
十七、继承
在对一些新的对象进行分类之前,首先要搞清楚它们的共同特征是什么?较大的差别是什么?接着把差别不大的分为一类,再找出这一类的共同特征,逐步细化,形成一个层次结构。最高层是最普通的,特征最简单,越往下层越具体。一旦高层类的某个特征被定义下来,所有在它之下的种类就包含了该特征,不必再重新定义。实现了软件的重用。
面向对象的程序设计中,可以在已有的类的基础上定义新的类,而不需要把自己已有类的内容重新书写一遍,这就是继承。已有的类称为基类或父类,在继承建立的新类称为派生类或导出类、子类。
继承性允许一个类从其他类中继承属性。如果一个对象从单个基类中继承了属性,就被称为单继承;如果一个对象从多个基类中继承了属性,就被称为多重继承。
十八、派生类(或导出类、子类)的定义
定义格式:
class<派生类名>:<继承方式><基类名>{
private:
新增私有成员声明语句列表
public:
新增公有成员声明语句列表
protected:
新增保护成员声明语句列表
};
继承方式决定了子类对父类的访问权限,包括public、private和protected3种,默认为private,最常用的是public。
关于继承的几点说明:
(1)如果子类继承了父类,则子类自动具有父类的全部数据成员(数据结构)和成员函数(功能);但是,子类对父类的成员的访问有所限制。
(2)子类可以定义自己的成员:数据成员和成员函数。
(3)基类、派生类或父类、子类都是相对的。一个类派生出新的类就是基类。派生类也可以被其他类继承,这个派生类同时也是基类。
关于继承方式的说明:
private派生使得基类的非私有成员都成为派生类中的私有成员;protected派生使基类中的非私有成员的访问属性在派生类中都降为保护的; public派生使得基类的非私有成员的访问属性在派生类中保持不变。
小结:
公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,在派生类中是不能访问的。
私有继承的特点是基类的公有成员和保护成员都为派生类的私有成员,而且不能被这个派生类的子类所访问,但是可以被派生类的成员函数访问。
保护继承的特点是基类的公有成员和保护成员都为派生类的保护成员,而且只能被它的派生类成员函数或友元函数访问,基类的私有成员仍然是私有的。
类中成员的权限有3种:公有成员在任何地方都能访问,私有成员只有本类中的成员函数才可以访问,保护成员只有本类中的或派生类中的成员函数可以访问。有3种派生,公有派生对基类的访问权限不变,私有派生对基类的访问完全变为私有的,保护派生对基类的非私有访问权限变为保护的。基类的私有成员任何派生类都是不可见的。
Private派生 | Protected派生 | Public派生 |
基类 派生类 | 基类 派生类 | 基类 派生类 |
Privatep 不可见 Protected private Public private | Privatep 不可见 Protected protected Public protected | Privatep 不可见 Protected protected Public public |
不管怎样,私有成员只能被自己所属类的成员函数访问。
保护成员:具有私有成员和公有成员的特性,对其派生类而言是公有成员,对其对象而言是私有成员。
例:
#include<iostream>
using namespace std;
class Piont
{
private:
float x,y;
public:
Piont(float a=0,float b=0)
{x=a;y=b;}
void SetP(float a=0,float b=0)
{x=a;y=b;}
void Display()
{cout<<"位置是:("<<x<<","<<y<<");\n";}
};//不要忘了未尾的分号;
class Circle:public Piont
{
private :
float r;
public:
Circle(float z=0,float a=0,float b=0):Piont(a,b)
{r=z;}
void SetC(float z=1,float a=0,float b=0)
{ r=z;SetP(a,b);}//注意这地方与构造函数不同哦!
void Print()
{
cout<<"圆的";
Display();
cout<<"圆的半径是:"<<r<<endl;
}
};//不要忘了未尾的分号;
int main()
{
Circle a(3.11);
a.Print();
a.SetC(15,23,87);
a.Print();
system("pause");
return 0;
}
十九、单继承
单继承就是每个派生类只有一个基类,派生类只从单个基类中继承属性。
保护成员:具有私有成员和公有成员的特性,对其派生类而言是公有成员,对其对象而言是私有成员。
例:学校职工类的公有继承和私有继承
/********************************
*学校教职工类的公有继承和私有继承
********************************/
#include<iostream>
using namespace std;
//定义一个people类:
class people
{
private:
char name[10],sex;//姓名,性别;
long idnumber;//身份号码;
public:
people(long idnum=0,char *n,char s)//构造函数;
{
idnumber=idnum;
strcpy(name,n);
sex=s; //性别设置;
}
void p_show()
{
cout<<"身份证号码:"<<idnumber;
cout<<" 姓名:"<<name<<" 性别:"<<sex<<endl;
}
};
//定义一个member教工类:
class member:private people//教工类私有继承people类;
{
int m_num;//工号;
public:
char department[100];//部门;
member(long n,char *na,char s='m',int mn,char *md='\0'):people(n,na,s)
{m_num=mn; strcpy(department,md);}//构造函数;
void m_show()
{
p_show();
cout<<"教工编号:"<<m_num<<" 单位:"<<department<<endl;
}
} ;
//定义一个work工人类:
class worker:public member//work类公有继承member类;
{
char station[50];// 岗位;
public:
worker(long n,char *na,char s='m',int mn=0,char *md='\0',char *st='\0'):
member(n,na,s,mn,md){strcpy(station,st);}//构造函数;
void w_show()
{
cout<<"\n工人:\n";
m_show();
cout<<"岗位:"<<station<<endl<<endl;
}
};
//定义一个教师类,私有继承member:
class teacher:private member
{
char course[10];
public:
teacher(long n,char *na,char s='m',int mn=0,char *md='\0',char *tc='\0'):
member(n,na,s,mn,md){strcpy(course,tc);}//构造函数;
void t_show()
{
cout<<"\n教师:\n";
m_show();
cout<<"课程:"<<course<<endl<<endl;
}
};
int main()
{
worker w(1232424,"王祥",'m',3761,"生物系","实验室");
w.w_show();
w.m_show();//work类公有继承member,可以直接访问member类的公有成员;
//w.p_show();//member私有继承people,不可直接访问people类公有成员;
teacher t(661001, "李辉",'m',1954,"计算机系","C++");
t.t_show();
//t.m_show();//teacher类私有继承member,不可直接访问member类公有成员;
//t.p_show();//teacher类私有继承member,member私有继承people类,不可直接访问people类的公有成员;
system("pause");
return 0;
}
二十、类的保护继承
/
********************************
*类的保护继承
********************************/
#include<iostream>
using namespace std;
class A
{
int i;
protected:
int j ;
void show_A1()
{cout<<"A protected show:i="<<i<<" j="<<j<<endl;}
public:
A(int x,int y){i=x;j=y;}
void showd_A2()
{
cout<<"A2 run A1().....";
show_A1();
cout<<"A2 run over."<<endl;
}
};
class B:protected A
{
int x;
public:
B(int i,int j,int z):A(i,j)
{x=z;}
void show_B()
{
cout<<"B run show_A1().......";
show_A1();
cout<<"B public show x="<<x<<endl;
cout<<"B run over!"<<endl;
}
};
class C:public B
{
public:
C(int i,int j,int x):B(i,j,x){}
void show_C()
{
cout<<"C run show_A2()......";
showd_A2();
cout<<"C run show_A1()......";
show_A1();
cout<<"C run show_B().......";
show_B();
cout<<"C run over!"<<endl;
}
};
int main()
{
B b(1,2,3);
b.show_B();
//b.showA2();//B从A类保护继承,所以不能直接访问A类的公有成员;
C c(100,200,300);
c.show_C();
//c.show_A2();C从B类公有继承,而B从A保护继承,所以不能直接访问A类的公有成员;
system("pause");
return 0;
}
分析:因为B 是保护继承,所以会将A类中的保护成员和公有成员当作自己的保护成员,所以C可以直接访问B的保护成员,也即可以访问A1。而保护成员在对象中,是不能直接访问的,它可以由继承类直接访问,但不能由该类的对象直接访问。
二十一、构造函数和析构函数
在派生关系中,构造函数和析构函数是不能继承的,对派生类要重新定义构造函数和析构函数。因为一个派生类对象中也包含了基类数据成员的值,所以在声明一个派生类对象时,系统首先要通过派生类的构造函数调用基类中的构造函数,对基类成员初始化,然后对派生类中新增的成员初始化。也就是说,派生类的构造函数除了对新增成员进行初始化处理之外,还要调用基类的构造函数。
一般来说,若X类派生出Y类,派生类Y中的构造函数的语法格式如下:
Y::Y(ArgX1, ArgX2.,…,ArgY1, ArgY2,…):X(ArgX1, ArgX2,…)
其中Y为派生类名,X为Y的直接基类名,ArgX是X构造函数中的参数,ArgY是Y构造函数中的参数。
二十二、多继承
多继承是由多个基类派生出新的类。语法格式如下:
class<派生类名>::<继承方式1><基类名1>,<继承方式2><基类名2>,……
{<新增成员列表>};
在多继承中,若基类成员有重名的,使用这个成员时,要加基类名和作用域运算符::来指明是哪个类的成员。例如:Class_A、Class_B、Class_C、Class_D都有变量x,在派生类Class_D使用基类的变量x,要分别写作:Class_A::x、Class_B::x、Class_C::x。
例:
/********************************
*多继承
********************************/
#include<iostream>
using namespace std;
class A
{
int i;
public:
A(int ii=0){i=ii;}
void show()
{cout<<"A::show()A中i="<<i<<endl;}
};
class B
{
int i;
public:
B(int ii=0){i=ii;}
void show()
{cout<<"B::show()B中i="<<i<<endl;}
};
class C:public A,public B
{
int i;
public:
C(int iA=0,int iB=0,int iC=0):
A(iA),B(iB){i=iC;}//构造函数;
void show()
{cout<<"C::show()C中i="<<i<<endl; }
};
int main()
{
C c(1,2,3);
c.A::show();
c.B::show();
c.show();
system("pause");
return 0;
}
二十三、虚基类
在多继承中,若在多条继承路径上,有公共基类,这个公共基类便会产生多个副本。为了解决这个问题,把公共基类定义为虚基类。使用虚基类的继承称为虚拟继承。
语法格式:
class<派生类名>:<继承方式><基类名>
例:
/********************************
*多继承
********************************/
#include<iostream>
using namespace std;
class A
{
public:void fn()
{cout<<"A:fn()"<<endl;}
};
class B1:virtual public A
{
public:
void fn()
{cout<<"B1:fn()"<<endl;}
};
class B2:virtual public A
{
public:
void fn()
{cout<<"B2:fn()"<<endl;}
};
class C1:public B1{};
class C2:public B2{};
class D:public C1,public C2{};
int main()
{
D obj;
obj.C1::fn();
obj.C2::fn();
obj.A::fn();
system("pause");
}
若类A不是虚基类语句obj.A::fn()就不能执行,这是因为计算机无法确定是执行B1继承的基类A的函数,还是执行B2继承的基类A的函数,具有二义性。A 为虚基类就只有一个基类副本。
二十三、虚基类构造函数
(以下程序继续调试)
/********************************
*虚基类构造函数
********************************/
#include<iostream>
#include<string>
using namespace std;
class base
{
private:
char name[15];
public:
base(char *n="王五"){strcpy(name,n);}
void show(){cout<<"base output name:"<<name<<endl;}
};
class base1:virtual public base
{
public:
base1(char *n):base(n){}
};
class base2:virtual public base
{
public:
base2(char *n):base(n){}
};
class derive:virtual public base1,public base2
{
char name[15];
public:
derive(char *,char *,char *,char *);
derive(char *,char *,char *);
void show_D()
{cout<<"D output:"<<name<<endl;}
};
derive::derive(char *p,char *q,char *r,char *t):
base(p),base1(q),base2(r)
{strcpy(name,t);}
derive::derive(char *p,char *q,char *r):base1(p),base2(q)
{strcpy(name,r);}
int main()
{
derive d("赵易","钱耳","孙伞","李思");
d.show();
d.show_D();
derive c("王","李","任");
c.show();
c.show_D();
base1 b("周");
b.show();
system("pause");
return 0;
}
二十四、静态成员的定义
静态成员作为类的一种数据成员可以实现多个对象之间的数据共享,并且使用静态成员还不会破坏信息隐藏的原则,保证了程序的安全性。
类的静态成员有两种类型:静态数据成员和静态成员函数。类的所有实例化的对象均可以访问类的静态数据成员,但它们都不保存类的静态数据成员;类的静态成员由于不含有this指针,因此访问类的静态成员要使用类名和类标识符“::”。
将一个类的数据成员定义为静态成员的格式为:
<static><数据类型><静态数据成员名>
<static><函数类型><静态成员函数名><(参数表)>
例: class date{
int year;
static int month;
static int day;
…
void disply();
static int count();
};
静态成员是属于类的,只在类中存在,在对象中没有自己的副本,如果在类中定义了静态成员,则该类的每个对象就都可以操作它。也就是说,类的静态成员只有一个,可以被该类的任何对象访问。
二十五、静态成员的使用
静态成员的访问格式如下:
<类名>::<静态数据成员名>
<类名>::<静态成员函数名><(参数表)>
静态成员变量使用前必须初始化。静态成员变量的访问控制权限没有意义,静态成员变量均作为公有成员使用。
例:(继续调试)
#include <iostream.h>
class CK{
private:
double cunkuan;
public:
static double lixi;
CK(double);
static void modLixi(double);
void calLixi(int m=1);
void set(double x){cunkuan=x;}
};
CK::CK(double c){cunkuan=c;}//构造函数;
void CK::modLixi(double x){lixi=x;}
void CK::calLixi(int m)
{
double x=0;
x=cunkuan*lixi/12;//计算月利息;
cunkuan+=x*m;//将利息加入到存款中;
cout<<cunkuan<<endl;
}
//初始化静态变量,静态变量必须初始化:
double CK::lixi=0;
int main()
{
CK saver1(2000.0),saver2(3000.0);
CK::modLixi(0.03);
cout<<"年利率为3%时"<<endl;
cout<<"一个月后甲的存款余额为:";
saver1.calLixi();
cout<<"一个月后乙的存款余额为:";
saver2.calLixi();
saver1.set(2000.0);
saver2.set(3000.0);
cout<<"三个月后甲的存款余额为:";
saver1.calLixi(3);
cout<<"三个月后乙的存款余额为:";
saver2.calLixi(3);
return 0;
}
二十六、友元的定义
友元的提出就是为了解决如何让一个在类外部定义的函数访问类的私有成员的问题。友元不是该类的成员函数,只是一个在该类外部定义的其他函数,但是却可以访问该类的私有成员。
友元是C++提供的一种破坏数据封装和数据隐藏的机制,可以将一个函数定义为类的友元函数,也可以将一个类定义为类的友元类,以便使用它们可以访问类的私有成员。
友元分为友元函数和友元类。
一个类的友元函数是定义在类外部的一个函数,它不是类的成员函数,但是却可以访问类的私有成员变量和私有成员函数,在类的内部要有它的声明,声明格式如下:
friend<函数类型><友元函数名>(<参数表>);
同样,也可以将一个类定义为另一个类的友元。如果类A是类B的友元类,那么类A的所有成员函数都是类B的友元函数。类A要在类B中声明,声明格式为:
friend class<友元类名>;
例 :定义一个求和的友元函数:
#include <iostream.h>
class Integer{
int n;
public:
Integer(int x=0){n=x;}
friend int sum(Integer &a,Integer &b);
};
int sum(Integer &a,Integer &b)
{return a.n+b.n;}
void main()
{
Integer x(25),y(37);
cout<<"求和结果为:"<<sum(x,y)<<endl;
}
有时候,不能将凡是访问类的私有成员的所有函数都作为该类的成员函数,为了解决这个问题,就要引入友元函数。
对于某一个函数,也许它是一个普通函数,也许它是类A的一个成员函数,总之,如果它不是类A的成员函数,它就无权访问类A的私有成员。但是如果它被声明为类A的一个友元函数,它就可以访问类A的私有成员。
例:友元类的使用
#include <iostream.h>
class Integer{
int n;
public:
Integer(int x=0){n=x;}
friend class Operation;
};
class Operation{
public:
int sum(Integer &x,Integer &y){return (x.n+y.n);}
int difference(Integer &x,Integer &y){return (x.n-y.n);}
int product(Integer &x,Integer &y){return (x.n*y.n);}
int ratio(Integer &x,Integer &y){return (x.n/y.n);}
};
void main()
{
Integer x(38),y(12);
Operation z;
cout<<"sum(x,y)="<<z.sum(x,y)<<endl;
cout<<"difference(x,y)="<<z.difference(x,y)<<endl;
cout<<"product(x,y)="<<z.product(x,y)<<endl;
cout<<"ratio(x,y)="<<z.ratio(x,y)<<endl;
}
二十七、友元的使用
对友元函数的使用,和普通函数的使用方法一样,不需要在友元函数前面加上特殊标志。但如果该友元函数是一个类的成员函数,则使用时还是要在友元函数前面加上自己的类名。
例:
#include <iostream>
using namespace std;
class Trigon{
private:
float a,b,c;
public:
Trigon(float x,float y,float z)
{a=x;b=y;c=z;}//构造函数;
friend class S;//将类S声明为其友元类;
};
class S{
public:
float max(Trigon &);
float average(Trigon &);
float sum(Trigon &);
};
//编写S的成员函数:
float S::sum(Trigon & tri)
{return tri.a+tri.b+tri.c;}
float S::average(Trigon &tri)
{return (tri.a+tri.b+tri.c)/3;}
float S::max(Trigon &tri)
{
float x=tri.a;
if(tri.b>x) x=tri.b;
if(tri.c>x) x=tri.c;
return x;
}
int main()
{
Trigon tri2(4,5,6);
S t1;
cout<<"周长:"<<t1.sum(tri2)<<endl;
cout<<"平均边长:"<<t1.average(tri2)<<endl;
cout<<"最长边:" <<t1.max(tri2)<<endl;
system("pause");
return 0;
}
二十八、运算符重载
在设计一个新的类时,其实是将一个新的数据类型引入到了C++中,对于新的数据类型的操作需要重新定义,而不能直接应用一些系统预先定义好的操作符。
对新的数据类型仍然使用已有的一些运算符进行操作,可以将运算符重载为类的成员函数或是友元函数。
例:
class Complex{
float a;//复数的实部;
public:
Complex(float x=0,float y=0)
{
a=x;b=y;
}
};
Complex one,two;
对于上述复数类的两个对象one和two,不能直接用“one+two”来表示两个复数的相加。但为了人们的习惯,有进希望对新的数据类型仍然使用已有的一些运算符进行操作,如果希望用“+”来表示两个复数的加法,这就需要对运算符“+”进行重载。
只有类的成员和类的友元函数才能访问类的私有数据成员,因此只有将运算符重载为类的成员函数或是类的友元函数时,才能使被重载的运算符起到操作新的数据类型的目的。
运算符重载规则:
(1)被重载的运算符一定不能是下面这些运算符中的一个:
“.”类成员运算符;“*”指针运算符;“::”类作用域运算符;“?:”条件运算符。
(2)运算符被重载后,不能改变优先级和结合性,也不能改变语法结构,即不能将单目运算符重载为双目运算符。
(3)被重载的运算符必须是系统预先已经定义好的运算符,即不能自己定义新的运算符。
(4)被重载的运算虽然可以用来做任何事情,但是最好还是应使其新的功能与系统预先定义的功能相似,以便使人容易理解。
1、重载为成员函数
将运算符重载为成员函数的格式为:
<类名>::operator<运算符><(参数表)>;
类的非静态成员函数都隐含有表示自己的this指针(而友元函数则没有),因此被重载的运算符,作为类的成员函数也隐含了一个this指针,这样一来,对于单目运算符可以不写参数,而双目运算符可以只定一个参数,另一个参数就是自己this,也就是说双目运算符的左操作数是this对象。
例:(vc环境下)
#include <iostream>
using namespace std;
class clock{
int h;//小时;
int m;//分钟;
int s;//秒;
public:
clock operator +(clock &);//重载运算符“+”;
void display();
clock(int ,int,int );
clock(clock &);//复制构造函数;
};
clock::clock(int x,int y,int z)
{
h=x;m=y;s=z;
}
clock::clock(clock &c)
{
h=c.h;m=c.m;s=c.s;
}
void clock::display()
{cout<<h<<":"<<m<<":"<<s;}
//重载运算符:
clock clock::operator + (clock &c)
{
clock clk=*this;//指向自己;
int cs=0,cm=0;
clk.s+=c.s;//秒相加;
cs=clk.s/60;//秒相加后大于60就取模60;
clk.s%=60;//秒相加后的进位;
clk.m+=c.m+cs;//分相加;
cm=clk.m/60;//分相加后大于60就取模60;
clk.m%=60;// 分相加后进位;
clk.h+=cm+c.h;//时相加;
clk.h%=24;//时相加后取模24;
return clk;//返回clock类的clk对象的值;
}
int main()
{
clock one(12,12,12),two(12,40,55);
clock three=one+two;
one.display();
cout<<"+";
two.display();
cout<<"=";
three.display();
system("pause");
return 0;
}
2、重载为友元函数
由于被重载为类成员函数的运算符隐含this指针,而被重载为类的友元函数的运算符没有隐含this指针,因此用成员函数方式重载的运算符可以比用友元函数方式重载的运算符少输入一个参数,实就使得前一种方法的效率高于后者,但是后一种方法也有它自身的优点,那就是它允许被重载的运算符的左操作数为一个常数,而前一种方法是办不到的。
将运算符重载为类友元函数的格式:
friend<函数返回类型>operator<运算符><(参数表)>;
例:
#include <iostream.h>
class clock{
int h;//小时;
int m;//分钟;
int s;//秒;
public:
void display();
clock(int ,int,int );
clock(clock &);//复制构造函数;
friend clock operator + (int,clock &);//将"+"重载为友元函数,第2个参数是clock;
friend clock operator + (clock &,int);//将"+"重载为友元函数,第1个参数是clock;
};
clock::clock(int x,int y,int z)
{
h=x;m=y;s=z;
}
clock::clock(clock &c)
{
h=c.h;m=c.m;s=c.s;
}
void clock::display()
{cout<<h<<":"<<m<<":"<<s;}
//重载运算符:
clock operator + (int x,clock &c)
{
clock clk=c;
clk.h+=x;
clk.h%=24;
if(clk.h<0) clk.h+=24;
return clk;
}
clock operator + (clock &c,int x)
{
clock clk=c;
clk.h+=x;
clk.h%=24;
if(clk.h<0)clk.h+=24;
return clk;
}
int main()
{
clock one(5,30,0);
cout<<"现在时间:";
one.display();
clock two=4+one;
cout<<"4个小时以后将是:";
two.display();
clock three=one+(-8);
cout<<"8个小时前是:";
three.display();
return 0;
}
被重载为类的成员函数的运算符其实是将其左操作数固定为成员函数的this指针,而被重载为类的友元函数的运算符的左操作数则根据定义,可以为常数或者其他类型的参数。
统合典例:
#include<iostream.h>
class CK{
private:
float cunkuan;
static int number;//存款人数定义为静态数据成员;
static float money;//当前存款总额定义为静态数据成员;
public:
static void dispMoney();//当前存款总数;
static void dispNumber();//当前存款总人数;
CK(float blc);//构造函数;
void outMoney(float outm);
void inMoney(float inm);
~CK();
};
CK::CK(float blc)
{
cout<<"开户!存入¥"<<blc<<endl;
cunkuan=blc;
number++;
money+=blc;
}
CK::~CK()
{
cout<<"销户!"<<endl;
money-=cunkuan;
number--;
}
void CK::inMoney(float inm)
{
cout<<"存入¥"<<inm<<endl;
cunkuan+=inm;
money+=inm;
}
void CK::outMoney(float outm)
{
if(outm>cunkuan)
{cout<<"对不起!你的存款只有¥"<<cunkuan<<"。"<<endl;
return ;}
cunkuan-=outm;
money-=outm;
cout<<"取出¥"<<outm<<endl;
}
void CK::dispNumber()
{
cout<<"当前共有存款人:"<<number<<"个。"<<endl;
}
void CK::dispMoney()
{
cout<<"当前共有存款¥"<<money<<"。"<<endl;
}
/**********初始化静态变量****************/
int CK::number=0;
float CK::money=0;
/**************主函数*******************/
void main()
{
CK saver1(5000.0),saver2(8000.0),saver3(200.0);
CK::dispNumber();
CK::dispMoney();
saver1.inMoney(500);
saver2.outMoney(300);
saver3.outMoney(200);
saver1.inMoney(2000);
CK::dispNumber();
CK::dispMoney();
}
二十九、虚函数
虚函数是动态联编的基础。虚函数是成员函数,而且是非静态的成员函数。如果某类中的一个成员函数被说明为虚函数,这就意味着该成员函数在派生类中可能有不同的实现。
虚函数的定义方法:
virtual<函数返回类型><虚函数名称>(<参数列表>)
定义虚函数要遵循以下规定:
(1) 类的静态成员函数不可以定义为虚函数;
(2) 类的构造函数不可以定义为虚函数;
(3) 非类的函数不可以定义为虚函数;
例:
#include<iostream>
using namespace std;
class Person{
public:
virtual void work();
Person(){cout<<"run Person class!"<<endl;}
};
class Docter:public Person{
public:
void work();
Docter(){cout<<"run Docter class!"<<endl;}
};
class Teacher:public Person{
public :
void work();
Teacher(){cout<<"run Teacher class!"<<endl;}
};
class Peasant:public Person{
public:
void work();
Peasant(){cout<<"run Peasant class!"<<endl;}
};
//分别实现各个类的成员函数work():
void Person::work()
{cout<<"人人都要工作!"<<endl;}
void Docter::work()
{cout<<"医生工作是给病人看病!"<<endl;}
void Teacher::work()
{cout<<"老师的工作是给学生上课!"<<endl;}
void Peasant::work()
{cout<<"农民的工作……!"<<endl;}
/***********主函数*************/
int main()
{
Docter x1;
Teacher x2;
Peasant x3;
Person *p;
p=&x1;
p->work();
p=&x2;
p->work();
p=&x3;
p->work();
system("pause");
return 0;
}
三十、纯虚函数
纯虚函数只有函数的声明,但是并没有具体实现函数的功能。纯虚函数没有函数体,具体功能要在派生类中实现。纯虚函数的声明格式:
virtual<函数类型><虚函数名称>(<参数列表>)=0;
纯虚函数为可以直接调用,也不可以被继承。
例:
#include<iostream.h>
#include<math.h>
class S{
public:
virtual void size()=0;
};
class SS:public S{
double x;
public:
void size();
SS(double n);
};
class FS:public S{
double b;
double a;
public:
void size();
FS(double m,double n);
};
//实现SS和FS的成员函数size():
SS::SS(double n)
{x=n;}
void SS::size()
{
cout<<"该数的大小为:"<<abs(x)<<endl;
}
FS::FS(double m,double n)
{a=m;b=n;}
void FS::size()
{cout<<"该数的大小为:"<<sqrt(a*a+b*b)<<endl;}
void main()
{
SS ss(-6.6);
FS fs(3.0,4.0);
ss.size();
fs.size();
}
三十一、抽象类
含有纯虚函数的类是抽象类。抽象类不能产生对象。
一般来说,就是将基类中的成员函数定义为纯虚函数,在基类中只给出函数的声明,而函数的具体实现则放在派生的子类中,这个基类就是抽象类。
抽象类是一种特殊的类,只能作为基类来使用,其纯虚函数的实现由派生类给出。抽象类不可以实例化,不可以作为函数的返回类型和函数的参数类型,如果一个派生类继承了抽象类,但是却没有重新定义抽象类中的纯虚函数,则该派生类仍然是抽象类。只有当派生类将基类中的所有的纯虚函数都实现的时候 ,它才不再是抽象类。
例:
#include<iostream.h>
class Figure{
public:
virtual double area()=0;
};
class Rect:public Figure{ //定义一个矩形的类;
float Px;
float Py;
public:
Rect(float x=0,float y=0);
double area();
};
class Circle:public Figure{
float Cx,Cy,r;
public:
Circle(float x=0,float y=0,float z=1);
double area();
};
double Rect::area()
{
double s=Px*Py;
cout<<"矩形面积是:"<<s<<endl;
return s;
}
Rect::Rect(float x,float y)
{
Px=x;Py=y;
}
double Circle::area()
{
cout<<"矩形面积是:"<<3.14*r*r<<endl;
return 3.14*r*r;
}
Circle::Circle(float x,float y,float z)
{Cx=x;Cy=y;r=z;}
int main()
{
Rect p(5,7);
p.area();
Circle c(4,4,6);
c.area();
cout<<"-----------"<<endl;
Figure *f;
f=&p;
f->area();
f=&c;
f->area();
}
三十二、多态
同一消息为不同的对象接受时可产生完全不同的行动,这种现象称为多态性。利用多态性用户可发送一个通用的信息,而将所有的实现细节都留给接受消息的对象自行决定,如是,同一消息即可调用不同的方法。
从广义上讲,多态是指一段程序能够处理多种类型对象能力。在C++语言中,这种多态性可以通过强制多态、重载多态、类型参数化多态、包含多态等多种形式来实现。
多态性是指某类对象在接受同样的消息时,所做出的响应不同。它有两种不同的形式:
(1)、编译时多态性,通过重载来实现(包括函数重载和运算重载),它在编译器对源程序进行编译时就确定了所调用的函数。
(2)、运行时多态性,是指在程序运行过程中,会根据具体情况来确定调用的是哪一个函数。运行时多态性通过继承和虚函数来实现的。
多态就是通过类的继承,使得同一个函数可以根据调用它的对象的类型不同作出不同的响应。它与继承和重载共同构成了面向对象的三大编程特性。
多态是通过虚函数来实现的,虚函数的使用本质就是将派生类类型的指针赋给基类类型的指针,虚函数被调用进会动态的判断调用对象的类型,从而给出相应的响应。
例:
#include<iostream.h>
class Vehicle {
public:
virtual void drive()=0;//定义一个纯虚函数;
};
class Car:public Vehicle
{
public:
void drive();
};
class Truck:public Vehicle
{
public:
void drive();
};
//编写函数类的成员函数drive():
void Car::drive()
{
cout<<"启动轿车!"<<endl;
}
void Truck::drive()
{
cout<<"启动卡车!"<<endl;
}
void driver(Vehicle * v)//注意与drive的区别;
{
v->drive();
}
/***********************/
int main()
{
Car c;
c.drive();
Truck t;
t.drive();
cout<<"----------"<<endl;
Vehicle * v1;
v1=&c;
driver(v1);
v1=&t;
driver(v1);
return 0;
}
三十三、输入输出流的概念
endl是end line的缩写,表示行结束,输出endl有两个目的,一个是可以起到换行 的作用,另外一个目的是刷新输出缓冲区,可以确保用户立即看到输出信息。
三十四、输入输出类库
类istream和ostream都是通过单一继承从基类ios派生而来的。iostream类是由istream类和ostream类联合派生出来的,由于istream类主要支持输入操作,ostream类主要支持输出操作,所以istream灰同时支持输入输出操作。
cerr是类ostream的对象,方便把程序的出错消息迅速地在屏幕上显示出来。
三十五、文件的输入输出类
在fstream.h文件中主要定义了3个类来完成对文件的各种输入输出操作,它们分别是ifstream、ofstream、fstream分别对应输入、输出和输入输出3类。
创建文件流对象格式:
<文件流><文件对象>(<文件名>,<存取模式>);
文件流可以是ifstream、ofstream或fstream
文件对象是创建的文件流对象名;
文件名是磁盘上的文件名称(包含文件路径,路径应使用双斜杠“\\”)。
存取模式是文件操作模式的标志。一般常用的操作模式有:
ios::in //只读模式;
ios::out //只写模式;
ios::app //追加文件模式;
这些模式可以使用“|”符号相连,表示同时设置几种模式。
对已打开的文件的读写完成后,应关闭些文件,关闭文件用成员函数close(),close()是一个没有参数且无须指定返回值的函数。
三十六、cout流对象
cout是console output的缩写,意思是从控制台,即显示器上面显示数据,当使用cout<<输出基本类型的数据进,完全不需要考虑输出数据的类型。这是因为运算符<<是重载的。
三十七、cout的成员函数
成员函数 put()和write()的作用是向输出流中插入字符或字符串,作用与插入运算符<<灰似。
函数put()是向输出流中插入单个字符。
例:
#include<iostream>
void main()
{
cout.put(‘a’);
cout.put(‘b’);
cout.flush();//刷新输出流中的数据,便其立刻在屏幕上显示出来。
}
运行结果:
ab
函数write()用于向流中插入一个字符串,该字符串由第一个参数指定,插入字符串的长度由第二个参数指定。
例:
void main()
{
char *str=”Hello World!”;
cout.write(str,5);
coutflush();
}
运行结果:
Hello
三十八、cerr流对象
cerr与标准输出流cout的用法差不多,只有一点不同,cerr只能在显示器上显示输出数据,而cout还可以将数据输出到磁盘文件,通常在调试程序的时候 ,如果需要及时地在屏幕上显示出错信息,则需要使用 cerr。cerr流中的信息是根据用户自己的需要指定的。
cout的输出可以重定向到一个文件中,而cerr必须输出在显示器上。
例:
using namespace std;
int main()
{
cout << "hello world---cout" << endl ;
cerr << "hello world---cerr" << endl ;
return 0;
}
三十九、按指定格式输出数据
1、设置输出进制
在屏幕上输出一个整数时,默认情况下是以十进制方式进行显示的,如果想以其他的进制显示数据,就需要调用成员函数setf或直接利用操作符oct、hex和dec来实现。
例:(在VC环境下)
#include <iostream.h>
int main()
{
int a=100;
cout.setf(ios::showbase);
cout.setf(ios::oct);//设置输出方式为八进制;
cout<<"a="<<a<<endl;
cout.setf(ios::hex);//设置输出方式为十六进制;
cout<<"a="<<a<<endl;
cout.setf(ios::dec,ios::basefield);//将输出方式设置为十进制;
cout<<"a="<<a<<endl;
int b=200;
//直接利用格式控制符进行不同进制的输出:
cout<<"b="<<oct<<b<<endl; //八进制;
cout<<"b="<<hex<<b<<endl; // 十六进制;
cout<<"b="<<dec<<b<<endl; // 十进制;
return 0;
}
2、设置浮点数精度
系统默认的情况下,每个浮点数的输出精度是6位,要想改变输出精度,可以通过成员函数precision(int)来实现,而不带参数的precision()会返回当前的精度值。
例:(VC环境下)
#include <iostream>
#include<math.h>
using namespace std;
int main()
{
double f=sqrt(5.0);
cout<<f<<endl;//按默认精度(6位)输出f;
cout.precision(10);//将当前精度改为10;
cout<<f<<endl;
return 0;
}
3、设置输出宽度
使用成员函数width(int)可调整输出数据的宽度,默认是右对齐方式。域宽设置仅对下一行的流插入有效,在一次操作完成之后,域宽又被置回0。
例:
#include <iostream.h>
int main()
{
int value=1;
for(int i=1;i<5;i++)
{
cout.width(10);
cout<<value<<endl;
value*=10;
}
return 0;
}
输出结果:
注意:当输出的数据不能达到指定的输出宽度时,默认使用空格填充剩余的部分。如果超出了指定的输出宽度,width函数也不会截断数值,会将其全部显示出来。
4、设置填充字符
作用成员函数fillI(int)设置填充的字符,其参数为需要填充的字符。
例:
#include <iostream.h>
int main()
{
int value=1;
cout.fill('*');
for(int i=1;i<5;i++)
{
cout.width(10);
cout<<value<<endl;
value*=10;
}
return 0;
}
输出结果:
5、设置对齐方式
调用成员函数setf(Flags),如果Flags为right标志可以使用输出域右对齐并把填充字符放在输出数据的左边。left标志可以使输出域左对齐并把填充字符放在输出数据的右边。
例:
#include <iostream.h>
int main()
{
int value=1;
cout.setf(ios::left);
cout.fill('*');
for(int i=1;i<5;i++)
{
cout.width(10);
cout<<value<<endl;
value*=10;
}
return 0;
}
6、浮点数按科学计数法显示
调用成员函数setf(),设置scientific标志使输出的浮点数按照科学计数法的形式进行显示,设置fixed标志使输出的浮点数按照定点的方式进行显示。如果不进行设置,则由浮点数的数值自动决定输出格式。
例:
#include <iostream.h>
int main()
{
double x=0.00123456,y=1.245e9;
cout<<"displayed in default format:\n"
<<x<<'\t'<<y<<'\n';
cout.setf(ios::scientific,ios::floatfield);
cout<<"displayed in scientific format:\n"
<<x<<'\t'<<y<<'\n';
cout.unsetf(ios::scientific);//取消格式设置;
cout<<"displayed in default format after unsetf:\n"
<<x<<'\t'<<y<<'\n';
cout.setf(ios::fixed,ios::floatfield);
cout<<"displayed in fixed format:\n"
<<x<<'\t'<<y<<'\n';
return 0;
}
输出结果:
如果要取消某个格式设置,可以使用成员函数unsetf(long Flags)。不同标志符之间可以用OR(|)进行组合设置。
四十、输入流
一般情况下,使用标准输入对象cin和运算符“>>”实现输入操作。若按空格或Enter键表示一次输入的结束。
利用cin可以输入一个连续无间断的字符串。
例:
#include<iostream>
using namespce std;
int main()
{
char buf[30];
cin>>buf;
cout<<”buf=”<<buf<<endl;
}
若输入“My name is ”,buf只得到了第一个单词,这是因为输入机制是通过寻找空格或回车来区分输入的,而My的后面有空格,所以“My name is ”被截断了。要解决这个问题要使用成员函数get()和getline()。
四十一、cin的成员函数get()和getline()
cin的成员函数get()和getline()这两个函数都有3个参数:指向目标缓冲区的指针,缓冲区的大小(不能溢出)和终止符。终止符的默认值是“\n”。一般情况下默认第三个参数用“\n”所以不用写。
例:
#include<iostream>
void main()
{
char buf[30];
cin.get(buf,30);//接收输入最多29个字符,每个汉字是2个字符;
cout<<buf<<endl;
cin.get(buf,30);
cout<<buf<<endl;
}
它可以接收事先语句,但是,程序没给出第二次输入的机会。这是因为get()遇到输入流的终止符进就会停止,它并不会从输入流中提取终止符。要解决这个问题使用getline()函数。
getline()函数读到终止符时会将其提取出来,所以它程序允许多次输入。但是并不会反它存进结果缓冲区。get()与getline()函数在读到终止符后,都会将“0”存入结果缓冲区中,作为输入的终止。一般情况下,使用getline(),而不是get()函数。
四十二、不同进制下的输入
利用格式操作符oct(八进制)、hex(十六进制)和dec(十进制)可以得到不同进制下的输入,其使用方法与cout非常类似。
例:
#include<iostream>
void main()
{
int i;
cout<<”请输入一个十六进制数:”;
cin>>hex>>i;
cout<<”换算成十进制为:”<<i<<endl;
cin>>oct; // 设置当前输入进制为八进制;
cout<<”请输入一个八进制数字:”;
cin>>i;
cout<<”换算成十进制为:”<<i<<endl;
}
注意:一旦使用cin>>oct;将当前输入进制设置为八进制,则下面的所有输入全部按照八进制进制读入数据,直到使用cin>>dec将当前输入进制设置回十进制。
四十三、文件的打开和关闭
在C++中使用输入输出流对象进行操作,要打开一个文件,首先要做的是创建一个文件流对象,并与指定的磁盘文件建立关联。
C++中的文件流分为输入、输出和输入输出3类,分别对应ifstream、ofstream和fstream三个文件流类。创建需包含头文件fstream.h,用对应的文件流类声明实例对象,再执行相关的读写操作。
打开文件就是把流与文件相连接,关闭文件就是切断这一连接。
1、创建文件流对象
格式:<文件流><文件对象>(<文件名>,<存取模式>);
文件流可以是ifstream、ofstream或fstream;
文件对象是创建文件流对象名;
文件名是磁盘上的文件名称(包含文件路径,路径应使用双斜杠“\\”);
存取模式是文件操作模式的标志。一般常用的操作模式有:
ios::in //只读文件模式;
ios::out //只写文件模式;
ios::app //追加文件模式;
这些标志可以使用“|”符号相连接,表示同进设置几种模式。
对已打开的文件的读写完成之后,应关闭此文件,关闭文件用成员函数close(),close()是一个没有参数且无须指定返回值的函数。
例:
#include <iostream.h>
#include<fstream.h>
int main()
{
int buffer[10];
//定义ofile输出流对象,并打开路径c:\temp下在samle.txt文件,设置打开方式为输出方式;
ofstream ofile("c:\\temp\\samle.txt",ios::out);
{
//如果文件不能正常打开,则显示出错提示
cerr<<"open sample.txt is failed!"<<endl;
return 1;
}
cout<<"input 10 numbers:"<<endl;
for(int i=0;i<10;i++)
{
cin>>buffer[i];
ofile<<buffer[i]<<" ";
}
ofile.close();
return 0;
}
需要注意的几个点:
(1)、声明ofile的同时,如果指定的文件不存在,便会创建这个文件作为输出之用;如果指定的文件已经存在,这个文件会被打开作为输出之用,并且清空这个文件的原有内容;
(2)、如果文件已经存在,又不希望丢弃其中原有的内容,而是希望添加新数据到文件中,那么必须使用追加模式打开文件,比如下列代码:
ofstream ofile(“c:\\temp\\sample.txt”,ios::app);
(3)、如果需要对文件进行读取操作,那么必须设置既读又写模式,例如:
ofstream ofile(“c:\\temp\\sample.txt”,ios::in|ios::out);
(4)、文件有可能打开失败,在进行写入操作之前,必须确定文件已经成功打开,最简单的办法就是检验文件类对象的真假,如果文件未能成功打开,ofstream 的对象ofile会返回false值,在这种情况下,可以显示错误信息,提醒用户。
if( !ofile ) { cerr<<"open sample.txt is failed!"<<endl; }
(5)、打开文件时如果不指明存取模式,则默认为只读文件模式,例如:
ofstream ofile(“c:\\temp\\sample.txt”);
2、使用文件流成员函数open()
可以用open()函数来打开一个文件,其调用格式如下:
<文件流对象>(<文件名>,<存取方式>);
fstream file_object;
file_object.open(file_name,access_mode);
各个参数的含义与创建文件流方式时间相同。
可以用下面格式打开使用infile.txt文件:
ifstream ifile;
ifile.open(“infile.txt”,ios::in);
除了ios::in外还有其他的存取方式标志位如下表:
名称 | 含义 |
ios::in | 以只读方式打开文件(用ifstream创建对象时默认) |
ios::out | 以只写方式打开文件(当用一个没有ios::app、ios::ate或ios::in的ofstream时,ios::trnuc是默认设置) |
ios::app | 以追加方式打开文件,即写在文件尾部 |
ios::ate | 打开一个现成的文件(无论输入还是输出)并寻找末尾 |
ios::binary | 以二进制打开文件,默认为文本方式 |
ios::nocreate | 如果文件不存在,打开操作失败(仅打开存在的文件) |
ios::noreplace | 如果文件存在,打开操作失败(仅打开不存在的文件) |
ios::trunc | 如果文件已存在,则将其长度截为0,并清除原来的内容 |
四十四、文件的读写
一个文件被打开就与对应的文件流连接起来了。这时对文件的读操作,就是从流中提取元素,对文件的写操作就是向流中插入元素。
文件的打开模式分为文本模式和二进制模式。
1、文本模式
文本模式打开文件,操作其对应的文件流的方式与操作一般输入输出流相类似。
例:
#include <iostream.h>
#include<fstream.h>
int main()
{
int buffer[10];
//定义ofile输出流对象,并打开路径c:\temp下在samle.txt文件:
ofstream ofile("c:\\temp\\samle.txt");
if(!ofile)
{
cerr<<"open sample.txt is failed!"<<endl;
}
else
{
ofile<<"姓名\t"<<"年龄\t"<<"性别\t"<<endl;
ofile<<"赵易\t"<<"21\t"<<"男\t"<<endl;
ofile<<"钱耳\t"<<"25\t"<<"女\t"<<endl;
ofile<<"孙伞\t"<<"26\t"<<"男\t"<<endl;
ofile<<"李思\t"<<"22\t"<<"女\t"<<endl;
}
ofile.close();
return 0;
}
例:向文本文件中先写入特定字符串,再从文件读出并输出。
#include <iostream.h>
#include<fstream.h>
int main()
{
ofstream ofile;
ifstream ifile;
char buf1[30],buf2[30];
ofile.open("c:\\temp\\abc.txt");//打开文件,写入字符串:
ofile<<"Hello file!";
ofile<<"\nHi Boy and Girl!";
ofile.close();
ifile.open("c:\\temp\\abc.txt");
ifile.getline(buf1,30);
ifile.getline(buf2,30);
cout<<buf1<<endl;
cout<<buf2<<endl;
ifile.close();
return 0;
}
2、二进制模式
以二进制方式打开的文件,对它的读写操作与文本有所不同。程序必须按照数据在内存或磁盘中的存放格式一个字节一个字节地读取或写入。这需要使用read()函数和write()函数。
例:以二进制方式将员工的工资信息写入文件:
#include <iostream.h>
#include<fstream.h>
struct person{
char name[20];
double salary;
};
int main()
{
ofstream ofile("c:\\temp\\person.txt",ios::binary);//以二进制打开文件;
person emp1={"李思",1200};
person emp2={"王强",1000};
if(!ofile)//如果打开文件错误则退出程序;
{cerr<<"打开文件错误!"<<endl;return;}
ofile.write((char *)&emp1,sizeof(emp1));//将李思的信息写入文件;
ofile.write((char *)&emp2,sizeof(emp2));//将王强的信息写入文件;
ofile.close();
}
由于write函数只能写字符串,所以,对于其他类型的数据,必须先以“(char *)+数据地址”的形式将它们转变为字符串,通过sizeof()函数可以得到数据的长度。
读取员工数据时,在事先不知道文件中存储了几个员工的数据情况下,通常是采用一个循环结构,不断地读取下一个员工的信息,直到读取到文件的末尾为止。判断是否读到文件的末尾通常是调用eof()函数,如果读到末尾,该函数返回一个非零值,没有则返回零值。
例:循环读取文件中员工的信息,直到文件结束:
#include <iostream.h>
#include<fstream.h>
struct person{
char name[20];
double salary;
};
int main()
{
person emp;
ifstream ifile("c:\\temp\\person.txt",ios::binary);
while(true)
{
ifile.read((char *)&emp,sizeof(emp));//从文件中读取员工数据;
if(ifile.eof()) break;//判断是否读到结尾;
cout<<"职工姓名:"<<emp.name<<endl;
cout<<"工 资:"<<emp.salary<<endl<<endl;
}
ifile.close();
return 0;
}
四十五、文件的随机读写
在一般情况下,以读方式打开文件时,文件指针总是指向文件的开头;以写方式打开文件时,文件指针总是指向文件的结尾。当读文件时,每读一个字节,文件指针就向后移动一个字符的位置;写文件时每写一个字符后,文件指针就移动到文件的结尾。一个文件实际上有两个指针,一个用于读,一个用于写。因此,函数分为对应于istream类和ostream类的两套版本。
1、文件指针相对移动函数
在istream类和ostream类中分别定义了不同的相对指针移动函数seekg()和seekp()来执行。它们的定义如下:
istream& istream::seekg(streamoff off,seek_dir dir);
ostream& ostream::seekp(streamoff off,seek_dir dir);
参数off 是相对于参照位置的偏移量,为正就是往文件尾部移动,为负应时往文件头部移动。off被定义为streamoff类型,实际上就是long类型。
参数dir是文件指针相对移动的参照位置,如下表,共有 3种情况,在ios中被定义为一个枚举类型seek_dir。
名 称 | 含 义 |
ios::beg | 文件头部 |
ios::end | 文件尾部 |
ios::cur | 当前文件指针的位置 |
infile.seekg(2,ios::cur) //文件指针从当前位置向文件尾部移动2个字节;
infile.seekg(-3,ios::end) //文件指针从文件尾部向文件头部移动3个字节;
outfile.seekp(0,ios::beg) //文件指针移动到文件头部;
例:从文件的相对位置读取字符:
#include <iostream.h>
#include<fstream.h>
int main()
{
char strBuffer[]="I am student.";
//建立一个文本文件:
ofstream ofile("c:\\temp\\goal.txt");
ofile<<strBuffer;//向文件中写入字符;
ofile.close();
char ch;
ifstream ifile("c:\\temp\\goal.txt");
ifile.seekg(-3,ios::end);
ifile.get(ch);
cout<<ch<<endl;
ifile.seekg(2,ios::beg);
ifile.get(ch);
cout<<ch<<endl;
ifile.seekg(2,ios::cur);
ifile.get(ch);
cout<<ch<<endl;
return 0;
}
2、文件指针定位函数
对应于istream类和ostream类的指针定位函数分别是tellg()和tellp()。其定义如下:
streampos istream::tellg();
streampos ostream::tellp();
它们返回文件指针的当前位置,返回类型为streampos,等同于long类型。
streampos inpos,outpost;
inpos=infile.tellg();
outpos=outfile.tellp();
例:显示读取文件时,文件指针的移动位置:
#include <iostream.h>
#include<fstream.h>
int main()
{
char strBuffer[]="I am student.";
//建立一个文本文件:
ofstream ofile("c:\\temp\\goal.txt");
ofile<<strBuffer;//向文件中写入字符;
ofile.close();
char ch;
streampos pos;
ifstream ifile("c:\\temp\\goal.txt");
pos=ifile.tellg();
cout<<"当前文件指针位置:"<<pos<<endl;
ifile.get(ch);
cout<<"得到的字符:"<<ch<<endl;
pos=ifile.tellg();
cout<<"读取一个字符后的文件指针的位置:"<<pos<<endl;
return 0;
}
3、文件指针绝对移动函数
文件指针的绝对移动函数可以将指针移动到指定的绝对地址上。对应于istream类和ostream类的成员函数是seekg()和seekp()函数原型如下:
istream& istream::seekg(streampos pos);
ostream& ostream::seekp(streampos pos);
例:分别读出第4个和第6个字符:
#include <iostream.h>
#include<fstream.h>
int main()
{
char strBuffer[]="I am student.";
//建立一个文本文件:
ofstream ofile("c:\\temp\\goal.txt");
ofile<<strBuffer;//向文件中写入字符;
ofile.close();
char ch;
streampos pos;
ifstream ifile("c:\\temp\\goal.txt");
pos=3;
ifile.seekg(pos);//将文件指针移动到第4个字符的位置;
ifile.get(ch);//读取一个字符;
cout<<"文件中第4个字符为:"<<ch<<endl;
pos=5;
ifile.seekg(pos);//将文件指针移动到第6个字符的位置;
ifile.get(ch);//读取一个字符;
cout<<"文件中第5个字符为:"<<ch<<endl;
return 0;
}
四十六、模板
……………………
模板也叫做参数化的数据类型,有函数模板和类模板两种。
函数模板的使用,使得程序能够用不同类型的参数调用相同的函数;
类模板的使用,使得程序可以声明模板的多个不同类型的对象,这大大缩短了程序的长度,在某种程序上也增加了程序的灵活性。在使用模板时程序员不必关心所使用的每个对象的类型,而只要集中精力到程序的算法上面。虽然模板有着强大的功能,但是想要用好它也需要有丰富的编程经验,否则将会对程序的结构和执行效率带来负面的影响。
……………………
四十七、异常处理
异常处理是C++的一个特点,它能够在检测到程序的运行错误后,终止程序,并按事先指定的方法对错误进行处理,当异常被处理完毕后,程序会被重新激活,并在异常处理点继续执行下去。选择异常处理和策略通常和实际的应用联系在一起,有些时候,程序给出错误信息,有时要求用户重新输入和选择。
一般而言,C++的异常处理可以分为两大部分:一是异常的识别和发出,二是异常的捕捉与处理。语法结构如下:
class <异常标志>{}
try
{
…
throw(<异常标志>) //抛出异常
…
}
catch(<异常标志>) //捕捉异常
{
… //处理异常
}
如果在try()程序内发现异常,则由throw(异常)语句抛出异常,catch(异常)语句负责捕捉异常,当异常被捕捉之后,catch{}程序块仙的程序则进行异常的处理。
在这里throw(异常)语句所抛出的异常其实是某种对象,是用来识别异常的。
在try{}程序块中可能根据不同的错误情况抛出不同的异常,当需要处理多种异常的时候,只要增加相应的catch()程序块即可。
例:
#include<iostream.h>
class S{};
class L{};
void main()
{
int a,b;
cout<<endl<<"请输入矩形长和宽: ";
cin>>a;
cin>>b;
try{
if(a*b<10)throw S();//抛出异常;
else if(a*b>100)throw L();//抛出异常;
else cout<<endl<<"长为:"<<a
<<",宽为:"<<b<<"的矩形面积为"<<a*b<<"。";
}
catch(S){cout<<endl<<"矩形面积不能小于10!"<<endl;}
catch(L){cout<<endl<<"矩形的面积不能大于100!"<<endl;}
}
/**************************************************************************/
/**************************************************************************/
以下为补充资料(知识点的补充)
/**************************************************************************/
/**************************************************************************/
四十八、面向对象
面向对象,就是把同类问题打包了,随时调用
面向过程,就是一步步执行,不能打乱执行先后。
面对对象就是:
把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。对同类对象抽象出其共性,形成类。类中的大多数数据,只能用本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。程序流程由用户在使用中决定。
所谓的面向对象就是在做程序时,把所要操作的东东实例化为对象操作,每类对象都有自己接口函数,使用接口函数便可以调用该对象的各种方法与属性,就涂个方便。
面向过程就是:
自顶向下顺序执行,逐步求精;其程序结构是按功能划分为若干个基本模块,这些模块形成一个树状结构;各模块之间的关系尽可能简单,在功能上相对独立;每一模块内部均是由顺序、选择和循环三种基本结构组成;其模块化实现的具体方法是使用子程序。程序流程在写程序时就已决定。
面向对象的基本概念如下:
对象:对象是要研究的任何事物。从一本书到一家图书馆,单的整数到整数列庞大的数据库、极其复杂的自动化工厂、航天飞机都可看作对象,它不仅能表示有形的实体,也能表示无形的(抽象的)规则、计划或事件。对象由数据(描述事物的属性)和作用于数据的操作(体现事物的行为)构成一独立整体。从程序设计者来看,对象是一个程序模块,从用户来看,对象为他们提供所希望的行为。在对内的操作通常称为方法。
类:类是对象的模板。即类是对一组有相同数据和相同操作的对象的定义,一个类所包含的方法和数据描述一组对象的共同属性和行为。类是在对象之上的抽象,对象则是类的具体化,是类的实例。类可有其子类,也可有其它类,形成类层次结构。
消息:消息是对象之间进行通信的一种规格说明。一般它由三部分组成:接收消息的对象、消息名及实际变元。
面向对象主要特征:
封装性:封装是一种信息隐蔽技术,它体现于类的说明,是对象的重要特性。封装使数据和加工该数据的方法(函数)封装为一个整体,以实现独立性很强的模块,使得用户只能见到对象的外特性(对象能接受哪些消息,具有那些处理能力),而对象的内特性(保存内部状态的私有数据和实现加工能力的算法)对用户是隐蔽的。封装的目的在于把对象的设计者和对象者的使用分开,使用者不必知晓行为实现的细节,只须用设计者提供的消息来访问该对象。
继承性:继承性是子类自动共享父类之间数据和方法的机制。它由类的派生功能体现。一个类直接继承其它类的全部描述,同时可修改和扩充。继承具有传递性。继承分为单继承(一个子类只有一父类)和多重继承(一个类有多个父类)。类的对象是各自封闭的,如果没继承性机制,则类对象中数据、方法就会出现大量重复。继承不仅支持系统的可重用性,而且还促进系统的可扩充性。
多态性:对象根据所接收的消息而做出动作。同一消息为不同的对象接受时可产生完全不同的行动,这种现象称为多态性。利用多态性用户可发送一个通用的信息,而将所有的实现细节都留给接受消息的对象自行决定,如是,同一消息即可调用不同的方法。例如:Print消息被发送给一图或表时调用的打印方法与将同样的Print消息发送给一正文文件而调用的打印方法会完全不同。多态性的实现受到继承性的支持,利用类继承的层次关系,把具有通用功能的协议存放在类层次中尽可能高的地方,而将实现这一功能的不同方法置于较低层次,这样,在这些低层次上生成的对象就能给通用消息以不同的响应。在OOPL中可通过在派生类中重定义基类函数(定义为重载函数或虚函数)来实现多态性。
综上可知,在面对对象方法中,对象和传递消息分别表现事物及事物间相互联系的概念。类和继承是是适应人们一般思维方式的描述范式。方法是允许作用于该类对象上的各种操作。这种对象、类、消息和方法的程序设计范式的基本点在于对象的封装性和类的继承性。通过封装能将对象的定义和对象的实现分开,通过继承能体现类与类之间的关系,以及由此带来的动态联编和实体的多态性,从而构成了面向对象的基本特征。
面向对象设计是一种把面向对象的思想应用于软件开发过程中,指导开发活动的系统方法,是建立在“对象”概念基础上的方法学。对象是由数据和容许的操作组成的封装体,与客观实体有直接对应关系,一个对象类定义了具有相似性质的一组对象。而每继承性是对具有层次关系的类的属性和操作进行共享的一种方式。所谓面向对象就是基于对象概念,以对象为中心,以类和继承为构造机制,来认识、理解、刻画客观世界和设计、构建相应的软件系统。。按照Bjarne STroustRUP的说法,面向对象的编程范式:
l 决定你要的类;
2 给每个类提供完整的一组操作;
3 明确地使用继承来表现共同点。
由这个定义,我们可以看出:面向对象设计就是“根据需求决定所需的类、类的操作以及类之间关联的过程”。
面向对象和基于对象的区别
很多人没有区分“面向对象”和“基于对象”两个不同的概念。面向对象的三大特点(封装,继承,多态)却一不可。通常“基于对象”是使用对象,但是无法利用现有的对象模板产生新的对象类型,继而产生新的对象,也就是说“基于对象”没有继承的特点。而“多态”表示为父类类型的子类对象实例,没有了继承的概念也就无从谈论“多态”。现在的很多流行技术都是基于对象的,它们使用一些封装好的对象,调用对象的方法,设置对象的属性。但是它们无法让程序员派生新对象类型。他们只能使用现有对象的方法和属性。所以当你判断一个新的技术是否是面向对象的时候,通常可以使用后两个特性来加以判断。“面向对象”和“基于对象”都实现了“封装”的概念,但是面向对象实现了“继承和多态”,而“基于对象”没有实现这些,的确很饶口。
从事面向对象编程的人按照分工来说,可以分为“类库的创建者”和“类库的使用者”。使用类库的人并不都是具备了面向对象思想的人,通常知道如何继承和派生新对象就可以使用类库了,然而我们的思维并没有真正的转过来,使用类库只是在形式上是面向对象,而实质上只是库函数的一种扩展。
面向对象是一种思想,是我们考虑事情的方法,通常表现为我们是将问题的解决按照过程方式来解决呢,还是将问题抽象为一个对象来解决它。很多情况下,我们会不知不觉的按照过程方式来解决它,而不是考虑将要解决问题抽象为对象去解决它。有些人打着面向对象的幌子,干着过程编程的勾当。
参考:
http://dict.youdao.com/wiki/_%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/#
四十九、面向对象程序设计
面向对象程序设计中的概念主要包括:对象、类、数据抽象、继承、动态绑定、数据封装、多态性、消息传递。通过这些概念面向对象的思想得到了具体的体现。
1)对象 对象是运行期的基本实体,它是一个封装了数据和操作这些数据的代码的逻辑实体。
2)类 类是具有相同类型的对象的抽象。一个对象所包含的所有数据和代码可以通过类来构造。
3)封装 封装是将数据和代码捆绑到一起,避免了外界的干扰和不确定性。对象的某些数据和代码可以是私有的,不能被外界访问,以此实现对数据和代码不同级别的访问权限。
4)继承 继承是让某个类型的对象获得另一个类型的对象的特征。通过继承可以实现代码的重用:从已存在的类派生出的一个新类将自动具有原来那个类的特性,同时,它还可以拥有自己的新特性。
5)多态 多态是指不同事物具有不同表现形式的能力。多态机制使具有不同内部结构的对象可以共享相同的外部接口,通过这种方式减少代码的复杂度。
6)动态绑定 绑定指的是将一个过程调用与相应代码链接起来的行为。动态绑定是指与给定的过程调用相关联的代码只有在运行期才可知的一种绑定,它是多态实现的具体形式。
7)消息传递 对象之间需要相互沟通,沟通的途径就是对象之间收发信息。消息内容包括接收消息的对象的标识,需要调用的函数的标识,以及必要的信息。消息传递的概念使得对现实世界的描述更容易。
8)方法 方法(Method)是定义一个类可以做的,但不一定会去做的事。
优点:
面向对象出现以前,结构化程序设计是程序设计的主流,结构化程序设计又称为面向过程的程序设计。在面向过程程序设计中,问题被看作一系列需要完成的任务,函数(在此泛指例程、函数、过程)用于完成这些任务,解决问题的焦点集中于函数。其中函数是面向过程的,即它关注如何根据规定的条件完成指定的任务。
在多函数程序中,许多重要的数据被放置在全局数据区,这样它们可以被所有的函数访问。每个函数都可以具有它们自己的局部数据。
这种结构很容易造成全局数据在无意中被其他函数改动,因而程序的正确性不易保证。面向对象程序设计的出发点之一就是弥补面向过程程序设计中的一些缺点:
对象是程序的基本元素,它将数据和操作紧密地连结在一起,并保护数据不会被外界的函数意外地改变。
比较面向对象程序设计和面向过程程序设计,还可以得到面向对象程序设计的其他优点:
1)数据抽象的概念可以在保持外部接口不变的情况下改变内部实现,从而减少甚至避免对外界的干扰;
2)通过继承大幅减少冗余的代码,并可以方便地扩展现有代码,提高编码效率,也减低了出错概率,降低软件维护的难度;
3)结合面向对象分析、面向对象设计,允许将问题域中的对象直接映射到程序中,减少软件开发过程中中间环节的转换过程;
4)通过对对象的辨别、划分可以将软件系统分割为若干相对为独立的部分,在一定程度上更便于控制软件复杂度;
5)以对象为中心的设计可以帮助开发人员从静态(属性)和动态(方法)两个方面把握问题,从而更好地实现系统;
6)通过对象的聚合、联合可以在保证封装与抽象的原则下实现对象在内在结构以及外在功能上的扩充,从而实现对象由低到高的升级。
五十、结构化程序设计
概念
结构化程序设计(structured programming)是进行以模块功能和处理过程设计为主的详细设计的基本原则。它的主要观点是采用自顶向下、逐步求精的程序设计方法;使用三种基本控制结构构造程序,任何程序都可由顺序、选择、循环三种基本控制结构构造。结构化程序设计主要强调的是程序的易读性。
详细描述处理过程常用三种工具:图形、表格和语言。
图形:程序流程图、N-S图、PAD图 表格:判定表
语言:过程设计语言(PDL)
结构化程序设计曾被称为软件发展中的第三个里程碑。该方法的要点是:
(1) 没有GOTO语句,在有资料里面说可以用,但要谨慎严格控制GOTO语句,仅在下列情形才可使用:
·用一个非结构化的程序设计语言去实现一个结构化的构造。
·在某种可以改善而不是损害程序可读性的情况下。
(2) 一个入口,一个出口;
(3) 自顶向下、逐步求精的分解;
(4) 主程序员组。
其中(1)、(2)是解决程序结构规范化问题;(3)是解决将大划小,将难化简的求解方法问题;(4)是解决软件开发的人员组织结构问题。
设计方法的原则
程序设计时,应先考虑总体,后考虑细节;先考虑全局目标,后考虑局部目标。不要一开始就过多追求众多的细节,先从最上层总目标开始设计,逐步使问题具体化。
对复杂问题,应设计一些子目标作为过渡,逐步细化。
一个复杂问题,肯定是由若干稍简单的问题构成。模块化是把程序要解决的总目标分解为子目标,再进一步分解为具体的小目标,把每一个小目标称为一个模块。
限制使用goto语句 结构化程序设计方法的起源来自对GOTO语句的认识和争论。肯定的结论是,在块和进程的非正常出口处往往需要用GOTO语句,使用GOTO语句会使程序执行效率较高;在合成程序目标时,GOTO语句往往是有用的,如返回语句用GOTO。否定的结论是,GOTO语句是有害的,是造成程序混乱的祸根,程序的质量与GOTO语句的数量呈反比,应该在所有高级程序设计语言中取消GOTO语句。取消GOTO语句后,程序易于理解、易于排错、容易维护,容易进行正确性证明。作为争论的结论,1974年Knuth发表了令人信服的总结,并证实了:
(1)GOTO语句确实有害,应当尽量避免;
(2)完全避免使用GOTO语句也并非是个明智的方法,有些地方使用GOTO语句,会使程序流程更清楚、效率更高;
(3)争论的焦点不应该放在是否取消GOTO语句上,而应该放在用什么样的程序结构上。其中最关键的是,应在以提高程序清晰性为目标的结构化方法中限制使用GOTO语句。
基本结构
顺序结构表示程序中的各操作是按照它们出现的先后顺序执行的。
选择结构表示程序的处理步骤出现了分支,它需要根据某一特定的条件选择其中的一个分支执行。选择结构有单选择、双选择和多选择三种形式。
循环结构表示程序反复执行某个或某些操作,直到某条件为假(或为真)时才可终止循环。在循环结构中最主要的是:什么情况下执行循环?哪些操作需要循环执行?循环结构的基本形式有两种:当型循环和直到型循环。
当型循环:表示先判断条件,当满足给定的条件时执行循环体,并且在循环终端处流程自动返回到循环入口;如果条件不满足,则退出循环体直接到达流程出口处。因为是"当条件满足时执行循环",即先判断后执行,所以称为当型循环。
直到型循环:表示从结构入口处直接执行循环体,在循环终端处判断条件,如果条件不满足,返回入口处继续执行循环体,直到条件为真时再退出循环到达流程出口处,是先执行后判断。因为是"直到条件为真时为止",所以称为直到型循环。
结构化程序中的任意基本结构都具有唯一入口和唯一出口,并且程序不会出现死循环。在程序的静态形式与动态执行流程之间具有良好的对应关系。
由于模块相互独立,因此在设计其中一个模块时,不会受到其它模块的牵连,因而可将原来较为复杂的问题化简为一系列简单模块的设计。模块的独立性还为扩充已有的系统、建立新系统带来了不少的方便,因为我们可以充分利用现有的模块作积木式的扩展。
按照结构化程序设计的观点,任何算法功能都可以通过由程序模块组成的三种基本程序结构的组合: 顺序结构、选择结构和循环结构来实现。
结构化程序设计的基本思想是采用"自顶向下,逐步求精"的程序设计方法和"单入口单出口"的控制结构。自顶向下、逐步求精的程序设计方法从问题本身开始,经过逐步细化,将解决问题的步骤分解为由基本程序结构模块组成的结构化程序框图;"单入口单出口"的思想认为一个复杂的程序,如果它仅是由顺序、选择和循环三种基本程序结构通过组合、嵌套构成,那么这个新构造的程序一定是一个单入口单出口的程序。据此就很容易编写出结构良好、易于调试的程序来。
①整体思路清楚,目标明确。
②设计工作中阶段性非常强,有利于系统开发的总体管理和控制。
③在系统分析时可以诊断出原系统中存在的问题和结构上的缺陷。
①用户要求难以在系统分析阶段准确定义,致使系统在交付使用时产生许多问题。
②用系统开发每个阶段的成果来进行控制,不能适应事物变化的要求。
③系统的开发周期长。
五十一、函数指针
函数指针是指向函数的指针变量。 因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是一致的。函数指针有两个用途:调用函数和做函数的参数。
函数指针的声明方法为:
函数类型 (标志符 指针变量名) ( 形参 列表); 注1:“函数类型”说明函数的返回类型,“(标志符 指针变量 名 )”中的括号不能省,若省略整体则成为一个函数说明,说明了一个返回的 数据类型 是指针的函数,后面的“ 形参 列表”表示指针变量指向的函数所带的 参数 列表。例如: int func(int x); /* 声明一个函数 */ int (*f) (int x); /* 声明一个函数指针 */ f=func; /* 将func函数的首地址赋给指针f */ 赋值时函数func不带括号,也不带 参数 ,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。 注2: 函数括号中的形参可有可无,视情况而定 。 下面的程序说明了函数指针调用函数的方法: 例一、 #include<stdio.h> int max(int x,int y){ return(x>y?x:y); } void main() { int (*ptr)(int, int); int a,b,c; ptr=max; scanf("%d%d",&a,&b); c=(*ptr)(a,b); printf("a=%d,b=%d,max=%d",a,b,c); } ptr是指向函数的 指针变量 ,所以可把函数max()赋给ptr作为ptr的值,即把max()的入口地址赋给ptr,以后就可以用ptr来调用该函数,实际上ptr和max都指向同一个入口地址,不同就是ptr是一个指针变量,不像函数名称那样是死的,它可以指向任何函数,就看你想怎么做了。在程序中把哪个函数的地址赋给它,它就指向哪个函数。而后用 指针变量 调用它,因此可以先后指向不同的函数。 不过注意,指向函数的指针变量没有++和--运算,用时要小心 。
指针函数和函数指针的区别