第一章 C++语言简介
第一节 C++语言的发展简史
C++是由贝尔实验室1979年在C语言的基础上开发成功的。
C++兼容C语言,用c语言写的程序基本上可以不加修改地用于C++。
C++对C的“增强”表现在两个方面:
(1)在原来面向过程的机制基础上,增强了C语言对类型的处理。
(2)增加了面向对象的机制。
C++语言是一种编译式的、通用的、大小写敏感的编程语言,完全支持面向对象的开发模式。
第二节 C++语言的发展简史
一、基本的输入/输出
#include <iostream>
using namespace std;
int main(
{
int a[10];
for(int i=0; i<10;i++)
cin>>a[i];
for(int i=0; i<10; i++)
cout<<a[i]<<""
cout<<endl ;
说明:
①标准输入: cin>>变量1>>变量2>>…>>变量n;
②标准输出: cout<<表达式1<<表达式2<<…<<表达式n;
③使用标准输入cin及标准输出cout前,要在程序的最前面包含:
#include //包含头文件: 输入输出流
using namespace std;//使用命名空间
④换行操作:用语句cout<<endl;或cout<<“\n”;
⑤当连续从键盘读取想要的数据时,以空格、制表符〈Tab〉键或〈Enter〉键作为分隔符。如果第一个字符是空格、〈Tab〉键或〈Enter〉键,则cin会将其忽略并清除掉,继续读取下一个字符。
⑥用户自己定义的类型的数据,不能直接用“>>”和“<<”进入输入/输出,必须对“>>”和“<<”进行运算符重载后才可以使用。
二、头文件和命名空间
头文件
前面例题中#include
每个C++程序由头文件和定义文件组成。头文件作为一种包含功能函数、数据接口声明的载体文件,主要用于保存程序的声明,而定义文件用于保存程序的实现。
头文件是用户应用程序和函数库之间的桥梁和纽带。编译时,编译器通过头文件找到对应的函数库,进而把已引用函数的实际内容导出来代替原有函数,然后再进行翻译。
常用的头文件:
- 标准输入输出流:
- 标准文件流:
- 标准字符串处理函数:
- 标准数学函数:
除了可以使用系统提供的头文件外,程序员还可以定义自己的头文件。
命名空间
一个大型的程序不同模块中出现的标识符之间有可能发生重名现象,这就会引发错误。C++中为了避免名字定义冲突,特别引入了“命名空间”的定义,即namespace。命名空间的作用是为了消除同名引起的歧义。
在程序中使用标准程序库中的标识符时,要写语句 “using namespace std;”。
三、强制类型转换运算符
当不同类型的量进行混合算术运算时,系统自动进行合理的类型转换,也可以强制转换类型。
- 将—种数据类型转换成另—种数据类型
stati_cast < 类型名 >(表达式)
说明: stati_cast—可以省略不写
oneint2=static_cast < int> (oneDouble);
oneint2=int(oneDouble);
oneint2= ( int) oneDouble; //圆括号
oneint2=oneDouble //自动类型转换
例: int b;
float fa=30.45;
b=static_cast<int>(fa) ;
b=int(fa) ;
b=(int)fa; //旧有强制转换的形式
b=fa //自动类型转换
- const_cast<类型名>(表达式)
功能:将常量指针转化成非常量的指针,并且仍然指问原来的对象;或是将常量引用转换成非常量的引用,并且仍然指向原来的对象。
例: int a=10,*q; const int ca=30;
const int *p=&a; //不能使用常量指针p修改a的值
q=const_cast<int *>(p) //不能q=p
*q=20 //变量a的值变为20,不能形成*p=20
p=&ca; //ca的值不能修改
q=const_cast<int *>(p) //不能q=p
*q=40 //*q的值为40,的值变成40
//变量ca的仍然是30
四、函数参数的默认值
C++中,可以在声明函数时为形参指定默认值。
【例1-3】定义带默认值的函数
#include<iostream>
using namespace std;
void func(int a=11,int b=22,int c=33)
{
cout<<"a="<<a<<"b="<<b<<"c="<<c<<endl ; }
int main()
{
func() ;
func(55);
func(77,99);
func (8,88,888);
retun 0;
}
- C++语言规定,定义函数时,只能为函数最后面的连续若干个参数设置默认值,且在调用处也只能缺省后面的连续若干个实参。
例:判断以下函数声明正确与否 //注意不是定义
void df1(int a=2, double b=3.0);//正确
void df2(int a, double b=3.0);//正确
void df3(int a=2, double b);
void f1(int a,int b=2,int c=3); //正确
void f2(int a=1,int b,int c=3) ;
void f3(int a=1,int b=2.int c):
- C++语言规定,在函数调用处只能缺省后面的连续若干个实参,而且所有缺省的实参必须已经有默认值。
例:函数调用示例
函数声明: void func(int a,int b=2,int c=3)
函数调用:func(1,22,33);
func();//错误
func(10,20);
func(5,,9);//错误
C++语言规定,指定默认值时不仅可以用常数,还可以用任何有定义的表达式作为参数的默认值。
例:假设给出如下的函数及变量声明:
int Max(int m,int n)
int a, b;
void func2(int x,int y=MAX(a, b) ,int z=a-b)
{
……}
调用func2(4);等价于func2(4,MAX(a, b) , a-b);
func2(4,9);等价于func2(4,9,a-b);
func2(4,,10);错误
- 注意:函数参数的默认值可以写在声明函数的地方,也可以写在定义函数的地方,但不能在两个地方都写。函数声明表示有这么个函数了,函数定义就是具体实现了。
函数声明:
int fun(int a, int b);
函数定义:
int fun(int a,int b){ int c;c=a+b;return c; }
五、引用和函数参数的传递
- 引用:相当于给变量起了一个别名。别名的地址与引用变量的地址是一样的。程序中使用哪个名字都是允许的。“引用”的定义格式:
类型名 &引用名=同类型的变量名;
例:int x=10;
int &a=x //a就是x的一个引用,可以有多个引用
注意:①对象在引用前必须先初始化;=
②声明中符号“&”的位置无关紧要。
如int& a=x; int & a=x; int &a=x;等效
常引用的格式:
const类型名&引用名=同类型的变量名;
例: int x=10;
int&a=x;//a就是x的一个普通引用
const int &b=x;//b就是x的一个常引用
a=20;//则x=20,b=20
x=30;//则a=30,b=30
b=40;//错误 b是常量
注意:不能通过常引用去修改其引用的变量的值
constT&和T&是不同的类型。当声明变呈并进行初始化时,同类型之间进行初始化是允许的,不同类型之间进行初始化,有些情况下也是允许的。
2. 引用在函数中使用
- 引用作为函数的参数数
在函数在C++中,函数调用时参数的传递有两种方式:传值和传引用。传引用是传递对象的首地址值,形参的改变就意味着实参的改变。
【例1-6】不同的参数传递方式
#include<iostream>
using namespace std;
void SVal(int a,int b)
{
int tmp;
tmp = a; a= b; b= tmp;
cout<<"在SVal()函数中:\t\ta="<<a<<",b="<<b<<endl;
return;
}
void SRef(int& a, int& b) //a、b值互换
{
int tmp;
tmp = a; a = b; b= tmp;
cout<<"在SRcf()函数中:\t\ta= "<<a<<", b="<<b<<endl;
return;
}
int main()
{
int a= 10, b= 20;
cout<<"数据交换前:\t\ta="<<a<<", b="<<b<<endl<<endl;
SVal(a, b);
cout<<"调用SVal()后:\t\ta="<<a<<", b="<<b<<endl<<endl;
a= 10; b= 20;
SRef(a, b);
cout<<"调用SpRcf()后:\t\ta="<<a<<", b="<<b<<endl;
return 0;
}
程序的执行结果:
数据交换前: a=10, b=20
在SVal()函数中: a=20,b=10
调用SVal()后: a=10,b=20
在SRef()函数中: a=20,b=10
调用SRef()后: a=20,b=10
- 引用作为函数返回值
返回引用的函数原型的格式如下:
数据类型 & 函数名(参数列表);
【例1-7】引用作为函数返回值
#include <iostream>
using namespace std;
int oneX=10;
int oneY=20;
int & refValue(int & x)//函数返回值是引用{ return x;}
六、const与指针共同使用
当const与指针共同使用时,其书写的位置不同,语句含义也不同。
1)如果唯一的const位于符号*的左侧,表示指针所指数据是常量,数据不能通过本指针改变,但可以通过其他方式进行修改;指针本身是变量,可以指向其他的内存单元。
例: int a1=10,a2=20.*pa1=&a1;
*pa1=30; //正确,a1变成10
const int *pa2=&a2;//pa2所指的是常量,pa2是变
*pa2=40;//错误
pa2=&a1;//正确
2)如果唯一的const位于符号*的右侧,表示指针本身是常量,不能让该指针指向其他内存地址;指针所指的数据可以通过本指针进行修改。
例: int a1=10,a2=20;
int * const pa2=&a2;//指针变量pa2是常量
pa2=&a1; //错误
*pa2=40; //正确
3)在符号*的左右各有一个const时,表示指针和指针所指数据都是常量,既不能让指针指向其他地址,也不能通过指针修改所指向的内容。
例: int a1=10,a2=20;
const int * const pa1=&a1;//数据和指针都是常量
pa1=&a2;//错误
*pa1=30 ;//错误
int const * const pa2=&a2;//数据和指针都是常量
pa2=&a1;//错误
*pa2=40 ;//错误
记住const的修饰规则:const修饰其左侧的内容;如果const是本行的第一个标识符,则它修饰其右侧的内容。
const int *pa2=&a2; //pa2所指的是常量
int * const pa2=&a2; //指针变量pa2是常量
const int * const pa1=&a1; //数据和指针都是常量
int const *const pa2=&a2; //数据和指针都是常量
七、内联函数
对于需要频繁调用,且代码量少的函数,可以将其定义为内联函数。编译时,编译程序将整个函数体的代码复制到调用该函数的位置。
定义内联函数的格式如下:
inline返回值类型函数名(形参表) {函数体}
如果函数体中有循环语句和switch语句则通常不定义为内联函数。
八、函数的重载
函数重载:C++允许为同一个函数定义几个版本,从而使一个函数名具有多种功能,这称为函数重载。只要分别为不同参数编制相应的函数体,就可以实现各自的功能。
例:函数重载的例子1
#include <iostream>
using namespace std;
int max(int, int);//声明2个整型参数的函数原型
int max(int,int, int);//声明3个整型参数的函数原型
int main()
{
cout<<max(36,25)<<","<<max(25,39,35)<<endl;};
int max(int m1, int m2) //定义2个整型参数的函数
{
return (m1>m2) ? m1:m2;}
int max(int m1,int m2,int m3) //定义3个整型参数的函数
{
int t = max(m1,m2);return max(t,m3);}
程序运行结果36.39
例:函数重载的例子2
#include <iostream>
using namespace std;
int bigger(int x, int y)
{
if(x>y) return x; else return y;}
float bigger(float x, float y)
{
if(x>y) return x; else return y;}
double bigger(double x, double y)
{
if(x>y) return x; else return y;}
int main(){
int x1=10,y1=20;
float xF=30,yF=40;
double xD=50,yD=60;
cout<<bigger(x1,y1)<<endl;
cout<<bigger(xF,yF)<<endl;
cout<<bigger(xD,yD)<<endl;
}
实现函数的重载必须满足下列条件之一:
- 参数表中对应的参数类型不同;
- 参数表中参数个数不同;
- 参数表中不同类型参数的次序不同。注意:
1)两个函数的名字和参数表都是一样的,仅仅是返回值类型不同,则这两个函数不是重载的。
例:错误的重载函数
float add(int,float);
int add(int,float);//错误
2)函数的参数采用引用的,不能区分函数,则这两个函数不是重载的。
例:错误的重载函数
void print(double);
void print(double &);//错误
3)函数调用可能会引发二义性,不能采用函数重载。例:若定义了重载函数Sum(),如下所示:
int Sum(int a, int b, int c = 0);
int Sum(int a, int b);
则函数调用语句:Sum(1,2);//调用会产生二义性
4)调用语句中的实参与函数形参的类型不完全匹配,但存在赋值兼容的情况。此时,编译器也可以确定要调用的函数。
例:调用函数时进行必要的类型提升
double bigger(double x, double y){
if(x>y) return x;
else return y;
}
int xl= 10,yl= 20;
float xF = 30,yF = 40;
double xD= 50, yD = 60;
cout<<bigger(xl.yF)<<endl: //int与float自动转换为double
九、指针和动态内存分配
1.指针:即指针变量,该变量储存的是一个地址,是该指针所指对象的首地址。
int a=100,*pa=&a;
int s[10],ps=s; //指针ps指向数组s的首地址
2.动态内存分配
动态分配内存一般格式为:
指针名= new 类型名;//分配
delete 指针名; //释放
当不再使用这个空间时,必须使用delete释放空间。若使用new运算符动态分配了一个数组,那么释放该数组时,语句如下: delete[]指针。
【例】演示使用new和dcletc的例子。
#includc <iostrcam>
using namespace std;
int main( ){
double*p;//声明double型指针
p=new double[3];
//分配3个doublc型数据的存储空间
for(int i= 0; i<3; i++) //定义对象i的初值为0
cin >> * ( p +i); //将输入数据存入指定地址
for(int j=0; j<3; j++)
cout <<*(p +j)<<" "; //将地址里的内容输出
delete []p;//释放空间}
十、用string对象处理字符串
C语言用字符数组来处理字符串,C++提供string数据类型来处理字符串。
1.声明string对象
例: string strl;//声明string对象strl,值为空
string city="Beijing”;//声明string对象并初始化
string str2=city; //使用字符串变量进行初始化
char name[]=“C++程序”;
string str3= name;//使用字符数组对string变量进行初实例化
string citysf]= {
"Beijing","Shanghai","Tianjing”};//声明string对象数组,每个数组元素都是字符串
说明:1)使用string对象,必须#include ;
2) string对象储存的是字符串的首地址,非字符串本身;sizeof(string)在32位的Dev C++中是4,在64位的Dev C++中是8。
2.string对象的操作
①、string对象可以使用cin和cout进行输入和输出
例: string s1, s2;
cin>>s1>>2;
cout<<s<<","H<<s2<<endl;
②、string对象之间可以互相赋值,也可以用字符串常量和字符数组的名字对string对象进行赋值。
例: string sl,s2 =”OK";
sl=”China”";
s2 = s1; //赋值后s2的内容和si相同
③、string对象之间可以用“>”,“=”,“==”,“<=”,'<”,=”运算符进行比较。大小的判定标准是按字典序进行的,而且是大小写相关的。
例: bool b;/llC++新增了bool类型,该类型只有两个取值1或0,1表示“真”,0表示“假”。
string sl=”China",s2 =”OK";b=sl>s2;l//变量b的值为0
④、使用运算符“+”对字符串进行连接
例: string sl="China",s2 ="OK",s3=" ".
sl=sl+s3+s2;//连接后sl的内容“China OK”
3、string 类中的常用成员函数
【例1-9】字符串成员函数的使用
#include<iostream>
#include <string.h>
using namespace std;
int main()
{
string str;//未初始化,空串
if(str.empty())
cout<<"str is NULL."<<" ,length="<<str.length()<<endl;
cout<<"str is not NULL."<<endl;
str = str.append("abcdefg");//字符串连接到当前字符串的结尾处
cout<<"str is "<<str<<", size="<<str.size()<<str.length()<<endl;
cout<<"length="<<str.length()<<endl;
const char* p = str.c_str();//p指向了字符串
strcout<<"p=“<p<<endl;l/l输出p=abcdefg
cout<<"find: "<<str.find("de", 0)<<cndl; //从str的第0查找字符串"dc",成功,返回3
cout<<"find: "<<str.find( "dc",4 )<<endl;//查找失败,返回-1对应的无符号数
string strl = str.insert(4,"123");/从str的第4位置插入“123"
cout<<strl<<endl;//输出abcd123efg
return 0;
}
第三节 C++语言的程序结构
①、C++程序以.cpp作为文件扩展名,文件中包含若干个类和若干个函数。
②、程序中有且仅有一个主函数main(),程序从主函数的开始处执行,在主函数结束。
程序的结束通常是遇到了以下两种情形之一。
1)在主函数中遇到return语句。
2)执行到主函数最后面的括号}。
③、主函数中可以调用程序中定义的其他函数,但其他函数不能调用主函数,其他函数直接可以相互调用。
④、C++程序中注释有以下两种形式。
1)从/开始,到/结束,如:/………/
2)从//直到行尾,如://……
⑥、C++的格式和c一样,都很自由,一行可以写几条语句,但也要注意错落有致,增加可读性。
第二章 面向对象的基本概念
第一节 结构化程序设计
-
结构化程序设计的基本方法:采用自顶向下、逐步求精及模块化的思想,将复杂的大问题层层分解为许多简单的小问题。
-
结构化程序设计的三种基本结构:顺序结构、选择结构、循环结构。
-
结构化程序设计的基本思想:数据结构+算法=程序
-
结构化程序设计的缺点:程序在代码规模庞大时,变得难以理解、难以扩充、难以查错和难以复用。
这种情况下,面向对象的程序设计方法就应运而生了
第二节 面向对象程序设计的概念和特点
一、面向对象思想的提出
-
面向对象程序设计方法是20世纪90年代以来软件开发方法的主流,它继承了结构化程序设计方法的优点,同时又比较有效地改善了结构化程序设计的不足。
-
面向对象的程序设计方法:使分析、设计和实现一个系统的方法尽可能地接近人们认识一个系统的方法。通常包括3个方面:面向对象的分析、面向对象的设计和面向对象的程序设计。
-
面向对象技术把问题看成是对象的集合。对象具有两个特性:一是对象本身的信息,也称为属性;一是对对象的操作,也称为行为。
-
对象=数据(属性)+函数(行为)。
-
类是对象的一个抽象。
二、面向对象程序设计的特点
面向对象程序设计的四个基本特点:抽象、封装、继承和多态。
1)抽象:将同一类事物的共同特点概括出来,这个过程就叫作“抽象”。
类是对现实世界中客观事物的抽象。对于一个具体的类,它有许多具体的个体,这些个体叫做“对象”
对象是系统中用来描述客观事物的一个实体,用对象名、属性和操作三要素来描述对象。
描述属性的数据称为成员变量或数据成员,对这组属性进行操作的函数称为成员函数。对象由数据成员和成员函数构成。
例:用简单对象表示平面上的A (3.5,6.4)两个坐标点。可用如下的对象结构图表示A的对象结构:
例: string类的简化图—实际很复杂
①string:类名;
②str:属性;
③string:构造函数,用来初始化字符串;
④find:成员函数,用来在str字符串中检索需要的子串;
⑤size:成员函数,计算并输出str存储的单词长度;
⑥substr成员函数,用来返回str字符串中的子串。
2)封装:就是把对象的属性和操作结合成一个独立的单元。
封装作用:
- 数据和操作数据的函数紧密联系起来;
- 将对象的一部分属性和函数隐藏起来,让这部分属性和函数对外不可见,起到保护作用。
- 另一些属性和函数对外可见,作为对对象进行操作的接口
3)继承:在编写一个“新”类的时候,以现有的类作为基础,使得新类从现有的类“派生”而来,从而达到代码扩充和代码复用的目的。
4)多态:不同的对象可以调用相同名称的函数,但可导致完全不同的行为的现象称为多态性。利用多态性,程序中只需进行一般形式的函数调用,函数的实现细节留给接受函数调用的对象,这大大提高了人们解决复杂问题的能力。
第三节 类的初步知识
一、类的定义
- 类的基本概念
类是具有相同的属性和操作的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性(数据变量)和操作(成员函数)两个主要部分。
类是一种用户自己构造的数据类型;
类要先声明后使用、是具有唯一标识符的实体; - 声明类
C++中声明类的一般形式为:
class类名{
private:私有变量和函数
public:公有变量和函数
protected:保护变量和函数
};
说明:
①private是完全私有的,只有本类内可以访问,派生类和外部都不可以访问﹔
②protected是受保护的,只有本类内和派生类可以访问,外部不能访问;
③public内、外部都能访问;
④如无访问权限的关键字,则默认声明为private;
⑤不能在类的声明中对数据变量进行初始化;
⑥类声明中可以给出成员函数的参数的默认值;
⑦类中声明的任何成员不能使用extern、auto和register关键字修饰;
存储类型
⑧类中可以不含有任何成员变量和成员函数,这样的类称为空类。
【例】描述点的Point类。
class Point //class是定义类的关键字,Point是类名
{
private : //声明为私有访问权限
int x, y; //私有数据成员
public: //声明为公有访问权限
void Setxy (int a, int b); //声明无返回值的公有成员函数
void Move ( int a, int b);//声明无返回值的公有成员函数
void Disp( ); //声明无返回值的公有成员函数
int Getx ( ); //声明返回值为int的公有成员函数
int Gety ( ); //声明返回值为int的公有成员函数
}; //类声明以分号结束
3、定义成员函数
(::所属) 返回值类型 类名::成员函数名(形参列表){ 成员函数的函数体 }
说明:
①返回类型是指这个成员函数返回值的类型;
②类名是指成员函数所属类的名字;
③“:”是作用域运算符,用于表示其后的成员函数属于这个特定的类;
例:定义属于Point类的成员函数Setxy
void Point:Setxy(int a,int b)
{
x=a; y=b; }
④类中定义的成员函数允许重载。
⑤可以使用关键字inline将成员函数定义为内联函数(凡是出现调用该函数的地方,编译程序自动将其转换为该函数的函数体,不再在程序执行时调用该函数,大大提高了效率)。
如: inline int Point::Getx()
{return x; }
⑥如果在声明类的同时,在类体内给出成员函数的定义,就默认为内联函数,
如将声明Getx的语句"int Getx () ;”改为“int Getx (){return x; }”,则Getx为内联函数。
二、类的定义示例
【程序例2-1】定义日期类myDate
class myDate{
public:
myDatc();//构造函数
myDatc(int, int, int);//构造函数
void setDatc(int, int, int);//设置日期
void sctDatc(myDate);//设置日期
myDatc getDatc(); //获取日期
void setYear(int);//设置年
int getMonth();//获取月
void printDate( ) const;//打印日期
private:
int year, month, day;//成员变量,表示年、月、日
};
//在类体外定义成员函数
myDate:.myDate( ) //构造函数用来初始化
{
ycar=1970, month=1, day=l;}
myDate:myDatc(int y, int m, int d)//构造函数
{
year=y; month=m; day=d;}
void myDate:.setDate(int y, int m, int d)//用变量设置日期
{
year=y; month=m; day=d;
return; }
void myDate:setDate(myDate oneD)//用对象设置日期
{
year=oneD.year; month=oneD.month; day=oncD.day;return;};
myDate myDatc:getDate() //获取日期
{
return *this;}
void myDate:setYear(int y)//设置年
{
year=y;
return;}
int myDate:getMonth( )
//获取月
return month;
}
void myDate.printDate( ) const
//打印日期
{
cout<<year <<"/"<< month <<"/"<<day;
return;
}
【程序2-2】定义学生类Student
class Student{
public:
void setStudent(string, myDatc);//设置学生信息
void setName(string);//设置姓名
string getNamc();//获取姓名
void setBirthday(myDatc);//设置生日
myDate getBirthday();//获取生日
void printStudent( ) const;//打印信息
private:
string name;//姓名
myDate birthday;//生日
};//string 系统定义好的类,myDatc前面例题定义的类
//在类体外定义成员函数
void Student::setStudent(string s, myDate d) //设置学生信息
{
name=s;
birthday.setDate(d);return;}
void Student::.setName(string n)//设置姓名
{
name=n;
return;}
string Student::getName( )//获取姓名{return name;};
void Student:setBirthday(myDate d) //设置生日
{
birthday.sctDatc(d);
return;
}
myDate Student::getBirthday( )//获取生日
{
return birthday;
void Student:printStudent( ) const //打印信息
{
cout<<"姓名: "<<namc<<"生日: ";
birthday.printDate();//调用类myDate的成员函数cout<< cndl;
};
第四节 类的示例程序剖析
一、程序结构
一个完整的C++程序包括以下几部分。
一个主函数,可以调用其他函数,但不能被调用,也称为主程序。
用户定义的任意多个的类及全局函数。
全局说明。在所有函数和类定义之外的变量说明及函数原型。
注释。
头文件。
三、创建类对象的基本形式
定义一个普通对象,即类变量的基本方法有两种。
1)类名 对象名;
类名 对象名(参数); //定义对象的同时初始化
类名 对象名=类名(参数);
2)类名 *对象指针名=new 类名;
类名 *对象指针名=new类名( )﹔
类名 *对象指针名=new类名(参数);
声明对象引用
类名 &对象引用名=对象;
声明对象指针
类名 *对象指针名=对象的地址;
声明对象数组
类名对象数组名[数组大小];
例:定义了类C后,可以有如下的声明:
C al,bl; //定义了C类的对象al和bl
C *p=&al; //定义了指向对象al的C类类型的指针p
C &R=bl; //定义了C类类型对象bl的引用R
C A[3]; //定义了C类类型对象的数组A,含3个元素
第五节 访问对象的成员
一、使用对象访问成员变量与调用成员函数
对象访问成员变量的格式:
对象名.成员变量名
通过对象的指针访问成员变量的格式:
对象的指针->成员名
通过对象的引用访问成员变量的格式:
对象的引用.成员变量名
调用成员函数的格式:
对象名.成员函数名(参数表)
对象的指针->成员函数名(参数表)
对象的引用.成员函数名(参数表)
【程序2-3】验证类功能的驱动程序
#include <iostream>
#include <string>
using namespace std;int main()
{
Student ss;
int y, m, d;string name_;
cout<<"请输入学生的姓名和生日,生日以‘年月日'的次序输入:";
cin >> name_ >>y >>m>>d;
ss.setStudent(name_, myDate(y, m, d));//调用成员函数
ss.printStudent();
return 0 ;
}
二、使用指针访问对象的成员
【程序2-4】使用指针方式的驱动程序
int main()
{
Studcnt ss;int y, m, d;
string namc_;
Student *sp=&ss;//对象的指针
cout<<"请输入学生的姓名和生日,生日以‘年月日'的次序输cin >> name_>>y>>m>>d;
sp->sctStudent(name_, myDate(y, m, d)); //调用成员函数sp->printStudcnt();
return 0 ;
}
三、使用引用访问对象的成员
见书本【P75】程序2-5
第六节 类成员的可访问范围
一、访问范围说明符的含义
public:是公有的,使用它修饰的类的成员可以在程序的任何地方被访问。
private:是私有的,使用它修饰的类的成员仅能在本类内被访问。
protected:是保护的,它的作用介于public与private之间,使用它修饰的类的成员能在本类内及子类中被访问。一般会将成员变量声明为私有的,而将相关的成员函数声明为公有的,以便在类的外部可以调用这些成员函数来操纵成员变量。
二、成员的访问
【程序2-6】类成员的访问
#include <iostream>
using namespace std;
class Box
{
public:
double length;
void setWidth( double wid);double gctWidth();
private:
double width;
};
//类体外定义成员函数
double Box::getWidth(){
rcturn width;}
void Box:.setWidth(double wid) ( width=wid;
void Box:.sctWidth(double wid){
width=wid; }
int main()
{
Box box;
box.length=10.0;//正确,因为length是公有的
cout<< "Length of box:"<<box.length <<vendl;
//输出Length of box : 10
box.width=10.0;//错误,因为width是私有的
box.sctWidth(1 0.0);l必须使用成员函数设置宽度
cout<< "Width of box : n <<box.getWidth( ) <<cndl;
//输出Width of box : 10
return 0;
}
三、隐藏的作用
设置私有成员的机制叫作“隐藏”。
“隐藏”的优点:
1)有利于程序的修改。
2)可以避免对对象的不正确操作
第七节 标识符的作用域和可见域
C++中标识符的作用域有函数原型作用域、局部作用域(块作用域)、类作用域和命名空间作用域。
l.函数原型作用域:在声明函数原型时形参的作用范围就是函数原型作用域,这是C++程序中最小的作用域。例: double ares (double r) ;
r的作用范围就在函数area形参列表的左右括号之间。
2.局部作用域:程序中使用相匹配的一对大括号括起来的一段程序称为块。作用域局限在块内的称为局部作用域。具有局部作用域的变量也称为局部变量。
例2-10 局部作用域示例
void fun(int a)
{
a的作用域
int b=a;
cin>>b;
if(b>0)
{
b的作用域
Int c:
....
} c的作用域
count<<b;
}
3.类作用域
设:m是类X的成员,x是类X的对象,ptr是指向类X的一个对象的指针。则对m的访问方式有如下2种:
1)类X的成员函数中可直接使用m。(除非成员函数中有同名变量)。
2)在类外,可以通过表达式x.m、X::m或者ptr->m来访问m。但注意不能违反m的访问修饰符的限定。
4.命名空间作用域
命名空间是为了消除程序各大模块之间同名引起的歧义。定义命名空间的一般形式如下:
namespace命名空间名
{命名空间内的各种声明(函数声明、类声明、…)}
在命名空间内部可以直接引用当前命名空间中声明的标识符,如果需要引用其他命名空间的标识符,需要使用下面的方式:
命名空间名::标识符名
C++提供两种形式using语句:
l) using命名空间名:.标识符名;
2) using namespace命名空间名;
使用该语句后,可以在当前作用域中直接引用该命名空间内的任何标识符。
例: using namespace std;
具有命名空间作用域的变量也称为全局变量。
标识符的可见性的一般原则:
标识符要声明在前,引用在后。
在同一个作用域中,不能声明同名的标识符。在没有互木包含关系的不同作用域中声明的同名标识符,互不影响。
如果存在两个或多个具有包含关系的作用域,外层声明一个标识符,而内层没有再次声明同名标识符,那么外层识符在内层仍然可见。如果在内层声明了同名标识符,外层标识符在内层不可见,这时称内层标识符隐藏了外层[名标识符,这种机制称为隐藏规则。
【程序2-8】作用域隐藏规则
#include <iostream>
using namespace std;int main()
{
int a=l;
cout<<a<<"in";//输出1
for(int i=l; i<2; i++){
int a=2;
cout<<a<<"'n";//输出2}
cout<<a<<"n";//输出lreturn 0;}
第三章 类和对象进阶
第一节 构造函数
一、构造函数的作用
构造函数是类中的特殊成员函数。
构造函数的作用是完成对象的初始化。
给出类定义时,由程序员编写构造函数。如果程序员没有编写类的任何构造函数,则由系统自动添加一个不带参数的构造函数。
二、构造函数定义
构造函数在类体里的声明形式:
类名(形参1,形参2,…形参n) ;//也可没有形参
构造函数的定义形式:
假设数据成员为x1,x2,…x,类体外定义构造函
数时通常有3种形式:
①类名::类名(形参1,形参2,…,形参n):x1(形参1),x2(形参2), xn(形参n){ }
②类名:类名(形参1,形参2,…,形参n)
{xl =形参1;
…
x2=形参2;
xn=形参n;}
③类名::类名()//成员变量所赋的初值都是固定的
{x1 =初始化表达式1;
x2=初始化表达式2;
…
xn=初始化表达式n;}
说明:
①构造函数的名字必须和类名相同;
②在定义构造函数时不能指定返回类型,即不要返回值,即使是void类型也不可以;
③另外类可有多个构造函数,即函数重载;或重载
④构造函数的参数在排列时无顺序要求,只要保证相互对应即可;
⑤构造函数可以使用默认参数。
⑥在程序中说明一个对象时,程序自动调用构造函数来初始化该对象。
【例2-1】定义日期类myDate //第二章的例子
class myDate
{
public:
myDate();//声明构造函数
myDatc(int);//声明构造函数
myDate(int, int);//声明构造函数
myDate(int, int, int);l/声明构造函数
……
private:
int year,month,day;l/成员变量
}
//定义构造函数
myDate::myDate( ) //固定值初始化
{
year=1970, month=l, day=l; }
myDate:myDate( int d): year(1970), month(l) //带1个参数
{
day=d;}
myDate:.myDate(int m, int m) : year(1970) //带2个参数
{
month=m; day=d;}
myDate:.myDate(int y, int m, int d) //带3个参数
{
year=y; month=m; day=d;}
myDate:myDate( ) //形式3
{
year=1970, month=1, day=l;}
可以改写为:形式1
myDate:myDate( ) : year( 1970), month(1), day(1){
}
myDate:.myDate(int y, int m, int d) //形式2
{
year=y; month=m; day=d;}
可以改写为:形式
myDate::myDate(int y, int m, int d) : year(y), month(m),day(d){
}
例3-3错误的构造函数定义,定义了下面两个构造函数
myDate::myDate(int d): year(1970), month(l)
{
day = d;}
my Date::myDate(int d): year(1970), day(l)
{
month = d;}
两个函数的函数名相同,参数相同,编译会出错。
三、构造函数的使用
当程序创建一个对象时,系统自动调用构造函数来初始化该对象。
例3-6使用构造函数创建类的对象
myDate d( ); //使用无参的构造函数
myDate dl(25); //使用1个参数的构造函数
myDate d2(10,20);
myDate d3(1971,9,18);
对象d的值:1970/1/1,对象dl的值:1970/1/25
对象d2的值:1970/10/25,对象d3的值:1971/9/18
例3-7使用构造函数的默认参数创建对象
myDate::myDate(int y=1970, int m=2, int d=14)
{
year=y; month=-m; day=d;}
myDate d( );//使用无参的构造函数
myDate dl(1980);//使用1个参数的构造函数
myDate d2(1990,3);
myDate d3(2000,4,18);
对象d的值:1970/2/14,对象dl的值:1980/2/14
对象d2的值:1990/3/14,对象d3的值:2000/4/18
例3-9使用构造函数创建对象指针
myDate::myDate(int y=1970, int m=2, int d=14)//默认参数
{
year=y; month=m; day=d;}
myDate *pd = new myDate();//p所指对象的值1970/2/14
myDate*pdl = new myDate(1955);//1955/2/14
myDate *pd2 = new myDate(1955,4);//1955/4/14
myDate *pd3 = new myDate(1955,5,13);//1955/5/13
}
注意:使用new创建对象时,下面两种都是合法的:
myDate *pd = new myDate() //带括号
myDate *pd = new myDate //不带括号
用户定义了构造函数,都会调用构造函数进行初始化;
用户未定义构造函数,对带括号的情况,系统在为成员变量分配内存的同时,将其初始化为0。不加括号时,系统只为成员变量分配内存空间,但不进行内存的初始化,成员变量的值是随机值。
例:使用构造函数创建对象数组
myDate::myDate(int y=1970, int m=2, int d=14)
{
year=y; month=m; day=d;}
myDate A[3];//对象A[0]、A[1]、A[2]的值都是1970/2/14
myDate B[3]= {
myDate( 1980),