1.07k likes | 1.3k Views
清华大学计算机系列教材 殷人昆 陶永雷 谢若阳 盛绚华 编著. 数据结构 (用面向对象方法与 C++ 描述). 编程简介. C++. C++ 语言的概要 类、对象、构造函数与析构函数 输入 / 输出 函数、参数传递与函数返回值 函数名重载与操作符重载 动态存储分配 友元函数与内联函数 结构、联合与类. C++ 语言概要. C++ 源于 C 语言。 1970 年,两位程序员 Brian Kernighan 和 Dennis Ritchie 首创了一种新的程序设计语言,取名为 C 语言。
E N D
清华大学计算机系列教材 殷人昆 陶永雷 谢若阳 盛绚华 编著 数据结构 (用面向对象方法与C++描述)
编程简介 C++ • C++语言的概要 • 类、对象、构造函数与析构函数 • 输入/输出 • 函数、参数传递与函数返回值 • 函数名重载与操作符重载 • 动态存储分配 • 友元函数与内联函数 • 结构、联合与类
C++语言概要 • C++源于C语言。 • 1970年,两位程序员Brian Kernighan和Dennis Ritchie首创了一种新的程序设计语言,取名为C语言。 • 设计C语言的最初目的是编写操作系统。由于其简单、灵活的特点,C语言很快就被用于编写各种不同类型的程序,从而成为世界上最流行的语言之一。
C 语言是一个面向过程的语言。随着软件开发技术的进步, 程序员们最终发现, 把数据和施加在其上的操作结合起来,会得到更易于理解的程序,由此产生了面向对象的程序设计思想。 • 1980年代初,美国 AT & T 贝尔实验室的Bjarne Stroustrup设计并实现了C语言的扩充、改进版本,C++语言诞生了! • C++改进了C的不足之处,增加了对面向对象的程序设计的支持,在改进的同时,保持了C的简洁性和高效性。
//一个简单的C++程序 /* 一个简单的C++程序,该程序在标准输出设备上输出一句问候语“hello” */ #include <iostream.h> int main() { cout << "hello!"; return 0;//正常返回 } • 这是一个只包含一个函数的程序,程序的基本元素是数据说明、函数和注释。
注释 • C++的第一次注释格式源于C语言。注释开始于“/*”,结束于“*/”,在两者之间的任何内容,包括换行符都被编译器忽略。 • 注意注释符对不可以嵌套。 • 第一种注释符以“//”开头,它是单行注释符,在它同一行右侧的任何信息都将被认为是注释而由编译器略去。 • 注意:上面两种注释符的两个标志符/和*,/和/之间不可以分开。
#include 语句 • C++将一些标准函数和变量说明放在头文件中。头文件中保存所有与标准函数或变量相关的信息,为了使用头文件中定义的变量和函数,必须将相应的头文件include进主程序,作为程序的一部分进行编译。 • 用户也可以定义自己的头文件,把一些相关的函数和变量组织在一个文件中,当另外的程序要用到这些函数和变量时,可以将该文件作为头文件include进来。
#include指令有两种格式: #include <头文件名> #include “头文件名” • 第一种文件名在 <>中指定, 表示该文件存放于系统设定的子目录中, 这类文件一般是由系统给出的, 并已经过编译。 • 第二种文件名在 “” 中给出,表示该文件存放在当前目录中, 这些头文件通常由用户自己给出。 • 头文件中可包含其它头文件, 即 #include 可以直接嵌套。
C++源程序中还可包括各种编译命令, 这些命令被称为预处理指令, 常用的除 #include外, 还有条件预处理指令 #if、#ifndef 和#endif等和宏替换指令 #define。 • 预处理命令对编译器起作用, 它指示编译器在正式编译前做一些预先处理。#include命令将指示编译器将其后所跟的文件内容插入到当前文件中;#define定义一个常量或替换宏,它指示编译器在使用该常量或宏的地方替换为其实际内容;#if、#ifndef和 #endif指示编译器做条件编译。
#define用来定义一个常量或替换宏, 如: #define size 20 //定义一个常量size,其值永远为20 #define MAX(x, y) ((x < y) ? y : x) //求x, y中的最大值 • 经过预编译后, 程序中所有出现size和 MAX(x, y)之处都会被 20和 ((x < y) ? y : x)代替,如: int array[size]; int i = MAX(4, 55); • 经预编译后会变为 int array[20]; int i = ((4 < 55) ? 55 : 4);
函数原型 • 下面的程序给出了典型的 C程序结构,它是“Hello, world”程序的变型。 • 这个程序由三个文件组成: /* File: hello.h */ char *hello( );/* File: hello.c */# include <stdio.h> /*包括sprintf ( )的原型*/# include <stdlib.h> /*包括malloc( )的原型*/# include <string.h> /*包括strlen( )的原型*/ # include "hello.h" /*包括hello( )的原型*/
char *hello(name) char *name; {char *value;/*返回串"Hello, name.". */ value = (char *)(malloc(9+strlen(name)); sprintf (value, "Hello, %s.", name);return value; } /* File: main.c */ # include <stdio.h> /*包括printf ( )的原型*/ # include "hello.h" /*包括hello( )的原型*/
main(argc, argv)int argc;char *argv[ ];{printf("%s", hello("world"));} • 头文件名字的后缀用“.h”表示,程序文件名字的后缀用“.c”表示。 • hello.h:包含hello函数的原型。main函数可通过“# include”定向到该原型的定义文件,取得对原型的访问性。
hello.c:这是hello函数的定义文件。它通过一个string类型的形式参数接受需要打印的 串, 返回一个string类型的值作为打印串。返回类型必须与在#include定向的“.h”文件中所给出的原型的类型匹配。 • main.c:这是打印“Hello, world”的主程序,它构造和返回一个欢迎词字符串,其结果通过函数printf打印出来。 • C 把函数和数据定义放在后缀为“.c”的代码文件中。在各代码文件中使用后缀为“.h”的include文件, 定义对其他各模块的调用接口。
C++的函数特征 • 特征是函数参数表的描述。 • 利用特征信息可进行严格的类型检查。它允许编译器检验实际参数的数目是否正确,对应的形参和实参是否相容,函数返回的类型与函数调用的环境是否相容。 • 它克服了在许多 C 程序的开发中,由于在C 原型中没有定义参数的类型和数量,而造成的实参和形参之间不匹配,函数返回类型与使用之间不匹配等许多缺陷。
现用 C++ 语句改写前面的 C 程序。 • C++ 程序在“.h”文件中对 hello( ) 使用了函数特征。对于 hello( ) 的原型: • 不要求形式参数的名字出现在特征中 • 参数的数目和类型,以及返回类型,都完整地在函数说明中定义 • C++允许在函数说明时, 在括号内直接声明形式参数的类型。
/* File: hello.h */char *hello(char *);/* File: hello.cpp */ # include <stdio.h> //包含函数sprintf( )的原型# include <string.h> //包含函数strlen( )的原型# include “hello.h” //包含函数hello( )的原型 char *hello(char *name) {char *value = new char [9 + strlen(name)]; sprintf (value, "Hello, %s.", name);return value;}
/* File: main.cpp */# include <iostream.h> //说明输出流对象cout# include "hello.h" //包含函数hello( )的原型 main(int argc, char *argv[ ]) {cout << hello("world");}
C++的数据声明 • C++的数据声明将数据名与数据类型联系起来。其主要形式有: • 常数值:如25, 13.4, “value is”, 它们的内容保持不变。 • 常量:数据声明时在变量名前冠以保留字const,如 const int MAX = 500, 可定义 一个常量。其内容在声明时给定,在声明它的程序运行时内容再赋值无效。 • 变量:数据类型的实例, 在程序执行时可以改变其内容。
C++提供两大类数据类型:基本数据类型和复合数据类型。C++提供两大类数据类型:基本数据类型和复合数据类型。 • 基本数据类型有 5 种:整型(int)、浮点型(float)、字符型(char)、双精度浮点型(double)和无值(void)。 • 复合数据类型包括结构(struct)、联合(union)、位域、枚举(enum)、类(class)和用户自定义类型。 • 此外还有由基本数据类型和复合数据类型引申而来的数据类型,包括数组、指针、引用等。
枚举:是声明一个整型常数序列的方式。例如, 在程序开头做如下声明 enum Boolean { FALSE, TRUE } 则建立一个Boolean类型。FALSE, TRUE都是Boolean类型整型常数, 默认值 0和 1。 • 指针:存放对象的存储地址,例如 int i = 5; int *np; //np为一个指向整型量的指针 np = &i; //把整型变量i 的地址赋给它 //np 成为指向整型变量 i的指针 int k = *np; //k中存入np所指地址i的内容
引用:它用来给一个对象提供一个替代的名字。例如引用:它用来给一个对象提供一个替代的名字。例如 int i = 5; int& j = i; i = 7; printf (“i = %d, j = %d”, i, j ); 此时, j 是一个引用类型, 它代表i 的一个替代名。当 i 的值改变时, j 的值也跟着改变。当 printf 语句执行后, 打印出的 i 和 j 的值都是7。
C++的作用域 • 在C++中, 每个变量都有一个作用域。区分一个变量时要同时考虑变量名及其作用域。 • 在函数定义中声明的变量, 仅能在该函数内部有效使用 • 在类定义中声明的变量, 仅能在该类内部有效使用 • 在一个段中声明的名字, 也仅能在该段及其子段中有效使用。
在整个程序各处都能访问的变量叫做全局变量。如果一个全局变量在文件1中声明,在文件2中使用,那么在文件2中必须使用保留字extern对该变量进行声明。在整个程序各处都能访问的变量叫做全局变量。如果一个全局变量在文件1中声明,在文件2中使用,那么在文件2中必须使用保留字extern对该变量进行声明。 • 如果在构成一个程序的两个文件中分别声明了两个同名的全局变量,这两个变量分别代表两个不同实体,此时需在两个文件中分别使用保留字static对变量进行声明。 • 如果一个段中的局部变量与一个全局变量同名,且还要在此段中使用该全局变量,此时需利用域操作符::访问该全局变量。
表达式与操作符 • 表达式是用来说明简单计算的。 • C++中的表达式由操作数和操作符组成,它将操作符施加于操作数,最终得到一个结果。结果的数据类型由参加运算的数据类型决定。 a a + b * c + 200 2 * * R (x + y ) / (a – b ) • 其中操作符执行的先后顺序由它们的优先级和结合性决定。
C++提供了很多预定义的操作符,程序员也可以重新定义这些操作符。C++提供了很多预定义的操作符,程序员也可以重新定义这些操作符。 • 算术操作符:+、-、*、/、%。其中*、/、%优先于+、-。括号用来改变计算顺序。计算时,先计算括号内表达式的值,再将计算结果与括号外的数一起计算,如: 4 *(1 + 2)= 4 * 3 = 12 • 取模操作符(%)用于计算两整数相除后得到的余数,如:22 % 7 = 1。 • 注意,%只能用于整数相除,不能对浮点数操作。
赋值操作符 “= ”将其右侧的表达式求出结果, 赋给其左侧的变量。例如: int Value; Value = (2 + 3) * 4; • 赋值表达式运算的结果是右运算元的值, 而结果类型是左运算元的数据类型, 例如: Value = 2.8 * 4//结果为11, 而不是11.2 • 可以连续赋值, 但必须保证各运算元的类型相同。它的处理结果是每个运算元的对象值都为最右侧的运算元值,例如: int i, j; i = j = 0;// i, j都赋为0
复合操作符: • 加a += b 等价于 a = a + b • 减a-= b等价于 a = a-b • 乘a *= b等价于 a = a * b • 除a /= b等价于 a = a / b • 取模a %= b 等价于 a = a % b • 左移一位 a <<= b 等价于 a = a << b • 右移一位a >>= b 等价于 a = a >> b • 按位与 a &= b 等价于 a = a & b • 按位异或 a ^= b等价于 a = a ^ b • 按位或a |= b等价于 a = a | b
自增,自减操作符 • 自增(++), 自减(--) : a++ 等价于 a = a + 1 a--等价于 a = a- 1 • 自增、自减符号既可位于变量的前面,也可位于变量的后面。前缀++表示先将其后的变量值增1,然后将增1后的变量参与表达式运算;而后缀++表示将其前面的变量先参与表达式运算,然后变量本身增1。 • 在单独作为一个表达式时,++a和a++效果一样,都是将变量a自增1。 • 自减操作符含义类似。
若自增(++), 自减(--)符作为一个复杂表达式的一部分时,如:(a++) + b和 (++a) + b效果就不一样:在a、b初值均为1的条件下结果不同。
条件操作符 • 条件操作符是C++中惟一的具有三个运算元的操作符,其形式为: 表达式1 ?表达式2 :表达式3 • 它的运算方式为: 先计算表达式1的值, 如果其值为非零 (true),则表达式2的值就是整个表达式的最终结果, 否则表达式3的值就是整个表达式的值。常见的一个例子为: #define MIN (x, y) ((x < y) ? x : y) • 上例定义了一个求两个数 x 和 y 中的最小值的宏, 其中决定哪一个是最小值用了条件操作符。
语句 • 语句是C++程序中最小的可执行单元。一条语句由一个分号结束。 • 语句可以是简单语句,也可以是复杂语句。 int radius;是声明语句; circum = 2 * PI * radius;是表达式语句; 它由一个表达式后接一个分号形成。 cout << "hello, world"; cin>> Value; 等都是简单语句。这些语句告知计算机该如何定义变量以及如何执行程序。
除简单语句外,C++还定义了一些可以控制程序执行流程的语句, 这些语句提供对控制流的分支和循环功能。 • C++中, 语句缺省都是顺序执行, 如果碰到分支或循环语句, 顺序执行的规则就要改变。此外,C++中还有一些跳转语句。 • 有时还有一些语句需要合在一起作为语法结构中的一条语句,这时需要将这些语句用大括号括起来,形成一个复合语句,复合语句不需要以分号终结。
if 语句 (二分支选择型) • if语句的一般格式为: if ( 条件表达式) 语句; • 如果条件表达式的结果为true(非零值),则执行语句语句,否则跳过这段语句。 • 语句可以有多条, 这时需用大括号 { }将这些语句括起来, 形成一条复合语句。 if (条件表达式) { 语句1; 语句2; …… }
如果希望在条件满足和不满足时分别执行不同语句,则用 else 引入条件不满足时的语句: if (条件表达式) 语句1; else 语句2; • 语句1,语句2也可以是复合语句,不过这时的语句不需以分号结尾。 • 语句1、语句2中又可以出现 if 语句,所以if 语句可以嵌套,不过这时容易带来语义的歧义性。
例if ( ch >= '0' ) if ( ch <= '9' ) cout << “这是一个数字!”; else cout << “这不是一个数字!”; • 这时else 与哪个if 匹配呢?为解决语义上的这种歧义性,C++中规定,else总是与最后一个出现的还没有else与之匹配的if匹配,所以上面一句的else与第二个if匹配,如果程序员想让它与第一个if匹配,可以用大括号将不与else匹配的if语句括起来,使之成为复合语句。
if ( ch >= '0' ) { if ( ch <= '9' ) cout << “这是一个数字!”; } else cout << “这不是一个数字!”; • 条件表达式用于程序有分支语义的场合。 • 下例判断两个数是否从小到大排列,如果不是,则交换两个数: voidswap ( intx1, intx2 ) { if (x1 > x2) { inttemp = x1; x1 = x2; x2 = temp; } }
Switch 语句 (多分支选择型) • switch语句用于有多重选择的场合, 形式为 switch (表达式) { case 值1: 语句组; break;//break可没有 case 值2: 语句组; break;//break可没有 …… case值n: 语句组; break;//break可没有 default: 语句组; }; • 注意case后的数值必须是一个整型的常量表达式,且任意两个选择项不能相等。
当switch语句执行时, 先计算其后的表达式值, 将表达式的值与后面各 case 关键字后所跟选择常量依次比较。 • 如果与某一选择常量相等,则执行其冒号后跟的语句。如果和任何选择常量都不等, 则执行 default 子句后的语句(如果 default子句存在)或什么也不做(如果 default子句不存在)。 • 每个case子句都以break语句结束。break子句的作用是终止当前switch语句的执行。
例: 统计文章中各字母出现的次数。程序每读入一个字符ch, 根据它的值, 将相应的计数值增1, 假定英文大小写不区分。 intaCnt = 0 , bCnt = 0 , … , zCnt = 0; switch (ch) { case 'a':case 'A': aCnt++; break; case 'b':case ’B': bCnt++; break; // case 'z': case ’Z': zCnt++; break; }
循环语句 • 循环语句提供重复处理的能力, 当某一特定条件为 true时, 循环语句就重复执行, 并且每循环一次, 就会测试一下循环条件, 如果为false, 则循环结束, 否则继续循环。 • C++支持三种格式的循环语句:while、 do和 for语句。三者可以完成类似的功能,不同的是它们控制循环的方式。
While 语句 (先判断循环) • while语句的一般形式为: while (条件表达式) 循环体语句 • while循环先计算条件表达式, 当条件表达式的运算结果为true时, 就执行循环体语句。执行一次循环体语句后, 就会重新计算条件表达式, 当表达式的值为 false时,循环结束。 • while循环可能一次也不执行。
下列程序计算输入文件的字符数,并在标准输出上输出文件内容:下列程序计算输入文件的字符数,并在标准输出上输出文件内容: #include <iostream.h> #include <fstream.h> main() { char ch;int count = 0; //字符数计数器 ifstream infile ("data.in", ios::in); while ( infile && infile.get (ch) ) { cout << ch; count++; } cout << "count : " << count; return (0); }
do 语句 (后判断循环) • do语句的一般形式为: do 循环体语句 while (条件表达式); • do 语句先执行循环体语句, 然后计算条件表达式是否为true, 如果是, 则继续执行循环, 否则结束循环。 • 与while 语句不同的是,do循环中的循环体语句至少执行一次, 而while语句当条件第一次不满足时循环体语句一次也不执行。
对字符计数的程序也可以用do语句实现。 # include <iostream.h> # include <fstream.h> int main() { char ch;int count = 0; //字符个数计数器 ifstream infile ("data.in", ios::in); if ( infile && infile.get(ch) ) { do { cout << ch; count++; } while ( infile && infile.get (ch) ); cout << "count : " << count; } return (0); }
for 语句 • for 语句用于预先知道循环次数的情况, 其一般形式为: for ( 初始化语句; 表达式1; 表达式2 ) 循环体语句; • 其中初始化语句可以是一条声明或表达式,用于对循环控制变量进行初始化或赋值。 • 表达式 1用于控制循环结束, 当它的值为 true 时, 继续循环, 为false时终止循环。 • 表达式 2在每次循环执行后改变循环控制变量的值。
具体来说,for循环的执行过程为: 执行初始化语句; 计算表达式1的值; 如果表达式1的值为true: 先执行循环体语句; 再执行表达式2; 然后转向步骤; 如果表达式1的值为false, 则结束循环。
例: 数组初始化 for ( inti = 0; i < size; i++ ) array[i] = 0; i = 0 False i < size True array[i] = 0 i++
跳转语句 • 除了顺序执行和分支、循环外, 有时需要中断一段程序的执行, 跳转到其它地方继续执行, 这时需用到跳转语句。 • 跳转语句包括break、continue和goto语句。 break语句 • break语句将使程序从当前的循环语句 ( do, while, for ) 内跳转出来, 接着执行循环语句后面的语句。Switch语句中也用到了break语句,这时它表示终止当前switch语句的执行,接着运行switch后的语句。
continue语句 • continue语句也用于循环语句, 它不是结束循环, 而是结束循环语句的当前一次循环,接着执行下一次循环。在 while 和 do 循环中, 执行控制权转至对条件表达式的判断,在 for 循环中, 转去执行表达式2。 goto语句 • goto 语句无条件转移程序的执行控制,它总是与一标号(label)相匹配,其形式为: goto 标号;