C++面向对象程序设计之C++的初步知识
本节内容为学习谭浩强老师编写的《C++面向对象程序设计》的第1章 C++的初步知识 后的个人总结。
在正文开始之前,首先声明,我是Python程序员。
1.2.最简单的C++程序
例1.1 输出一行字符:"This is a C++ program"
1 #include <iostream> //用count输出时需要用此头文件 2 using namespace std; //使用命名空间std 3 int main() 4 { cout <<"This is a C++ program. \n"; //用C++的方法输出一行 5 return 0; 6 }
程序分析:
- main函数必须声明为int型,即此主函数返回一个整型的函数值。
- 单行注释:"//注释的内容";多行注释:"/*多行注释内容*/"。
- 在C++中一般用cout进行输出,cout实际上是C++系统定义的对象名,称为输出流对象。"<<"是“插入运算符”,与cout配合使用,在本例中的作用是将字符串插入到输出的队列cout中。
- 使用cout需要用到头文件iostream,i表示input,o表示output,stream表示“流”,iostream表示“输入输出流”。
- 程序的第二行"using namespace std;"的意思是“使用命名空间std”。如果程序有输入输出时,必须使用“#include <iostream>” 指令以提供必要的信息,同时要用“using namespace std;”语句使程序能够使用这些信息,否则程序编译时将出错。
例1.2 求a和b两个数之和
#include<iostream> using namespace std; int main() //主函数首部 { int a,b,sum; //定义变量 cin>>a>>b; //输入语句,输入流对象,提取运算符,从键盘提取a和b sum = a+b; //赋值语句 cout<<"a+b="<<sum<<endl; //输出语句 return 0; //如果程序正常结束,向操作系统返回一个零值 }
程序分析:
- endl是end line的缩写,表示本行结束,与"\n"作用相同。
- cin和>>组合,cin是输入流对象,>>是提取运算符,可以理解为从键盘提取a和b。
- 当运行时,输入数字时应注意,两个数字间应加一个空格。
例1.3 从键盘输入两个数a和b,求两数中的大者
1 #include<iostream> 2 using namespace std; 3 int main() //主函数首部 4 { 5 int max(int x, int y); //对max函数做声明 6 int a,b,c; 7 cin>>a>>b; 8 c = max(a,b); //调用max函数 9 cout <<"max="<<c<<endl; 10 return 0; 11 } 12 13 int max(int x, int y) //定义max函数 14 { 15 int z; 16 if(x>y) z=x; 17 else z = y; 18 return(z); 19 }
程序分析:
- 学会C++中的声明函数,调用函数,定义函数思想。
- 学会比较并赋值的思想:if(x>y) z=x;else z = y; 如果x>y,则将大的值x赋值给Z,否则将大的值y赋值给z。
例1.4 包含类的C++程序
1 #include <iostream> 2 using namespace std; 3 class Student //声明一个类,类名为Student 4 { 5 private: //以下为类中的私有部分 6 int num; //私有变量num 7 int score; //私有变量score 8 public: //以下为类中的公用部分 9 void setdata() //定义公用函数stedata 10 { 11 cin>>num; 12 cin>>score; 13 } 14 void dispaly() //定义公用函数display 15 { 16 cout<<"num="<<num<<endl; 17 cout<<"score="<<score<<endl; 18 } 19 }; //类的声明结束 20 Student stud1,stud2; //定义stud1和stud2为Student类的变量,称为对象 21 22 int main() 23 { 24 stud1.setdata(); //调用对象stud1的setdata函数 25 stud2.setdata(); //调用对象stud2的setdata函数 26 stud1.dispaly(); //调用对象stud1的dispaly函数 27 stud2.dispaly(); //调用对象stud2的dispaly函数 28 return 0; 29 }
程序分析:
- 可以类比python中的类,声明类-->定义类,定义类的变量-->用对象调用类的方法,调用对象的函数-->调用对象的函数
- 掌握定义类和类的方法,定义类的变量即对象,学会调用对象的方法。
- class是声明“类”类型时必须使用的关键字,一个类是由一批数据以及对其操作的函数组成的。
- 凡是被指定为公用的数据或函数,既可以被本类的成员函数调用,也可以被类外的语句所调用。被指定为私有的成员(函数或数据)只能被本类中的成员函数所调用,而不能被类以外调用(以后介绍的友元“类成员”以外)。
-
Student stud1,stud2;是一个定义语句,它的作用是将stud1和stud2定义为Student类型的变量。
- 对象是占实际存储空间的,而类型并不占实际存储空间。
- 用了带后缀的“.h”的头文件时不必用“using namespace std;”做声明。
1.3 C++对C的扩充
1.3.1 C++的输入输出
流名 | 含义 | 隐含设备 |
cin | 标准输入 | 键盘 |
cout | 标准输出 | 屏幕 |
ceer | 标准出错输出 | 屏幕 |
clog | ceer的缓冲形式 | 屏幕 |
如果要指定输出所占列数,可以用控制符setw进行设置,如setw(5)的作用是为其后面一个输出项预留5列的空间,如果输出数据项的长度不足5列,则数据向右对齐,若超过5列则按实际长度输出。如下所示:
1 #include<iomanip> 2 #include<iostream> 3 using namespace std; 4 int main() 5 { 6 float a=3.45; 7 int b=5; 8 char c='A'; 9 //cout << "a="<<a<<","<< "b="<<b<<","<< "c="<<c<<endl; 10 //cout << "a="<<setw(6)<<a<<","<< "b="<<setw(6)<<b<<","<< "c="<<setw(6)<<c<<endl; 11 cout << "a="<<a<<endl; 12 cout << "b="<<b<<endl; 13 cout << "c="<<c<<endl; 14 cout << "a="<<setw(6)<<a<<endl; 15 cout << "b="<<setw(6)<<b<<endl; 16 cout << "c="<<setw(6)<<c<<endl; 17 system("pause"); 18 return 0; 19 }
运行结果:
代码分析:
- 若使用setw,应当在程序的开头包含头文件iomanip(或iomanip.h)。
- 如果执行窗口总是执行下就消失,可以在开头加上using namespace std;在return之前加上system("pause");就可解决。
1.3.2 用const定义常变量
在C语言中常用#define指令来定义符号常量,如#define PI 3.14159。
实际上,只是在预编译时进行字符置换,把程序中出现的字符串PI全部置换成3.14159.在预编译之后,程序中不再有PI这个标识符。PI不是变量,没有类型,不占用存储单元,而且容易出错。如下:
1 #include<iostream> 2 using namespace std; 3 int main() 4 { 5 int a=1;int b =2; 6 #define PI 3.14159 7 #define R a+b 8 cout <<PI*R*R<<endl; 9 10 system("pause"); 11 return 0; 12 }
输出结果为 7.14159,即输出的并不是3.14159*(a+b)*(a+b),而是3.14159*a+b*a+b。程序因此常常出错。
C++提供了用const定义常变量的方法,如下:
1 #include<iostream> 2 using namespace std; 3 int main() 4 { 5 int a=1;int b =2; 6 const float PI=3.14159; 7 //#define R a+b; //此句有错误 8 const R=a+b; 9 cout <<PI*R*R<<endl; 10 11 system("pause"); 12 return 0; 13 }
输出结果为28.2743 。
程序分析:
- const定义了常变量PI,它具有变量的属性,有数据类型,占用存储单元,有地址,可以用指针指向它,只是在程序运行期间变量的值是固定的,不能改变。
- const常与指针结合使用,有指向常变量的指针,常指针,指向常变量的常指针等。
1.3.3 函数原型声明
C++中,强制要求在函数调用之前必须对所调用的函数做函数原型声明。即:int max(int x, int y); //max函数原型声明。
参数表中一般包括参数类型和参数名,也可以只包括参数类型而不包括参数名,如下两种写法等价:
int max(int x, int y); //等价于下面 int max(int , int);
1.3.4 函数的重载
插入,提取运算符"<<",">>",本来是C和C++位运算中的左移运算符和右移运算符;重载其实就是一物多用。
C++允许在同一作用于中用同一函数名定义多个函数,这些函数的参数个数和参数类型不相同,这些同名函数用来实现不同的功能。这就是函数的重载,即一个函数名多用。
#include<iostream> using namespace std; 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, float c) //求三个整数中的最大者 { if (b>a) a=b; if (c>a) a=c; return a; } long max(long a,long b, long c) //求三个整数中的最大者 { if (b>a) a=b; if (c>a) a=c; return a; } int main() { int a,b,c; float d,e,f; long g,h,i; cin>>a>>b>>c; cin>>d>>e>>f; cin>>g>>h>>i; int m; //函数值为整型 m=max(a,b,c); cout<<"max_f="<<m<<endl; float n; //函数值为实型 n=max(d,e,f); cout<<"max_f="<<n<<endl; long int p; //函数值为长整型 p=max(g,h,i); cout<<"max_f="<<p<<endl; }
程序分析:
- main函数3次调用max函数,每次实参的类型都不同。系统会根据实参的类型找到与之匹配的函数,然后调用该函数。
- 上述的是参数个数相同而类型不同,实际上参数个数也可以不同而类型相同,例如“用一个函数求2个整数或者3个整数中的最大值”;至于参数不同,类型也不同还需读者自行研究。
1.3.5 函数模板
函数重载可以实现一个函数名多用,将实现相同或类似功能的函数用同一个函数名来定义。但3个函数内容基本相同,会显得太过冗长,为了简化,C++提供了函数模板。即建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代替,这个通用函数就称为函数模板。
3个整数选出最大值代码优化如下:
#include<iostream> using namespace std; template<typename T> //模板声明,其中T为类型参数 T max(T a,T b,T c) //定义一个通用函数,用T做虚拟的类型名 { if (b>a) a=b; if (c>a) a=c; return a; } int main() { int i1=8,i2=5,i3=6,i; double d1=82.2,d2=53.4,d3=69.7,d; long g1=78238,g2=-365,g3=45676,g; i=max(i1,i2,i3); //调用模板函数,此时T被int代替 d=max(d1,d2,d3); //调用模板函数,此时T被double代替 g=max(g1,g2,g3); //调用模板函数,此时T被long代替 cout<<"max_i="<<i<<endl; cout<<"max_d="<<d<<endl; cout<<"max_g="<<g<<endl; system("pause"); return 0; }
函数分析:
- 学会将多个函数内容相似的代码整合成一个函数。
- 模板声明和定义虚拟的类型名;调用函数,理解调用函数所做的替换。
- 模板函数的类型参数可以布置一个,根据需要确定个数。如:template<typename T1,typename T2>.
- 应注意,模板函数只适用于函数的参数个数相同而类型不同,且函数体相同的情况,如果参数的个数不同,则不能用函数模板。
1.3.6 有默认参数的函数
一般情况下,在函数调用时形参从实参那里取值,因此实参的个数应与形参相同。同时我们也可以给形参设置一个默认值。
如有一函数声明:
float area(float r=6.5); //指定r的默认值为6.5。 area(); //相当于area(6.5) area(7.5); //形参得到的值为7.5,而不是6.5
如果有多个形参,可以使每个形参都有一个默认值,也可以只对一部分形参指定默认值,另一部分不指定默认值。
实参与形参的结合是从左至右顺序进行的,因此指定默认值的参数必须放在形参表列中的最右端,否则出错。例如:
void f2(float a, int c, int b=0,char d='a'); //正确写法,默认值在形参表列中的最右端 //函数调用 f2(3.5, 5, 3, 'x') //形参的值全部从实参获得 f2(3.5, 5, 3) //******最后一个形参的值取默认值'a' f2(3.5, 5) //最后两个形参的值取默认值,b=0,d='a'
注意:一个函数不能既作为重载函数,又作为有默认参数的函数。
1.3.7 变量的引用
1.引用的概念
在C++中,变量的“引用”就是变量的别名,因此引用又称为别名。
假如有一个变量a,想给它起一个别名b,可以这样编写:
int a; int &b=a; //声明b是一个整型变量的引用变量,它被初始化为a
这就声明了b是a的“引用”,即a的别名。
对变量声明一个引用,并不另外开辟内存单元,b和a都代表同一变量单元。
当b作为了a变量的引用后,不能再作为其他变量的引用。
2.引用的简单实用
#include<iostream> using namespace std; int main() { int a=10; int &b=a; //声明b是a的引用 a=a*a; //a的值变化了,b的值也应一起变化 cout<<a<<b<<endl; b=b/5; //b的值变化了,a的值也应一起变化 cout<<b<<a<<endl; system("pause"); return 0; }
3.关于引用的简单说明
- 引用及被引用对象必须是相同类型,即整型对整型,浮点对浮点。
- 引用与其所代表的变量共享同一内存单元,系统并不为引用另外分配分配存储空间。
- 当&a的前面有类型符时(如int &a),它必然是对引用的声明;如果前面没有类型符(如p=&a),此时的&是取地址运算符。
- 当b作为了a变量的引用后,不能再作为其他变量的引用。
- b是a的引用,c也可以是b的引用,即可以用一个引用初始化另一个引用,其中a,b,c是同一地址;但存在一个疑问,书上显示“整型变量a有两个别名,即a和b”,为什么a的别名不是b和c?不知是书上的错误还是理解不够深刻。经过测试,a,b,c的空间地址是相同的。代码如下:
#include<iostream> using namespace std; int main() { int a=3; int &b=a; int &c=b; cout<<&a<<" "<<endl; //输出a的内存地址 cout<<&b<<" "<<endl; //输出b的内存地址 cout<<&c<<" "<<endl; //输出c的内存地址,结果显示a,b,c的地址相同 system("pause"); return 0; }
4.将引用作为函数参数
4.1.将变量名作为实参
例1.10 无法实现两个变量的值互换的程序
#include<iostream> using namespace std; void swap(int a,int b) { int temp; temp = a; a=b; b=temp; } int main() { int i=3,j=5; swap(i,j); cout<<i<<","<<j<<endl; //3,5 不能实现互换 system("pause"); return 0; }
程序分析:在本例中,a和b开辟临时空间,a和b的空间换了,但i和j的内存空间不变,随着程序结束,a和b的内存被释放。
4.2.传递变量的指针
例1.11 使用指针做形参,实现两个变量的值的互换
#include<iostream> using namespace std; void swap(int *p1,int *p2) { int temp; temp = *p1; *p1=*p2; *p2=temp; } int main() { int i=3,j=5; swap(&i,&j); cout<<i<<","<<j<<endl; //5,3 实现两个变量的互换 system("pause"); return 0; }
程序分析:p,指向整数的指针;*p1指向i的指针,*p2指向j的指针,i,j实现互换内存。
4.3.传递变量的别名
因为用传递指针的方法比较麻烦,所以可以用传递别名的方法解决这个不足。
例1.12 使用“引入实参”实现两个变量的值的互换
#include<iostream> using namespace std; void swap(int &a,int &b) { int temp; temp = a; a=b; b=temp; } int main() { int i=3,j=5; swap(i,j); cout<<i<<","<<j<<endl; //5,3 实现变量的互换 system("pause"); return 0; }
在C++调用函数时有两种传递数据的方式,一种是常用的方法:将实参的值传给形参,形参是实参的拷贝,这种方式称为传值方式调用。另一种是将实参的地址传给引用型形参,这时形参与实参是同一个变量,这种方式称为引用方式调用。
引用小结:引用其实就相当于python中的赋值操作,不会开辟新的内存空间,它只是复制了对象的引用,也就是说除了b这个名字之外,没有其他的内存开销。修改了a,也就二印象了b,同理,修改了b,也就影响了a。
1.3.8 内置函数
指定内置函数的方法很简单,只需在函数首行的左端加一个关键字inline即可。
例1.13 将函数指定为内置函数
#include<iostream> using namespace std; inline int max(int a,int b,int c) //这是一个内置函数,求3个整数中的最大者 { if (b>a) a=b; if (c>a) a=c; return a; } int main() { int i=7,j=10,k=25,m; m=max(i,j,k); cout<<"max="<<m<<endl; system("pause"); return 0; }
程序分析:
- 调用函数需要时间,内置函数在编译时直接将所调用函数嵌入到其中;即原来是调用,现在是替换后直接使用;
- 使用内置函数可以节省运行时间,但却增加了目标程序的长度。加入一个函数4行调用10次,原来4行,那么现在是40行了,大大增加了main函数的长度。因此只有对于规模很小且使用频繁的函数,才可大大提高运行速度。
1.3.9 作用域运算符
即全局变量和局部变量;全局变量用"::变量名"表示,如"::a"表示全局作用域中的变量a。C++提供作用域运算符":: ",它能指定所需要的作用域。
#include<iostream> using namespace std; float a=13.5; //定义一个浮点型的全局变量a int main() { int a=5; cout<<a<<endl; //输出局部变量a的值 cout<<::a<<endl; //输出全局变量a的值 system("pause"); return 0; }
程序分析:
- 在main函数中局部变量将屏蔽全局变量。
- C++提供作用域运算符":: ",它能指定所需要的作用域。但应注意,不能用":: "访问函数中的局部变量。
1.3.10 字符串变量
双引号--->字符串,即多个字符
单引号--->字符,即单个字符
1.定义字符变量:
string string1; //定义string1位字符串变量 string string2="China"; //定义字符串string2同时对其初始化 //要使用string类的功能时,在开头应包含“string”头文件,即#include<string> 没有.h
2.字符串变量的赋值
string0="Canada"; string2=string1; string word="Then"; //定义并初始化字符串变量word word[2]='a'; //修改序号为2的字符,修改后word的值为"Than"
3.字符串变量的输入输出
cin<<string1;
cout>>string2;
4.字符串变量的运算
string1=string2; string string1="C++"; string string2="Language"; string1=string1+string2; // ==,<,>,!=,>=,<=
5.字符串数组
不仅可以用string定义字符串变量,也可以用string定义字符串数组。如:
#include<iostream> using namespace std; int main() { //string name[5]; //定义一个字符串数组,它包含5个字符串元素 string name[5]={"Zahng","Li","Fan","Wang","Tan"}; //定义一个字符串数组并初始化 cout<<sizeof(string)<<endl; //书上为4,实际两个都为16 cout<<sizeof(name)<<endl; //书上为20,两个都为80 sizeof(string); //空 sizeof(name); //空 system("pause"); return 0; }
例1.15 输入三个字符串,要求按字母从小到大顺序输出
//暂不列举。
1.3.11 动态分配/撤销内存的运算符new和delete
C++提供了较简便而功能较强的运算符new和delete来取代malloc和free函数(为了兼容C,任然保留)。例如:
new int; //开辟一个存放整数的空间,返回一个指向整型数据的指针 new int(100); //开辟一个存放整数的空间,并制定该整数的初值为100 new char[10]; //开辟一个存放字符数组的空间,该数组有10个元素,返回一个指向字符数据的指针 new int[5][]4; //开辟一个存放二维整形数组的空间,该数组大小为5*4 float *p=new float(3.14159) //开辟一个存放实数的空间,并指定该实数的初值为3.14159,将返回的指向实型数据的指针赋给指针变量p
new运算符使用的一般格式为:new 类型[初值]; 用new分配数组空间时不能指定初值。
delete运算符使用的一般格式为:delete []指针变量;例如撤销例子5开辟的存放实数的空间:deletep;
new和delete是运算符,不是函数,因此执行效率高。用了new后要用delete释放空间。
习题
注:因为只是习题而已,没有写的多认真,有些部分是复制前面写的代码,故仅供参考。
1.略
2.略
3.略
4.略
5.略
6.略
7.求2个或3个数中的最大值,用带有默认参数的函数实现。
#include<iostream> using namespace std; int max_3(int a,int b=1,int c=2) { if(b>a)a=b; if(c>a)a=c; return a; } int max_2(int a,int b=2) { if(b>a) return b; else return a; } int main() { //重载函数不能带默认值 int a,b,c,d,e; cout<<"如果要比较三个数字中的最大数,请在前三位输数字,后两位输0"<<endl; cout<<"如果要比较两个数字中的最大数,请在前三位输0,后两位输数字"<<endl; cin>>a>>b>>c>>d>>e; if(a!=0,b!=0,c!=0) cout<<"max_3="<<max_3(a,b,c)<<endl; else if(d!=0,e!=0) cout<<"max_2="<<max_2(d,e)<<endl; //下面这两个符合默认参数的题意,但暂时无法做到从键盘获取参数 cout<<"max_3="<<max_3(0)<<endl; cout<<"max_2="<<max_2(77,88)<<endl; system("pause"); return 0; }
8.输入两个整数,按从大到小顺序输出,要求使用变量的引用。
#include<iostream> using namespace std; void sort(int &a,int &b) { int temp; if(a<b) {temp=b;b=a;a=temp;} cout<<a<<","<<b<<endl; }; int main() { int a,int b; cin>>a>>b; sort(a,b); system("pause"); return 0; }
9.对3个变量按从小到大排序,要求使用变量的引用。
注:或能力有限,感觉本题题意不清,是3个什么变量?是整型,实型,长整型,浮点型,双精度,字符,亦或是字符串?本例中使用的是模板函数,虽类型不明确,但能使用函数解决大部分的类型。
#include<iostream> using namespace std; template<typename T> //模板声明,其中T为类型参数 void sort(T &a,T &b,T &c) //定义一个通用函数,用T做虚拟的类型名 { int temp; if(a>b) {temp=a;a=b;b=temp;} if(c<a) cout<<c<<','<<a<<','<<b<<endl; else if(c<b) cout<<a<<','<<c<<','<<b<<endl; else cout<<a<<','<<b<<','<<c<<endl; } int main() { int i1=8,i2=5,i3=6; double d1=82.2,d2=53.4,d3=69.7; long g1=78238,g2=-365,g3=45676; char c1='c',c2='h',c3='d'; sort(i1,i2,i3); //调用模板函数,此时T被int代替 sort(d1,d2,d3); //调用模板函数,此时T被double代替 sort(g1,g2,g3); //调用模板函数,此时T被long代替 sort(c1,c2,c3); system("pause"); return 0; };
10.编一个程序,将两个字符串链接起来,结果取代第一个字符串。要求用string方法。
#include<iostream> #include<string> using namespace std; int main() { string string1,string2,string3; string1="Hello"; string2="World"; string3=string1+string2; string1=string3; cout<<"string1="<<string1<<endl; cout<<"string2="<<string2<<endl; system("pause"); return 0; }
11.用string方法,输入一个字符串,并逆向输出。
#include<iostream> #include<string> using namespace std; int main() { int ret,i; string string1,string2; cin>>string1; //string1="Hello"; string2=string1; ret = string1.length(); for (i=0;i<ret;i++) string2[i]=string1[ret-1-i]; cout<<string2<<endl; system("pause"); return 0; }
12.有5个字符串,要求对它们按由小到大的顺序排列,用string方法。
参考例1.15
13.编一个程序,用同一个函数名对n个数据进行从小到大排序,数据类型可以是整型,单精度型,双精度型。用重载函数实现。
注:因为本人对C++的数字类型不太了解,故仅供参考。-------------始终觉得,本题又是题意不清。。。是n个相同类型的数据,还是n个数据可以不同类型,如果是不同类型,根据13,14题的语境,本题应用类似于例1.6的方法处理,可是例1.6函数重载的例子又是3个相同类型的。-------------目前还没学习怎么处理不固定数量的输入数据,故我只处理3个数据,可以提供一个思路,用递归处理,毕竟,如果用户输入10个数甚至更多但还是用这种方法的话,会累死。
#include<iostream> using namespace std; int sort(int a,int b, int c) { int temp; if(a>b) {temp=a;a=b;b=temp;} if(c<a) cout<<c<<','<<a<<','<<b<<endl; else if(c<b) cout<<a<<','<<c<<','<<b<<endl; else cout<<a<<','<<b<<','<<c<<endl; return 0; } float sort(float a,float b, float c) { float temp; if(a>b) {temp=a;a=b;b=temp;} if(c<a) cout<<c<<','<<a<<','<<b<<endl; else if(c<b) cout<<a<<','<<c<<','<<b<<endl; else cout<<a<<','<<b<<','<<c<<endl; return 0; } long sort(double a,double b, double c) { double temp; if(a>b) {temp=a;a=b;b=temp;} if(c<a) cout<<c<<','<<a<<','<<b<<endl; else if(c<b) cout<<a<<','<<c<<','<<b<<endl; else cout<<a<<','<<b<<','<<c<<endl; return 0; } int main() { int a,b,c; float d,e,f; double g,h,i; cin>>a>>b>>c; cin>>d>>e>>f; cin>>g>>h>>i; sort(a,b,c); sort(d,e,f); sort(g,h,i); system("pause"); return 0; }
14.对13题改用函数模板实现,并进行对比分析。
题9和13的结合一下。
--------------------------------------------------------------------------------------------------本节结束---------------------------------------------------------------------------------------------------