Object-Oriented Programming in C++ 第一章 C++的初步知识 中国科大学继续教育学院 李艺 leeyi@ustc.edu.cn
第一章 C++的初步知识 第二章 类和对象 第三章 再论类和对象 第四章 运算符重载 第五章 继承与派生 第六章 多态性与虚函数 第七章 输入输出流 第八章 C++工具
参考教材 选用教材 《C++面向对象程序设计》 谭浩强 参考教材 《C++语言基础教程》 吕凤翥编著 英文教材 《Programming in C++》(SE)高等教育出版社
学时分配 总学时:60 讲课:40学时 上机:20学时 课外上机:每周不少于4学时
1.1 C++程序结构的特点 一个示范程序 程序的组成部分 程序的书写格式
一个C++的示范程序 /*This is a simple Cpp program*/ #include <iostream.h> //文件包含 void main( ) //主函数头 { //主函数体 double x,y; //变量说明 cout << "Enter two float number:"; //输出数据 cin >> x>>y ; //输入数据 double z=x+y; //定义并赋值 cout <<"x+y= "<<z<<endl ; //输出数据 } 任何变量都必须先说明后使用 执行结果为: Enter two float number: 3 4 <enter> x+y=7
1.2 用const定义常变量 C语言中,用#define 定义常量: #define PI 3.14159 #define R a+b 它只是在预编译时进行字符置换,将标识符置换成表达式或数字。预编译后,标识符PI,R不再存在。PI,R不是变量,没有类型,不占用存储单元,很容易出错: int a=1;b=2; cout<<PI * R * R; 输出的不是3.14159 * (a+b) * (a+b),而是 3.14159 * a+b * a+b。 程序因此出错。
C++进行了改进,用 const 定义常变量。如: const float PI = 3.14159; 它定义了常变量PI,有数据类型,占用存储单元,有地址,可以用指针指向它,只是在程序运行中,此变量的值固定,不能改变。 常变量定义时,const 与指针结合使用,可以有如下三种不同的定义: const float *ptr = 3.14159; //指向常变量的指针,数值3.14159不能改变为其它值 float const *ptr = 3.14159; //指向变量的常量指针,指针地址不能改变 const float const *ptr = 3.14159 ; //指向常量的常量指针
1.3 函数原型申明 C++强制规定,如果函数调用的位置在函数定义之前,则在调用函数之前必须实现作函数原型申明。这一点与C不同,C只是建议,而没有强制 函数申明的一般形式为: 函数类型 函数名(参数表); 最后的分号可不能忘了,否则出错!参数表中可以只指定各个参数的类型,而不指定参数名!
函数申明和定义的示例 //求圆面积,将函数申明和定义分开,在调用函数前先申明它 #include <iostream> float area( float ); //先申明 void main( ) { float radius; cout<<“Please input radius: “; cin>>radius; if (radius>0) cout<<area(radius )<<endl; //再使用 } float area( float r) //最后定义 { return r * r* 3.14159;}
程序可以改为如下形式: #include <iostream> float area( float r) { return r * r* 3.14159;} //先申明 main( ) { float radius; cout<<“Please input radius: “; cin>>radius; if (radius>0) cout<<area(radius )<<endl; //再使用 }
1.4 函数的重载(overloading) 什么叫重载? 运算符“>>”是左移运算符,在C++中,有作为输出算符;而“*”是乘法算符,也是指针符号。这种一个标识符有多种含义用途的现象,旧叫“重载”(overloading)。如果标识符为运算符,就叫“运算符重载”;如果标识符为函数名,就叫“函数重载”。 什么时候用到重载? 用于相类似而又不同的操作。例如,求多个数值的最大数,不同类型的参数,C语言要编写不同的函数: int max1(int a, int b, int c); // 3个整形数值求最大值 float max2(float a, float b); // 2个实形数值求最大值 long max3(long a, long b, long c); // 3个长整形数值求最大值
而C++的做法是重载:将这三个函数名取相同的名字,程序运行时,系统会根据实际参数的不同,调用相近参数的函数: #include <iostream> int max(int a, int b, int c) { if (b>a) a=b; if (c>a) a=c; return a; } float max(float a, float b) long max(long a, long b, long c) void main( ) { int a, b, c, d; float x, y, z; long m, n, p, q; cin>>a>>b>>c; cin>>x>>y; cin>>m>>n>>p; d = max (a, b, c); cout <<“int_d = “<<d<<endl; z = max (x,y); cout <<“float_z = “<<z<<endl; q = max (m, n, p); cout <<“long_q = “<<q<<endl; } Main函数三次调用max函数,每次实参的个数或类型不同,系统会寻找 与之匹配的函数调用。
1.5 函数模板 什么时候使用模板?重载函数的不便之处在于,对于同名的函数要一个一个地编写,编码量很大。对于有些相同功能的函数,如果函数个数相同,可以用另外的方法来解决,这就是模板(Template)。 函数模板的定义形式: Template <类型参数表> 返回类型 函数模板名(数据参数表) { 函数模板定义体 } 示例:将上述重载示例改为函数模板。
#include <iostream> using namespace std; //默认使用std标准库名 template <typename T>; T max (T a, T b, T c) // 建立函数模板时,只需要 { if (b>a) a=b; // 将函数类型、参数类型 int 换成 T 就行。 if (c>a) a=c; // 即用虚拟的类型名T代替实际的类型 return a; } void main( ) { int i = 8, j = 3, k = 4, h; long m = 1, n = 2, p = 3, q; h = max ( i, j, k); q = max ( m, n, p); cout <<“int_h = “<<h<<endl; cout <<“long_q = “<<q<<endl; 类型参数可以有多个:template <typename T1, typename T2>
由此程序可以看到,函数模板比函数重载更方便,但模板只适用于函数的参数个数相同而类型不同,且函数体相同的情况。不满足这种情况时,就只能使用函数重载。
1.6 有默认参数的函数 背景:一般情况下,函数调用时,形参从实参那里取得值。因此要求实参的个数和类型应该与形参相同。但是,有时候,多次调用同一函数时用的是同一实参数,或者调用时还不好确定实参数。C++提供一个简单的解决办法,即给参数一默认值。这样当不提供实参数时,形参就用默认参数作为参数值。 示例:有一函数声明为: float area ( float r=6.5 ); 调用area 函数时,如果不提供实际参数给r,r 就以默认数值 6.5 作为参数顶用area: area ( ); // 相当于 area (6.5 ); 注意: 1) 当有多个参数时,如果只有部分参数有默认值,则指定了默认值的参数必须放在参数表的最右边,否则出错。因为实参与形参的结合是从左至右顺序进行的,第一个实参必须给第一个形参,第二个实参必须给第二个形参…。
1.6 有默认参数的函数 声明: void area (float x1, int x2 = 1, char x3 = ‘a’); 调用:area ( 1.2, 3, ‘b’); // 形参值全部由实参得到 area (1.2, 3); // 最后一个参数取自默认值 area (1.2 ); // 最后两个参数取自默认值 area ( ); // 出错,第一个形参没有实参,也没有默 认值 2) 一个函数不能既作为重载函数,又作为默认参数函数。因为当调用函数时如果少写一实际个参数,系统无法判断是利用重载函数还是利用默认参数函数。
1.7 变量的引用(reference) 什么叫引用?变量的引用就是变量的别名。建立引用的作用,是为一个变量另取一个名字,以便在需要的时候间接地引用该别名。 如何使用引用?假如有一个变量a , 想给它另取一个别名b ,可以这样写: int a ; int &b = a; // 声明b 是一个整形变量的引用变量,并且被初始化为a 此处 & 不代表取地址,,只是“引用声明符”。对一个变量声明一个引用,并不另外开辟内存空间。b和a 代表同一个变量单元。 引用不是独立的变量,编译系统不给它分配存储单元。因此建立引用只有声明,没有定义,只是声明和某一个变量的关系。
1.7 变量的引用(reference) 声明了一个变量的引用后,在本函数执行期间,该引用一直与代表的变量相联系,不能再作为另一个变量的别名。请看如下示例: int a, b ; int &c = a; int &c = b; // 错误,c 已经是a 的引用,不能再作为另一个变量b的引用 引用不是独立的数据类型,它必须与某一种类型的数据相联系: int &x; // 错误, 没有指定 x 代表哪个变量
1.7 变量的引用(reference) 示例: #include <iostream> using namespace std; void main( ) { int a = 10; int &b = a; a = a * a; cout << “b = “<<b<<endl; } 运行结果:b = 100
1.7 变量的引用(reference) 将引用作为函数参数:C++之所以增加“引用”,主要是利用它作为函数参数,以弥补函数传递参数的不方便的遗憾。也降低了编程难度。 将变量名作为实参:这时传给形参的是变量的值,传递是单向的,函数运行时,形参发生变化,并不回传给实参。因为形参和实参不是同一个存储单元。请看下面示例: #include <iostream> void swap ( int a, int b ) { int temp; temp = a; a = b; b = temp; } void main( ) { int i=3, j=5; swap ( i, j ); cout<<i<<“,”<<j<<endl; } 运行结果,i, j 仍然是3,5。 没有发生交换。
1.7 变量的引用(reference) 传递变量的指针:这时传给形参的是变量的地址,形参得到一个变量的地址,即形参指针变量指向实参变量单元。函数中形参发生改变,实际上是改变实参。 这种方法实际上仍然是值传递:想指针变量传递地址值。然后通过指针变量访问有关变量。“间接地”回传了改变的变量。请看下面示例: #include <iostream> void swap ( int *p1, int *p2 ) { int temp; temp = *p1; *p1 = *p2; *p2 = temp; } void main( ) { int i=3, j=5; swap ( &i, &j ); cout<<i<<“,”<<j<<endl; } 运行结果,i, j 是5,3。 变量值发生交换。
1.7 变量的引用(reference) 传递变量的别名:将变量的引用作为函数形参,弥补了上面两种方法的不足。请看下面示例: 注意:swap函数形参&a,&b是指定整形变量的引用作为形参,但引用谁还没定。而main函数中,用一,i, j的名调用swap,就是将i, j的名字传给引用,这样a就成立i的别名,b就成立j 的别名。 #include <iostream> void swap ( int &a, int &b ) { int temp; temp = a; a = b; b = temp; } void main( ) { int i=3, j=5; swap ( i, j ); cout<<i<<“,”<<j<<endl; } 运行结果,i, j 是5,3。 变量值发生交换。
1.7 变量的引用(reference) 如何理解引用?我们从物理实现上来理解,引用是一个隐性指针,即引用值是引自所指向的实体。这就是引用的真意。高级编程(面向应用)多用引用,低级编程(面向机器)多用指针。 对引用的说明: 不能建立void类型的引用。因为任何实际存在的变量都属于非void类型。 void &a = 9; //错误 不能对数组名进行引用,因为数组名是首地址本身不占有存储空间。 char c[6] = “hello”; char &r =c; //错误 可以建立指针变量的引用: int i=5; int *p= &i; // 定义指针变量p,指向i int * &t = p; // t 是指向整形变量的指针变量的引用,初始化为p.
1.7 变量的引用(reference) 可以将引用的地址赋给指针。此时,指针指向原来变量: int a = 3; int &b = a; int *p = &b; // 指针p指向变量a的引用b,相当与指向a 不能定义指向引用类型的指针变量: int & *p = & a; // 错误 可以用常量或表达式对引用初始化,但必须用const限定: const int &c = 3; //合法
1.8 内联函数(inline function) 引入原因:目的是为了解决程序中函数调用的效率问题。 函数调用时需要建立栈内存环境,进行参数传递,并产生程序执行转移,则都要有时间和空间的代价。 而有时一些函数代码很短(1~5行),却有高使用频率,造成处理现场的开销巨增。 这时若将函数体嵌入函数调用处,则可避免每次调用函数的开销,大大提高效率。
1.8 内联函数(inline function) #include <iostream.h> inline int power(int x ) // 定义一个内联函数 { return x*x; } void main( ) { cout << power ( 2 ) << endl; // 编译系统展开为{x=2;x*x;} cout << power ( 1+2 ) << endl; // 编译系统展开为{x=1+2;x*x;} } 运行结果: 4 9
1.8 内联函数(inline function) 内联函数还有限制: 函数内不能含有循环结构或switch结构; 不能含有任何静态数据及数组声明。 不能是递归函数。
1.9 作用域运算符 标识符只能在说明它或定义它的范围内是可见的,而在该范围之外是不可见的。 大多数标识符的说明与定义是一致的,只有少数例外。如:函数等。 范围有大有小。最大为整个程序,最小为块,中间有文件和函数。 每一个变量都有自己的有效范围。我们只能在变量的作用域内使用该变量。不能直接使用其它作用域中的变量。如果要使用其它作用域中的同名变量,必须使用“作用域运算符”,即“::”。
1.9 作用域运算符 #include <iostream.h> float a=1.5; void main( ) 请看示例: #include <iostream.h> float a=1.5; void main( ) { int a = 5; cout << a << endl; // 输出作用域为main 函数的局部变量a 的值 cout << ::a <<endl; // 输出作用域为全局的 全局变量a 的值 } 运行结果:5 1.5 注意:不能用作用域运算符“::”访问函数中的局部变量。
局部变量和全局变量 1、局部变量 是指作用域在函数级和块级的变量。 2、全局变量 是指作用域在程序级和文件级的变量。
#include<iostream.h> int i(5); //外部全局变量extern 蓝色为文件作用域 #include<iostream.h> int i(5); //外部全局变量extern void func( ) { cout << i<<endl; } void main() { int i=3; // 内部局部变量 func( ); cout <<i<<endl; } 全局变量 绿色为函数作用域 局部变量
1.10 字符串变量 C++除了可以使用C语言提供的字符型变量和字符型数组外,还提供了字符串类。这种类可以定义字符串对象。但在文件开头必须包含string库:#include <string.h> 字符串定义: string s1; // 定义字符串变量s1 string s2 = “China”; // 定义字符串变量s2并初始化 字符串赋值:可以对定义了的字符串变量进行赋值: s1 = “Hello”; s2 = s1; s3 = s1+s2; // 字符串连接 字符串变量的输入输出: string c1; cin >> c1; cout << c1; 字符串比较:可以使用==,!=,>=,<= 来进行字符串的比较
1.10 字符串变量 字符串数组:也可以直接定义字符串数组: string name [4] = {“张三”,”李四”,”王五”,”刘六”}; 运行结果为: name [0] = “张三“; name [1] = “李四”; name [2] = “王五”; name [3] = “刘六”;
1.10 字符串变量 示例:从键盘输入3个字符串,并按字典顺序输出: #include <iostream> #include <string> using namespace std; void main( ) { string s1,s2,s3,temp; cin >>s1>>s2>>s3; if ( s2>s3) { temp =s2; s2 = s3; s3 = temp; } if ( s1>s2) { temp =s1; s1 = s2; s2 = temp; } cout << s1<<"->"<< s2<<"->"<<s3<<endl; }
1.11 动态分配内存new/回收内存delete C语言中,用两个函数malloc(size)/free来实现内存管理。C++提供了更为强大的运算符new/delete,来代替这两个函数。 分配内存空间:new 类型; 回收内存空间:delete 指针变量; 示例:开辟一个空间,存放有2个字段的结构体。 #include <iostream> #include <string> using namespace std; struct Student { string name; int age; }; void main( ) { Student *p; p = new Student; p->name = “zhangsan"; p->age = 20; cout << "name = "<< p->name <<";" cout <<"age = "<<p->age<<endl; delete p; }
本章作业 输入15个数,用函数求和与平均值。
作业讲解 void main() { double a[15],s=0,ave=0; for(int i=0;i<15;i++) { cout<<"输入第"<<(i+1)<<"个实数:"; cin>>a[i]; } for(i=0;i<15;i++) s+=a[i]; ave=s/15.0; cout<<"总和 = "<<s<<endl; cout<<"均值 = "<<ave<<endl;
习题: 本章习题请上网查阅教学网页: http://staff.ustc.edu.cn/~leeyi