
由C到C++ OOP第一课
C++继承了C的优点,并有自己的特点,主要有: 1、全面兼容C,C的许多代码不经修改就可以为Cpp所用,用C编写的库函数和实用软件可以用于Cpp。 2、用C++编写的程序可读性更好,代码结构更为合理,可直接在程序中映射问题空间结构。 3、生成代码的质量高,运行效率高。 4、从开发时间、费用到形成软件的可重用性、可扩充性、可维护性和可靠性等方面有了很大提高,使得大中型的程序开发项目变得容易得多。 5、支持面向对象的机制,可方便的构造出模拟现实问题的实体和操作。 C++的程序特征例1.1 输出一行字符:“This is a C++ program.”。程序如下: using namespace std; //使用命名空间std int main( ) { cout<<″This is a C++ program.″; return 0; } 在运行时会在屏幕上输出以下一行信息: This is a C++ program.
#include <iostream> //预处理命令 using namespace std; //使用命名空间std int main( ) //主函数首部 { //函数体开始 int a,b,sum; //定义变量 cin>>a>>b; //输入语句 sum=a+b; //赋值语句 cout<<″a+b=″<<sum<<endl; //输出语句 return 0; //如程序正常结束,向操作系统返回一个零值 } //函数结束 本程序的作用是求两个整数a和b之和sum。 第1行“//求两数之和”是一个注释行,Cpp规定在一行中如果出现“//” ,则从它开始到本行末尾之间的全部内容都作为注释。 using namespace std; int max(int x,int y) //定义max函数,函数值为整型,形式参数x, y为整型 { //max函数体开始 int z; //变量声明,定义本函数中用到的变量z为整型 if(x>y) z=x; //if语句,如果x>y, 则将x的值赋给z else z=y; //否则,将y的值赋给z return(z); //将z的值返回,通过max带回调用处 } //max函数结束 int main( ) //主函数 { //主函数体开始 int a,b,m; //变量声明 cin>>a>>b; //输入变量a和b的值 m=max(a,b); //调用max函数,将得到的值赋给m cout<<″max=″<<m<<′\\n′; //输出大数m的值 return 0; //如程序正常结束,向操作系统返回一个零值 } //主函数结束 本程序包括两个函数:主函数main和被调用的函数max。 程序运行情况如下:
注意输入的两个数据间用一个或多个空格间隔,不能以逗号或其他符号间隔。 在上面的程序中,max函数出现在main函数之前,因此在main函数中调用max函数时,编译系统能识别max是已定义的函数名。如果把两个函数的位置对换一下,即先写main函数,后写max函数,这时在编译main函数遇到max时,编译系统无法知道max代表什么含义,因而无法编译,按出错处理。 为了解决这个问题,在主函数中需要对被调用函数作声明。上面的程序可以改写如下: using namespace std; int max(int x,int y); //对max函数作声明 int main( ) { int a,b,c; cin>>a>>b; c=max(a,b); //调用max函数例1.3 给两个数x和y, 求两数中的大者。 cout<<″max=″<<c<<endl; return 0; } int max(int x,int y) //定义max函数 { int z; if(x>y) z=x; else z=y; return(z); } 只要在被调用函数的首部的末尾加一个分号,就成为对该函数的函数声明。函数声明的位置应当在函数调用之前。 C++程序的结构特性一个面向对象的C++程序一般由类的声明和类的使用两大部分组成。 类的使用部分一般由主函数及有关子函数组成。 //类的声明部分 class A{ int x,y,z; …… fun( ){……} …… }; //类的使用部分 int main() { A a; …… a.fun(); return 0; } 在C++程序中,程序设计始终围绕“类”展开。通过声明类,构建了程序所要完成的功能,体现了面向对象程序设计的思想。 C++程序的编辑、编译和运行C++源程序文件的扩展名为.CPP 可以用多种编译器编辑、编译和运行
Cpp新增了注释语句,它由“//”开始,到行尾结束。 例如: X = y + z; //This is a comment
<< “u=” << u << “v\ =” << v << “w=” << w << endl;2、输入输出流 C中I/O操作出现的问题: float f; scanf(“%f”,i); printf( “%d”,d); Cpp中使用更安全更方便的方法: float f; cin >> i; cout << f; cout和cin分别是C++的标准输出流和输入流。Cpp支持重定向,但一般cout指的是屏幕, cin指的是键盘。 操作符“<<”和“>>”除了具有C语言中定义的左移和右移的功能外,在这里符号“<<”是把右方的参数写到标准输出流cout中;相反,符号“>>”则是将标准输入流的数据赋给右方的变量。 int main() { char name[20]; cout << "Hello, your name: "; cin >> name; cout << name; return 0; } 注意:
void main() { int x=25; cout << hex << x << ' ' << dec << x << ' ' << oct << x << '\n'; } 输出结果为:19 25 31 在程序中的不同位置采用不同的变量定义方式,决定了该变量具有不同的特点。变量的定义一般可有以下三种位置: (1) 在函数体内部 在函数体内部定义的变量称为局部变量,这种局部变量只在进入定义它的函数体时起作用,离开该函数体后该变量就消失(被释放),即不再起作用。因此,不同函数体内部可以定义相同名称的变量,而互不干扰。 (2) 形式参数 当定义一个有参函数时,函数名后面括号内的变量,统称为形式参数。 (3) 全局变量 在所有函数体外部定义的变量,其作用范围是整个程序,并在整个程序运行期间有效。
例如 { int i; i=10; int j; j=25; // … } float fun(int x,int y) { for(int i=0;i<10;i++) { int sum=0; sum=sum+i; cout<<“sum=”<<sum; } int z=0; z=x+y; }4、结构、联合和枚举名 在C++中,结构名、联合名、枚举名都是类型名。在定义变量时,不必在结构名、联合名或枚举名前冠以struct、union或enum。 例如: struct string{ char *string; int length; }; union number{ int i; float f; }; 在传统的C中,定义变量时,必须写成: struct string str; union number x; 但是,在C++中,可以说明为: string str; number x;5、函数原型 C语言建议编程者为程序中的每一个函数建立原型,而Cpp要求为每一个函数建立原型,以说明函数的名称、参数类型与个数,以及函数返回值的类型。 其主要目的是让C++编译程序进行类型检查,即形参与实参的类型匹配检查,以及返回值是否与原型相符,以维护程序的正确性。 例如
void write(char *s); void main() {write("Hello,world!");} void write(char *s) {cout<<s;} 在程序中,要求一个函数的原型出现在该函数的调用语句之前。
一般格式: #define 宏名 常数 如 ………… s = 2 * PI * r; …………
一般格式:const 数据类型标识符 常数名=常量值; 采用这种方式定义的常量是类型化的,它有地址,可以用指针指向这个值,但不能修改它。 说明:1、const必须放在被修饰类型符和类型名前面 2、数据类型是一个可选项,用来指定常数值的数据类型,如果省略了该数据类型,那么编译程序认为它是 int 类型。 例2.6 #define的不安全性 main() { int a=1; #define T1 a+a #define T2 T1-T1 cout<<"T2 is "<<T2<<endl; return 0; } 但实际的输出是:T2 is 2 const作用与#define相似,但消除了#define的不安全性。 如果用const取代了两个#define,就不会引起这个错误。 int main() { int a=1; const T1=a+a; const T2=T1-T1; cout <<"T2 is"<<T2<<endl; return 0; }const可以与指针一起使用
例如: pc[3]=‘x’; //错误 pc=“efgh”; //允许
例如: pc[3]=‘x’; //合法 pc=“efgh”; //出错 创建一个常指针,就是创建一个不能移动的固定指针,但是它所指的数据可以改变。例如:
要声明一个指向常量的常指针,二者都要声明为const。 例如: pc[3]=‘x’; //出错 pc=“efgh”; //出错 这个语句的含义是:声明了一个名为pc的指针变量,它是一个指向字符型常量的常指针,用“abcd”的地址初始化该指针。
7、void型指针 void 通常表示无值,但将void作为指针的类型时,它却表示不确定的类型。 这种void型指针是一种通用型指针,也就是说任何类型的指针值都可以赋给void类型的指针变量。 例如下面的程序段 void* pc; //正确,可以声明void类型的指针 int i=456; char c=‘a’; pc=&i; pc=&c; void型指针可以接受任何类型的指针的赋值,但对已获值的void型指针,对它在进行处理,如输出或传递指针值时,则必须进行强制类型转换,否则会出错。 main() { void *pc; int i=456; char c='a'; pc=&i; cout<<*(int *)pc<<endl; pc=&c; cout<<*(char *)pc<<endl; return 0;8、内联函数 调用函数时系统要付出一定的开销,用于信息入栈出栈和参数传递等。特别是对于那些函数体较小但调用又较频繁的函数,计算机的开销相对就比较可观。 在C语言中,用宏替换,可解决这个问题。例如,有如下的函数: { return x+y; } 用宏替换时,上面的函数功能可写为: C++引进了内联函数(inline function)的概念。 宏替换实质上是文字替换。内联函数与一般函数不同的是,在进行程序的编译时,编译器将内联函数的目标代码作拷贝并将其插入到调用内联函数的地方。 inline double circle(double r) {return 3.1416*r*r;} int main() { for(int i=1;i<=3;i++) cout<<"r="<<i<<" area="<<circle(i)<<endl; return 0; }说明:
在C++中,函数的参数可以有缺省值。 当调用有缺省参数的函数时,如果相应的参数没有给出实参,则自动用相应的缺省参数值作为其实参。 函数的缺省参数,是在函数原型中给定的。 例如: init(100,80); //允许 init(25); //允许 init(); //允许说明:
int main() 函数重载是指一个函数可以和同一作用域中的其他函数具有相同的名字,但这些同名函数的参数类型、参数个数不同。如: void whatitis(int i) { cout<<"this is integer"<<i<<endl;} void whatitis(char c[]) { cout<<“this is string”<<c<<endl; } main() { int i=1; char c[]="abcdef"; whatitis(i); whatitis(c); } 在本例中定义了两个名称都叫whatitis的函数,但它们的形参类型不同。因此,这两个函数就是重载函数。 在原有C语言中,每个函数必须有其唯一的名称,这样的缺点是所有具有相同功能、而只是函数参数不一样的函数,就必须用一个不同的名称. 而C++中采用了函数重载后,对于具有同一功能的函数,如果只是由于函数参数类型不一样,则可以定义相同名称的函数。 由于重载函数具有相同的函数名,在进行函数调用时,系统一般按照调用函数时的参数个数、类型和顺序来确定被调用的函数。 具体来说,按以下三个步骤的先后次序找到并调用那个函数:
void print(double d) { cout<<"this is a double "<<d<<"\n"; } void print(int i) { cout<<"this is an integer "<<i<<"\n"; } void main() { int x=1,z=10; float y=1.0; char c='a'; print(x); //按规则(1)自动匹配函数void print(int i) print(y); //按规则(2)通过内部转换匹配函数 void print(double i) //因为系统能自动将float型转换成double型 print(c); //按规则(2)通过内部转换匹配函数 void print(int i) //因为系统能自动将char型转换成int型 print(double(z)); //按规则(3)匹配void print(double i) //因为程序中将实参z强制转换为double型。 }例 重载例子 编写一个程序,用来求两个整数或3个整数中的最大数。如果输入两个整数,程序就输出这两个整数中的最大数,如果输入3个整数,程序就输出这3个整数中的最大数。 using namespace std; int main( ) { int max(int a,int b,int c); //函数声明 int max(int a,int b); //函数声明 int a=8, b=-12, c=27; cout<<"max(a,b,c)="<<max(a,b,c)<<endl; //3个整数中的最大者 cout<<"max(a,b)="<<max(a,b)<<endl; //2个整数中的最大者 } int max(int a,int b,int c) { if(b>a) a=b; if(c>a) a=c; return a; } int max(int a,int b) { if(a>b) return a; else return b; }(4) 定义重载函数时的注意事项
{………………} int myfun(int i) {………………} // 这种重载是错误的
1) 函数模板 (function template): 建立一个通用函数,其函数类型和形参类型不具体指定,而是一个虚拟类型。 2) 应用情况: 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。 3) 一般形式: - template < typename T> // 模板头 通用函数定义 - template <class T> // 模板头 通用函数定义 - template <class T1,typename T2> // 多个参数 通用函数定义
using namespace std; template<typename T> // 模板声明,其中T为类型参数 T max(T a, T b) // 定义一个通用函数, T作为虚拟的类型名 { if(b>a) return b; else return a; } //template <typename T> T max(T a, T b) //{ //… //} int main( ) { int i1=111, i2=222, i; double d1=12.34, d2=56.78,d; i=max(i1,i2); // 调用模板函数,此时T被 int 取代 d=max(d1,d2,d3); // 调用模板函数,此时T被 double 取代 cout<<"i_max=" << i <<endl; cout<<"f_max=" <<f<<endl; return 0; }函数模板说明: 1) 在对程序进行编译时,遇到第13行调用函数max(i1,i2), 编译系统会将函数名max与模板max相匹配,将实参的类型取代了函数模板中的虚拟类型T。此时相当于已定义了一个函数,然后调用它。 { if(b>a) a=b; if(c>a) a=c; return a; } 2) 与重载函数比较:用函数模板比函数重载更方便,程序更简洁。但应注意它只适用于:函数的参数个数相同而类型不同,且函数体相同的情况。如果参数的个数不同,则不能用函数模板; 3) main函数不能定义为模板函数。 通常情况下,如果有两个同名变量,一个是全局的,另一个是局部的,那么局部变量在其作用域内具有较高的优先权。 下面的例子说明了这个问题。 int avar=10; main( ) { int avar; avar=25; cout<<"avar is"<<avar<<endl; return 0; } 如果希望在局部变量的作用域内使用同名的全局变量,可以在全局变量加上“::”,此时::avar代表全局变量avar int avar=10; main() { int avar; avar=25; cout<<"local avar ="<<avar<<endl; cout<<"global avar="<<::avar<<endl; return 0; }12、无名联合 无名联合是C++中的一种特殊联合,可以声明一组无标记名共享同一段内存地址的数据项。如: int i; float f; } 在此无名联合中,声明了变量i和f具有相同的存储地址。无名联合可通过使用其中数据项名字直接存取,例如可以直接使用上面的变量i或f,如:i=20; 在C中数据类型转换的一般形式:(数据类型标识符)表达式 float x=(float) i; C++支持这样的格式,还提供了一种更为方便的函数调用方法,即将类型名作为函数名使用,使得类型转换的执行看起来好像调用了一个函数。形式为:数据类型标识符 (表达式) float x=float(i); 以上两种方法C++都接受,但推荐使用后一种方式。 作为对C语言中malloc和free的替换,C++引进了new和delete操作符。它们的功能是实现内存的动态分配和释放。 动态分配new的一般形式是:
a=new int; b=new int(10); 释放由new操作动态分配的内存时,用delete操作。 它的一般形式是:delete 指针变量; delete b; main() { int *p; // 声明一个整型指针变量p p=new int; // 动态分配一个存放int型数据的内存空间,并将首地址赋给p *p=10; cout<<*p; delete p; // 释放指针变量p指向的内存空间 return 0; }例1.10 将new和delete用于结构类型 #include<string.h> struct person { char name[20]; int age; }; main() { person *p; p=new person; strcpy(p->name, "Wang Fun"); p->age=23; cout<<"\n"<<p->name<<" "<<endl<<p->age; delete p; return 0; } 与C的内存动态分配和释放操作(malloc和free)相比,C++提供的动态分配有以下优点
main() { int * p; p=new int; if(!p){ cout<<"allocation failure\n"; return 1; } *p=20; cout<<*p; delete p; return 0; }
int main() { int *p; p=new int(99); // 动态分配内存,并将99作为初始值赋给它 if (!p) { cout<<"allocation failure\n"; return 1; } cout<<*p; delete p; return 0; }例 1.13 给数组分配内存空间的例子。#include <iostream.h> main() { double *s; s=new double[10]; if(!s){ cout<<"alocation failure\n"; return 1; } for(int i=0;i<10;i++) s=100.00+2*i; for(int i=0;i<10;i++) cout<<s<<" "; delete []s; return 0; }15. 引用(1) 引用的概念 引用就是某一变量(目标)的一个别名,这样对引用的操作就是对目标的操作。 引用的声明方法: 类型标识符 &引用名=目标变量名; int &ra=a; //定义引用ra,它是变量a的引用,即别名说明:
void main() { int i; int &j=i; i=30; cout<<"i="<<i<<"j="<<j<<"\n"; j=80; cout<<"i="<<i<<"j="<<j<<"\n"; cout<<"Address of i"<<&i<<"\n"; cout <<"Address of j"<<&j<<"\n"; } 结果: i=80 j=80 Address of oxfff4 Address of oxfff4例1.16 使用引用可以简化程序#include<iostream.h> main() { int i=15; int* iptr=&i; int & rptr=i; cout<<" i is "<<i<<endl; cout<<" *iptr is "<<*iptr<<endl; cout<<" rptr is "<<rptr<<endl; i=29; cout<<" After changing i to 29: "<<endl; cout<<" i is "<<i<<endl; cout<<" *iptr is "<<*iptr<<endl; cout<<" rptr is "<<rptr<<endl; return 0; } 运行结果: *iptr is 15 rptr is 15 After changing i to 29: i is 29 *iptr is 29 rptr is 29(2) 引用的使用
int &j; j=i;
int &j1=i; int &j2=j1; int & ref=num; int *p=&ref;
void main() { int *a; //定义指针变量a int *&p=a; //定义引用p,初始化为指针变量a,所以p是a的引用(别名) int b=10; p=&b; //等价于a=&b,即将变量b的地址赋给a。 cout<<*a<<endl; //输出变量b的值 cout<<*p<<endl; //等价于cout<<*a; cout<<a<<endl; //输出a和p的地址 cout<<p<<endl; }
int & & ra=a; //错误 int &*p=&ra; //错误
int& i=j; // 声明引用i, "&"为引用运算符 i=123; // 使用引用i,不带引用运算符 int *pi=&i; // 在此, "&"为地址操作符 cout<<π // 在此, "&"为地址操作符(3) 用引用作为函数的参数 一个函数的参数也可定义成引用的形式 { int p; p=p1; p1=p2; p2=p; } 在主调函数的调用点处,直接以变量作为实参进行调用即可,不需要实参变量有任何的特殊要求。 void swap(int *m, int *n) { int temp; temp=*m; *m= *n; *n=temp; } main() { int a=5, b=10; cout<<"a="<<a<<" b="<<b<<endl; swap(&a, &b); cout<<"a="<<a<<" b="<<b<<endl; return 0; } 运行结果: a=10 b=5例1.18 采用“引用参数”传递函数参数#include <iostream.h> void swap(int& m, int& n) { int temp; temp=m; m=n; n=temp; } main() { int a=5, b=10; cout<<"a="<<a<<" b="<<b<<endl; swap(a, b); cout<<"a="<<a<<" b="<<b<<endl; return 0; } 运行结果: a=10 b=5(4) 用引用返回函数值 函数可以返回一个引用,将函数说明为返回一个引用的主要目的是:为了将函数用在赋值运算符的左边。 要以引用返回函数值,则函数定义时要按以下格式: 类型标识符 &函数名(形参列表及类型说明) {函数体}
int a[]={1, 3, 5, 7, 9}; int& index(int); // 声明返回引用的函数 void main() { cout<<index(2)<<endl; index(2)=25; // 将a[2]重新赋值为25 cout<<index(2)<<endl; } int& index(int i) { return a; }例1.20 用引用返回函数的值#include<iostream.h> int A[10]; int& array(int i); void main() { int i, number; A[0]=0; A[1]=1; cin>>number; for (i=2;i<number;i++) { array(i)=array(i-2)+array(i-1); cout<<"array("<<i<<")="<<array(i)<<endl; } } int& array(int i) { return A; } 运行结果: array(3)=2 array(4)=3 array(5)=5 array(6)=8 array(7)=13 array(8)=21 array(9)=34
{ int a; //... return a; }
{ int p; p=*p1; //必须用“*指针变量名”的形式操作目标变量 p1=*p2; *p2=p; } main() { int a,b; cin>>a>>b; swap(&a,&b); //必须以变量a和b的地址作为实参 cout<<a<<b; }如何使一个被调函数同时返回多个值 由于函数的返回值是通过函数体中的return语句完成的,但一个return语句只能返回一个值,为此,我们可以采用以下方法:
以下定义了可以同时返回10个数中的最大值和最小值的函数max_min。 void max_min(int *p,int n,int &max,int &min); //声明函数max_min void main() { int a[10]; int ma,mi; int i; for(i=0;i<10;i++) cin>>a; max_min(a,10,ma,mi); //调用函数max_min cout<<ma<<mi; } void max_min(int *p,int n,int &max,int &min) //形参max 和min定义成引用 { int i=0; max=*(p+i); min=*(p+i); for(i=1;i<n;i++) { if(max<*(p+i)) max=*(p+i); //实质上就是对实参变量ma赋值 if(min>*(p+i)) min=*(p+i); //实质上就是对实参变量mi赋值 } }例 以下程序中定义了一个普通的函数fn1(它用返回值的方法返回函数值),另外一个函数fn2,它以引用的方法返回函数值。 float temp; //定义全局变量temp float fn1(float r); //声明函数fn1 float &fn2(float r); //声明函数fn2 float fn1(float r) //定义函数fn1,它以返回值的方法返回函数值 { temp=(float)(r*r*3.14); return temp; } float &fn2(float r) //定义函数fn2,它以引用方式返回函数值 { temp=(float)(r*r*3.14); return temp; } void main() //主函数 { float a=fn1(10.0);//第1种情况,系统生成要返回值的副本(即临时变量) float &b=fn1(10.0);//第2种情况,可能会出错(不同C++系统有不同规定) //不能从被调函数中返回一个临时变量或局部变量的引用 float c=fn2(10.0); //第3种情况,系统不生成返回值的副本 //可以从被调函数中返回一个全局变量的引用 float &d=fn2(10.0); //第4种情况,系统不生成返回值的副本 //可以从被调函数中返回一个全局变量的引用 cout<<a<<c<<d; } 一个返回引用的函数值作为赋值表达式的左值 一般情况下,赋值表达式的左边只能是变量名,即被赋 值的对象必须是变量,只有变量才能被赋值,常量或表达式不能被赋值,但如果一个函数的返回值是引用时,赋值号的左边可以是该函数的调用。 int &put(int n); int vals[10]; int error=-1; void main() { put(0)=10; //以put(0)函数值作为左值, 等价于vals[0]=10; put(9)=20; //以put(9)函数值作为左值, 等价于 vals[9]=10; cout<<vals[0]; cout<<vals[9]; } int &put(int n) { if (n>=0 && n<=9 ) return vals[n]; else{ cout<<”subscript error”; return error; } }用const限定引用 声明方式:const 类型标识符 &引用名=目标变量名; 用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。 double &fn(const double &pd) { static double ad=32; ad+=pd; cout<<pd<<endl; return ad; } void main() { double a=100.0; double &pa=fn(a); cout<<pa<<endl; a=200.0; pa=fn(a); cout<<pa<<endl; } 程序运行的结果 132 200 332引用总结
int &max(int &num1,int &num2); // 返回一个较大值 int &min(int &num1,int &num2); // 返回一个较小值 main() { int num1, num2; cout<<"Enter the first number: "; cin>>num1; cout<<"Enter the second number: "; cin>>num2; max(num1,num2)=0; cout<<"\nAfter putting zero in largest, the numbers are"; cout<<"\n"<<num1<<" and "<<num2<<"\n"; cout<<"\nNow, please enter two more numbers.\n"; cout<<"Enter the first number :"; cin>>num1; cout<<"Enter the second number:"; cin>>num2; min(num1, num2)=0; cout<<"\nAfter putting zero in smallest the numbers are"; cout<<"\n"<<num1<<" and "<<num2<<"\n"; return 0; } int &max(int &num1,int &num2) { return (num1>num2)?num1:num2; } int &min(int &num1,int &num2) { return (num1<num2)?num1:num2; } |