C++在线入门

第一章 c++入门


1.c++是在c的基础上发展而来,是带类的c语言。c语言支持结构化程序设计,c++支持面向对象程序设计。程序设计方法正由结构化程序设计走向面向对象程序设计。

2.结构化程序设计主要思想是功能分解并逐步求精。面向对象程序设计的本质是把数据和处理数据的过程看成一个整体――对象。

3.开发一个C++程序需经过以下四步:
编辑(产生源文件,扩展名为cpp)
编译(产生目标文件,扩展名为obj)
连接(产生可执行文件,扩展名为exe)
运行

4.例:
//ch1_1.cpp
#include<iostream.h>
void main()
{
cout<<"I am a student./n";
}
运行结果:I am a student.

上例说明:
(1) c++的程序结构由注释、编译预处理和程序主体组成。

(2) 注释的目的是为了提高程序的可读性。注释以//开始,直到换行。注释分序言注释和注解性注释两种。

(3) 以#开头的行,称为编译预处理行。

(4) main()称为主函数。c++程序由函数构成。一个c++程序由一个主函数和若干个函数组成。程序的运行总是从主函数main()处开始。

第二章 基本数据类型和表达式

2.1 c++的基本数据类型
1.c++的数据类型

c++的数据类型

2.基本数据类型修饰符
long(长型符) short(短型符)
signed(有符号型) unsigned(无符号型)
3.用类型修饰符修饰后的基本数据类型
修饰后的基本数据类型
修饰后的基本数据类型
修饰后的基本数据类型
修饰后的基本数据类型
修饰后的基本数据类型

2.2 整型数据
1.整型数据的基本情况(见表2-1)
2.操作符sizeof(数据类型)可测定某数据类型所占字节长度。
例如:
cout<<“size of int is”<<sizeof(int)<<endl;
输出为:size of int is 2

3.整型常量(整型常数):三种表示法

(1)10进制常数(有正负):123,-46,0
(2)8进制常数(无符号):0开头的整数,如0123
(3)16进制常数(无符号):0X或0x开头的整数,如0x123,0X23

4. 整形变量的定义和初始化
变量命名:字母或下划线开头的字母、数字、下划线序列,不能为保留字。c++区分大小写。
变量命名常用方法:mycar ,my_car

整形变量定义
int i,j,k; unsigned int myAge,myWeight; long area,width,length;
整形变量赋值和初始化
赋值:先定义,后赋值

unsigned short width;

width=5; //=为赋值号

初始化:在定义的同时即对其赋值

unsigned short width=5; long width=7,length=7; double area,radius=23;

2.3 字符型数据
1. 字符型数据的基本情况

2.字符型常量的表示
(1)常规字符:单引号括起的一个字符。如‘a’,‘x’,‘?’等。
(2)转义字符:以“/”开头的字符序列。如‘/n’,‘/b’等。

(3) ‘/ddd’,d为8进制数,表示范围为‘/000 ’ - ‘/377’。

(4)‘/xhh’,h为16进制数,表示范围为‘/x00’- ‘/xff’。

(5)单引号内/后跟上述情况以外的其它字符,表示该字符本身。如:‘/d’即是‘d’

注:‘单引号,“双引号,/反斜杠这三个字符只能用下述方法表示: ‘/’’和‘/”’和‘//’.

(6)字符串常量:用双引号括起来的字符序列以‘/0’作为结束标志,该字符占有一字节的存储空间,但并不计入字符串的长度。如: “C++string”
 其存储格式为:

C
+
+
s
t
r
i
n
g
/0

 其中字符串的长度为9,存储占10个字节。

3.字符型变量的定义和初始化

定义:char c1;
初始化:char c2=‘A’;

4.字符型和整形的关系

字符型数据在外部表示字符,在内存单元中存入的并不是该字符本身,而是该字符的ASCII码(整数值)。
如:字符‘A’ ASCII码为65
字符‘a’ ASCII码为97
表达式求值过程中字符被转成与其代码等值的整数,进行计算(字符数据也可以进行加减乘除)
如:‘b’-’a’+’A’=98-97+65=66=‘B’

故只要范围合理,字符数据与整型数据可以互相赋值.

 如:char ch1=‘a’,ch2=‘B’;
 也可为char ch1=97,ch2=66;

2.4 枚举类型数据

1.枚举类型数据的基本情况

2.枚举类型的定义
 例如:
 enum weekday{Sun,Mon,Tue,Wed,Thu,Fri,Sat};

3.枚举变量的定义
 例如:weekday w1,w4;

4.枚举变量的初始化
 例如:weekday w1=Mon;

5.枚举型数据与整型数据之间的关系

枚举中的符号与整数有一一对应关系
 例如:
 enum weekday{Sun,Mon,Tue,Wed,Thu,Fri,Sat};

  0123456

可以用=为一个符号规定任意整数,该符号增1为下一个符号的默认整数。
例如:
enum somdigits{one=1,two,five=5,six,seven};

  12567

2.5 实型数据
1.实型数据的基本情况

2.实型常量(实型常数):二种表示法
(1)定点数形式:必须有小数点。
如 0.123,.234,0.0
(2)指数形式:E或e前必有数字,后必为整数。
如 123e5, 12.3e5
e3(不对), 2.5e2.5(不对)
说明:默认实型常数为double型,后加F或f表float型,后加l或L表long double型。
 例如:3.23, 3.23f, 3.23L
3.23e-3
3.23e-3F
3.23e-3l

3.实型变量的定义
 double a,b;
  float xyz1,xyz23;
4.实型变量的初始化
  double a=3.16,n=9;
  float xyx1= 3.23e-3f;

2.6 常量(constance)定义
(1)常量定义时必须初始化。 const float pi=3.1415926; const float g=9.8; const float pi; pi=3.1415926; // 错误

(2)相同类型的常量和变量在内存中占有相同大小的空间。但常量的内存空间值不可改变,变量的内存空间值可以改变。

(3)c语言中,编译预定义指令#define亦可定义常量。
例 #define PI 3.1415926

2.7 数值表达式
1.无操作符的表达式—简单表达式
如:54 -3.987 car f()

2.算术操作符
+(加)-(减)*(乘) /(除、整除)
%(求余)+(取正)-(取负)
 例:

10/2.0=5.010/3=310%3=1

3.位操作符

<<(左移,如k<<2)>>(右移,如k>>2)^(按位异或,如k1^k2)
| (按位或,k1|k2)&(按位与,如k1&k2)~按位求反,如~k1

例:5<<2值为20
5>>1值为2
例:设E1为0000000000001101
设E2为0000000000100101
E1|E2值为0000000000101101
E1&E2值为0000000000000101
E1|E2值为0000000000101000
E1|E2值为1111111111011010

4.赋值操作符(=)
赋值表达式:赋值操作符构成的表达式.赋值表达式的值为赋值号左边表达式的值.

例:k=7; 值为7
k=m=7;//k=(m=7); 值为7

5.复合赋值操作符

+=(加赋值)-=(减赋值)
*=(乘赋值)/=(除或整除赋值)
%=(求余赋值)<<=(左移赋值)
>>(右移赋值)|=(按位或赋值)
&=(按位与赋值)^=(按位异或赋值)

例:k+=3; //k=k+3;
s*=j-3;//s=s*(j-3); 

6.增1减1操作符
左值:能放在赋值号左边的表达式.
 例:k=5;//k为左值
   (k=5)=28;//k=5为左值
右值:只能出现在赋值号右边的表达式.
 例:k=4;//4为右值
   const int b=100;//b为常量,为右值

++:增量操作符表示加1
--:减量操作符表示减1

++i(前增量)先加后用
i++(后增量)先用后加
--i(前减量)后用
i--(后减量)先用后

例:  ++i // i=i+1
i++ // i=i+1
--i // i=i-1
i-- // i=i-1
注:增量和减量的操作对象均要求为左值

 例如:
 int b=++a;//a=a+1;b=a;
 int b=a++;//b=a;a=a+1;
 int c=--a;//a=a-1;c=a;
 int c=a--;//c=a;a=a-1;

前增量(前减量)返回的值是修改后的变量值,故为左值。
 后增量(后减量)返回的值是修改前的变量值,故不为左值。

 例:++(a++);//×    ++(++a);//√
  (a--)--;//×      (a--)--;//√

若有多个+或-连写时,编译总是将前面两个+或-认为是增量或减量操作符。
例:c=a+b;// √
 c=a++b;// ×
  c=a+++b;// √
  c=a++++b;// ×
  c=a+++++b;// ×

7.sizeof操作符
 操作符sizeof(数据类型)可测定某数据类型所占字节长度。
例如:
cout<<“size of int is”<<sizeof(int)<<endl;
输出为:size of int is 2

8.优先级和结合性
优先级:不同操作符出现在同一表达式中谁先运算的级别。

例:d=a+b*c;//*优先级比+高。

结合性:同等优先级的操作符出现在同一表达式中谁先运算的规定。
例:d=a+b-c;//左->右
d=a=3;//右->左

C++中表达式的书写.

9. 自动类型转换和强制类型转换

自动类型转换方向 见图


自动类型转换转换方向 

强制类型转换
  格式:(类型修饰符)表达式
      类型修饰符(表达式)

例:int(3.14+i)或(int)(3.14+i)
char(64.8)或(char)64.8或(char)(64.8)

2.8 逻辑型数据和逻辑表达式

1.基本情况
 例:bool isOk,ready=true;

2.关系操作符和逻辑操作符

关系运算符比较(==)大于(>)小于(<)大于等于(>=)小于等于(<=)不等于(!=)
逻辑运算符与(&&)或(||)非(!)   

3.比较(==)
比较(==)结果为真(非0)或假(0)表示。
 赋值(=)结果为=号左边表达式的值。
4.复合条件
子条件1&&子条件2:如(x>=2)&&(x<=7)
子条件1||子条件2 :如(x<2)||(x>7)
E1||E2||…||En
E1&&E2&&…&&|En

5.相反条件 例如:x>3和x<=3
   若x>3的值为true,则x<=3必为false   即x>3和x<=3是互为相反条件
6.等价条件
等价条件:两个逻辑表达式在任何情况下同为true或同为false,则这两个表达式所表示的条件称为等价条件。

7.永真条件:总是成立的条件称为永真条件.
 例如:x>0||x<=0,true是永真条件
 永假条件:总是不成立的条件称为永假条件。
 例如:x>12&&x<5,false

8.逻辑型数据与其它数据类型的关系

逻辑型数据作为数值数据使用时,false被转换成0,true被转换成1。
字符型、枚举型、整型、实型数据的值作为逻辑值使用时,一切0值被转换成false ,一切非零值被转换成true
指针值作为逻辑值使用时,空指针被换成false,非空指针被转换成true

总之:0即是false,非0即是true

9.条件操作符和条件表达式
条件操作符  ?:
 使用格式:条件?表达式1:表达式2
条件表达式
定义:由条件操作符构成的表达式称为条件表达式
表达式的值:条件成立时为表达式1的值,条件不成立时为表达式2的值
例: (score>=60)? ”pass”:”fail”

条件表达式可以嵌套
例:(x>y)?"great than":((x==y)?"equal to":"less than")

10.typedef:为一个已有类型名提供一个同义词。
定义格式:typedef 类型说明符 新类型名

如: typedef int INT,integer ; INT a;//int a; typedef double profit; profit d; // double d;
11. 表达式的副作用
表达式的副作用:表达式求值过程中,若参与运算的变量的值发生了改变,称此表达式是具有副作用的。

例:int i=1,j=2;
   cout<<(i+j++)<<endl;
   cout<<j<<endl;

结果: 3
    3
表达式i+j++具有副作用。

产生副作用的主要原因是引入了具有副作用的操作符。这些操作符包含:
(1)赋值=
(2)复合赋值(+=,-=,*=等)
(3)前增1前减1(++,--)
(4)后增1后减1(++,--)

赋值,复合赋值,前增1和后增1的结果仍然是变量对象,即仍为左值.
数值表达式,常量,后增1和后减1不能为左值.
例: (p+3)-=k;// × ++k*=5;// √
++5;// × k++*=5;// ×
100=p;// ×

12.逗号表达式
格式:p1,p2,……,pn(其中pi为表达式)
例:int a,b,c;
   a=1,b=a+2,c=b+3;
逗号表达式的值是其中最后一个表达式pn的值。
例:int a,b,c,d;
   d=(a=1,b=a+2,c=b+3);
   cout<<d<<endl;
   结果:6

第三章 C++程序的流程控制


流程控制与程序结构

1.流程控制:控制语句执行的顺序

2.流程控制的方式(三种)
①顺序控制
②条件分支控制
③循环控制

3.1 程序结构的分类

① 顺序结构
②条件分支结构:由if语句和switch语句实现。
③循环结构:由while语句、for语句和do…while语句实现。

3.2 条件分支结构
3.2.1 if语句
设p为条件表达式,s为语句
 格式1:if p
 s;

 格式2:if p
    s1;
   else
    s2;

例:#include<iostream.h>
  void main()
  {
  int score;
  cin>>score;
  if(score>=60)
  cout<<“恭喜,你及格了!”;
  }

例:#include<iostream.h>
   void main()
   {
    int score;
    cin>>score;
    if(score>=60)
    cout<<“恭喜,你及格了!”;
    else
     cout<<“抱歉,等着补考吧!”;
   }

例如:输入两个数找出最大的一个并显示出来。
#include<iostream.h>
void main()
{ int i ,j;
cout<<“输入两个数:”;
cin>>i>>j;
cout<<“两个数中最大的是:”;
if(i>=j)
cout<<i<<endl; //语句1
else
cout<<j<<endl; //语句2
}

例:输入三个数,找出其中最大的一个并显示出来。
#include <iostream.h>
void main()
{ int i,j,k;
cout<<“输入三个数:”;
cin>>i>>j>>k;
cout<<“三个数中最大的是:”;
if(i<j) i=j;
if(i<k) i=k;
cout<<i<<endl;
}

例:输入三个数,按最大到小的顺序显示出来。
#include <iostream.h>
void main()
{ int i,j,k,p;
cout<<“输入三个数:”;
cin>>i>>j>>k;
if(i<j) {p=i; i=j; j=p;}
if(i<k) {p=i; i=k; k=p;}
if(j<k) {p=j; j=k; k=p;}
cout<<i<<‘ ‘<<j<<‘ ‘<<k<<endl;
}

3.2.2 if语句的嵌套
1.解决二义性
例:int x=20;
  if(x>=0)
  if(x<50)
  cout<<"x is ok/n";//s1
  else
   cout<<"x is not ok/n";//s2

解释1//该解释为正确解释
  int x=20;
  if(x>=0)
  {
  if(x<50)
   cout<<"x is ok/n";//s1
  else
    cout<<"x is not ok/n";//s2
}

解释2//该解释为错误解释
int x=20;
if(x>=0)
{
if(x<50)
cout<<"x is ok/n";//s1
}
else
cout<<"x is not ok/n";//s2

因为:c++规定,else总是和其前面最近的尚未配对的且可见的if配对。

2.例:输入3个数,找出其中最大的一个并显示出来。(见书)

3.2.3 if多分支结构
格式3:
 if p1 s1;
  else if p2 s2;
 else if p2 s3;
 …
 else if pn sn;
 [else sn+1 ]

例:#include<iostream.h>
void main()
{
int grade;
cin>>grade;
if(grade>=90) cout<<“Excellent!”;
else if(grade>=80) cout<<“Good!”;
else if(grade>=60) cout<<“Pass!”;
else cout<<“Fail!”;
}

3.2.4 switch语句
1.格式1(不带break语句)
switch (表达式)
  { case 常量表达式1:语句1;
   case 常量表达式2:语句2;

 case 常量表达式n:语句n;
[default:语句n+1;]
}

例:switch(dayOfTheWeek)
{ case 0:cout<<“Sunday";
case 1:cout<<“Monday";
case 2:cout<<“Tuesday";
case 3:cout<<“Wednesday";
case 4:cout<<“Thursday";
case 5:cout<<“Friday";
case 6:cout<<“Saturday";
default:cout<<“Unknow week day";
}

若dayOfTheWeek的值为4,则输出为
Thursday
Friday
Saturday
Unknow week day

2.格式2(带有break语句)

switch (表达式)
  { case 常量表达式1:语句1;break;
   case 常量表达式2:语句2; break;

 case 常量表达式n:语句n; break;
[default:语句n+1; break;]
}

例:switch(dayOfTheWeek)
{ case 0:cout<<“Sunday";break;
case 1:cout<<“Monday"; break;
case 2:cout<<“Tuesday"; break;
case 3:cout<<“Wednesday"; break;
case 4:cout<<“Thursday"; break;
case 5:cout<<“Friday"; break;
case 6:cout<<“Saturday"; break;
default:cout<<“Unknow week day"; break;
}

若dayOfTheWeek的值为4,则输出为
Thursday

3. if多分支结构和switch语句可相互改写。

例:char grade;
switch(grade)
{ case 'A':cout<<"85--100/n";break;
case 'B':cout<<"70--84/n"; break;
case 'C':cout<<"60--69/n"; break;
case 'D':cout<<"<60/n"; break;
default:cout<<"error/n"; break;
}

可改写为:
? 例:char grade;
if(grade=='A')cout<<"85--100/n";
else if(grade=='B')cout<<"70--84/n";
else if(grade=='C')cout<<"60--69/n";
else if(grade=='D')cout<<"<60/n";
else cout<<"error/n";

3.3 循环结构
3.3.1 for循环
1.格式:for(表达式1;表达式2;表达式3)
   循环体
执行过程: 先计算表达式1;求解表达式2,若为0(false),则结束循环,执行循环语句的下一语句;若表达式2为1(true),执行循环体,然后求解表达式3; 转回第二步。直到表达式2为0退出循环。

 注意:如果循环体有一个以上的语句,应该用花括号{ }括起来。

例:计算并输出从1至100的和。
//ch415.cpp
#include<iostream.h>
void main()
{ int sum=0;
for(int i=1;i<=100;i++)
sum+=i;
cout<<“the sum of 1 to 100 is :”<<sum;
}

2.格式中表达式1(e1)、表达式(e2)、表达式3(e3)均可省略,但相应的分号不可省。

(1)e1可省。
  int i=1;
  for(;i<=100;i++)
   sum+=i;

(2)e2可省,此时e2为真,且在循环体中应有跳出循环的控制语句。
 
 for(int i=1;;i++)//e2值为1
 {
  sum+=i;
  if(i>100) break;
 }?

(3)e3可省, 此时应在循环体中应让循环变量递进变化。
 
 for(int i=1;i<=100;)
sum+=i++;

(4)e1、e3可同时省。
 
 int i=1;
 for(;i<=100;)
sum+=i++;

(5)e1、e2、e3可同时省。

 int i=1;
 for(;;)
 {
  sum+=i++;
  if(i>100) break;
}

(6)e1、e2、e3可为任意表达式。

for(sum=0;i<=100;i++)
sum+=i;
?
for(sum=0,i=1;i<=100;i++)//e1为逗号表达式
sum+=i;

for(i=0,j=100,k=0;i<=j;i++,j--)//e1,e3逗号表达式
k+=i*j;
?
for(i=1;i<=100;sum+=i++);//循环体为空语句

for(i=1; sum+=i++,i<=100;);//循环体为空语句

(7)e1可为循环变量定义。

for(int i=1;i<=100;i++)
sum+=i;

4.举例:
例3.9
例3.10
例3.11

3.3.2 while循环

1.格式:while (条件表达式)
  循环体
2. 执行过程:当条件表达式成立(true),执行循环体,否则退出循环。
3. 特点:先判断条件,再执行循环体。

例:计算1至100的和。
#include<iostream.h>
void main()
{ int sum=0;
int i=1;
while(i<=100)
{ sum=sum+i; //sum+=i;
i++;
}
cout<<“The sum of 1 to 100 is:”<<sum;
}

其中: while(i<=100)
     {
      sum=sum+i;
     i++;
     }
可改写为:

while(i<=100)
sum+=i++; //循环体

4.举例:
例3.12
例3.13
例3.14

3.3.3 do…while循环

1.格式
 do
 循环体
 while (条件表达式);
2. 执行过程:当条件表达式成立(true),执行循环体,否则退出循环。

3. 特点:先执行循环体,再判断条件。
例:#include<iostream.h>
viod main()
{ int i=1,sum0;
do{
sum+=i;
i=i+1;
}while(I<=100);
cout<<“sum=”<<sum<<endl;
}


4. 举例
例:3.15

3.3.4 循环结构的特殊控制:break语句和continue语句。
1.break语句
功能:用在switch语句中,使流程跳出switch语句。用在循环体中,使流程跳出最近的封闭循环体。

2.continue语句
功能:结束本次循环,接着进行下一次是否执行循环的判定。
3.二者区别
break跳出循环体,不再判定条件。
continue结束本次循环,再判定条件。

例:把100至200之间的不能被3整除的数输出。
for(int n=100;n<=200;n++)
{
   if(n%3==0) continue;
  cout<<n<<endl;
  //……
  }

以上程序段等价于
for(int n=100;n<=200;n++)
 {
  if(n%3==0)
 {
  cout<<n<<endl;
 //……
 }
 }
注意:break语句和continue语句的区别。 第三章 C++程序的流程控制
3.4 其他流程控制
3.4.1 goto语句
功能:将程序的执行转移到标识符所标识的语句处。
例:i=1;sum=0;
loop:
sum+=i++;
if(i<=100) goto loop;
cout<<"sum is"<<sum<<endl;


例: for(int i=1;i<10;i++)
for(int j=1;j<10;j++)
if(i*j==50) goto end;
end:
cout<<i<<"*"<<j<<"50/n";
?
3.4.2 return语句

格式1:return 表达式;
格式2:return;

 

第四章 程序结构


第一部分 本章知识点
外部存储类型extern
静态存储类型static
标识符的作用域
标识符的可见性与生命期
头文件与多文件结构
编译预处理
第二部分 内容精讲
要编好C十十程序,就必须对C十十的程序结构有一个全面的了解。所有的C十十程序都是由一个或多个函数构成的。一个C十十程序可以由一个或多个包含若干函数定义的源文件组成。C十十的编译器和连接器把构成一个程序的若干源文件有机地联络在一起,最终产生可执行程序。
4.1、外部存储类型
构成一个程序的多个源文件之间,通过声明数据或函数为外部的(extern)来进行沟通。
格式1:extern 数据类型 变量名; //声明数据为外部的
格式2:extern 类型 函数名(参数) //声明函数是外部的
说明:
(1) 带extern的变量说明是变量声明,不是变量定义。
(2) 所有函数声明一般都放在源文件的开始位置。
(3) 默认的函数声明或定义总是extern的。
例如:下面两个文件构成了一个程序,该程序由一个工程文件ch6.prj定义,工程文件和源文件中的内容分别为:
//ch6.prj
ch6_1.cpp
ch6_2.cpp
//ch6_1.cpp
# include <iostream.h>
void fn1();
void fn2(); //默认的函数声明或定义总是extern的。
int n;
void main()
{
n=3;
fn1(); //fn1()函数的定义在本文件中
cout<<n<<endl;
}
void fn1()
{
fn2(); //fn2()函数的定义不在本文件中,在下面的ch6_2.cpp中。
}
//ch6_2.cpp
extern int n; //n由另一个源文件(ch6_1.cpp)定义。
Void fn2() // fn2()函数在另一个源文件(ch6_1.cpp)中使用。
{
n=8; //此处使用n,n已声明是外部的。
}
注意:如果共同的变量一次都没有定义,或在各个文件中分别定义,造成定义多次,或声明的类型不一致,都会造成直接或间接的错误。
例如: //file1.cpp
int a=5 ;
int b=6;
extern int c;
//file2.cpp
int a; // error:多次定义
extern double b; //error:类型不一致
extern int c; //error:无定义
4.2、静态存储类型
1、 静态全局变量(又称全局静态变量):
(1) 静态全局变量的定义:在全局变量前加一个static,使该变量只在这个源文件中可用。
(2)全局变量与全局静态变量的区别:
(a)若程序由一个源文件构成时,全局变量与全局静态变量没有区别。
(b)若程序由多个源文件构成时,全局变量与全局静态变量不同:全局静态变量使得该变量成为定义该变量的源文件所独享,即:全局静态变量对组成该程序的其它源文件是无效的。
(3)静态全局变量的作用:
(a) 不必担心其它源文件使用相同变量名,彼此相互独立。
(b) 在某源文件中定义的静态全局变量不能被其他源文件使用或修改。
例如:一个程序由两个源文件组成,其中在一个源文件中定义了"int n;",在另一个源文件中定义了"static int n;"则程序给它们分别分配了不同的空间,两个值互不干扰。
例如:下面在file1.cpp中声明全局变量n,在file2.cpp中定义全局静态变量n。文件file1.cpp和file2.cpp单独编译都能通过,但连接时,file1.cpp中的变量n找不到定义,产生连接错误。
// file1.cpp
# include <iostream.h>
void fn()
extern int n;
void main()
{
n=20;
cout<<n<<endl;
fn();
}
// file2.cpp
# include <iostream.h>
static int n; // 默认初始化为0,注意此处定义的n 只能在file2.cpp中使用。
void fn()
{
n++;
cout<<n<<endl;
}
2、 静态函数:使某个函数只在一个源文件中有效,不能被其他源文件所用。
定义:在函数前面加上static。
说明:函数的声明和定义默认情况下在整个程序中是extern的。
静态函数的效果:
(1)它允其他源文件建立并使用同名的函数,而不相互冲突。
(2) 声明为静态的函数不能被其他源文件所调用,因为它的名字不能得到。
4.3、作用域:(从出现的位置看)
作用域是标识符在程序中有效的范围,标识符的引入与声明有关,作用域开始于标识符的声明处。
C++的作用域范围分为:局部作用域(块作用域),函数作用域,函数原型作用域,文件作用域和类作用域。
1、 局部作用域(块作用域):当标识符的声明出现在一对花括号所括起来的一段程序(块:复合语句)内时,该标识符的作用域从声明点开始,到块结束处为止,该作用域的范围具有局部性。
例如:下面的代码描述了局部作用域:
# include <iostream.h>
void fn ( int y ) // y的作用域从此开始
{
int x=1; // x的作用域从此开始

if (x>y)
cout << x << endl;
else
cout << y <<endl;
//...
} // x和y的作用域到此结束
例如:
# include <iostream.h>
void fn()
{
if(int i =5) //i的作用域从此开始
i =2* i;
Else
i =100;
//i的作用域到此结束
cout<< i <<endl; //error: i无定义
}
2、 函数作用域
3、 函数原型作用域:是C++程序中最小的作用域,是指在函数原型声明时形式参数的作用范围(开始于函数原型声明的左括号,结束于函数原型的右括号)。
说明:对于这种情况,形参可以省略,声明中加上形参目的是提高程序的可读性。
4、 文件作用域:在所有函数定义之外说明的,其作用域开始于声明点,结束于文件尾。
说明:
(a)静态全局变量和全局变量是文件作用域的,静态函数是文件作用域的。
(b)在头文件的文件作用域中所进行的声明,若该头文件被一个源文件嵌入,则声明的作用域扩展到该文件中,直到源文件结束。
4.4、可见性(从标识符引用时是否可见的角度看)
程序运行到某一点,能够引用到的标识符,就是该处可见的标识符。
可见性的一般规则是:
1、 标识符要声明在前,引用在后。
2、 在同一作用域中,不能声明同名的标识符。
3、 对于在不同的作用域声明的标识符,遵循的原则是:当存在两个或多个声明具有包含关系的作用域时,在外层声明了标识符后,如果内层中没有声明与之同名的标识符,则外层标识符在内层可见。如果内层中声明了与外层同名的标识符,则外层标识符在内层不可见。
4、 如果被隐藏的是全局变量,则可用符号 :: 来引用该全局变量。
例1、 下面的代码进一步说明可见性与作用域的关系。
{
int i ;
char ch;
i =3;
{
double i;
i =3.0e3; // int i 被隐藏,double i可用。
Ch='A'; //char ch 仍可见
} //double I 的作用域结束,恢复int i 可见。
i + = 1; //int i 可见。
} //int i,char ch作用域结束。
例2、 下面的代码定义了同名的全局变量和局部变量,但仍可在局部作用域中访问全局变量:
int s=0;
void f()
{
float s=3.0;
int a;
{
float a=2.0;
::a=1; //error:没有全局变量a
::s=1; //ok全局变量s
s=2.0; //ok指float s
}
}
作用域和可见性的关系:作用域讨论的是标识符的有效范围,可见性是标识符是否可以引用的问题,二者是相互联系又存在着相当差异的。
4.5、生命期(从存储位置角度看)
生命期也叫生存期。生命期与存储区域密切相关,存储区域主要有代码区、数据区、栈区和堆区,对应的生命期为静态生命期、局部生命期和动态生命期。
1、 静态生命期:变量在固定的数据区中分配空间的,具有静态生命期。包括全局变量、静态全局变量、静态局部变量。具有文件作用域的变量具有静态生命期。这种生命期与程序的运行相同,只要程序一开始运行,这种生命期的变量就存在,当程序结束时,其生命期就结束。
说明:
(1) 静态生命期的变量,若无显式初始化,则自动初始化为0。
(2) 函数驻在代码区,也具有静态生命期。在函数内部可以声明静态生命期的变量,即静态局部变量(加static)。
2、 局部生命期:
在函数内部声明的变量或者是块中声明的变量具有局部生命期。这种变量的生命期开始于程序执行经过其声明点时,而结束于其作用域结束处。所以具有局部生命期的变量也具有局部作用域。但反之不然,具有局部作用域的变量若为局部变量,则具有局部生命期,若为静态局部变量,则具有静态生命期。
说明:
(1) 具有局部生命期的变量驻在内存的栈区。
(2) 具有局部生命期的变量如果未被初始化,则内容不可知。
3、 动态生命期:
这种生命期由程序中特定的函数调用(malloc()和free()或操作符(new和delete)来创建和释放。
具有这种生命期的变量驻在内存的堆中。当用函数malloc()或new为变量分配空间时,生命期开始,当用free()或delete释放该变量的空间或程序结束时,生命期结束。
(具体应用在第八章)
4.6、头文件:头文件起着源文件之间接口的作用。
同一名字的声明可以多次,具有外部存储类型的声明可以在多个源文件中引用,方便的方法是将它们放在头文件中。
头文件一般可包含:
类型声明,如enum COLOR{//…}
函数声明,如extern int fn(char s);
内联函数定义 ,如inline char fn(char p){return *p++;}
常量定义,如const float pi=3.14;
数据声明,如extern int m;extern int a[];
枚举,如enum B00LEAN{false,true};
包含指令(可嵌套),如#include <iostream.H>
宏定义,如#define Case break;case
注释,如//check for end of file
但头文件不宜包含:
一般函数定义,如char fn ( char p ) { return *p++;}
数据定义,如 int a; int b[5];
常量聚集定义,如const int c[]={1,2,3};

4.7、多文件结构
程序开发的示意图
图中,源文件中含有包含头文件的预编译语句,经过预编译后,产生翻译单元,该翻译单元以临时文件的形式存放在计算机中。之后编译,进行语法检查,产生目标文件(.obj)。若干个目标文件经过连接,产生可执行文件(.exe)。连接包括C十十库函数的连接和标准类库的连接。
许多小程序可以由单个源文件建立,它编译成一个目标文件(.obj),然后输给连接器,
产生运行程序。这样的程序维护方便。如果修改了源文件中的任何函数,只需再次启动编
译器。
大程序倾向于分成多个源文件,其理由为:
(1)避免一而再、再而三地重复编译函数。因为编译器总是以文件为单位工作的。如果一个文件中包含的函数太多,则由于被修改的函数总是少数的几个,所以大多数正确的函数都得重新编译一次。
(2)使程序更加容易管理。可以将程序按逻辑功能划分,分解成各个源文件,便于程序员的任务安排,以及程序调试。
(3)把相关函数放到一特定源文件中。例如,所有输入函数放在一个源文件中。
4.8、编译预处理
预处理程序也称预处理器,它包含在编译器中。预处理程序首先读源文件。预处理的输出是"翻译单元",它是存放在内存中的临时文件。编译器接受预处理的输出,并把源代码转化成包含机器语言指令的目标文件。
预处理程序对源文件进行第一次处理,它处理的是预处理指令。我们介绍三类预处理指令:
1.# include包含指令
include命令让预处理器把一个源文件嵌入到当前源文件中该点处。它有两种格式:
格式1 : # include <文件名>
格式2: # include "文件名"
格式1用于嵌入C十十提供的头文件。这些头文件一般存于C++系统目录中include子目录下。C十十预处理器遇到这条指令后,就到include子目录下搜索给出的文件,并把它嵌入到当前文件中。这种方式是标准方式。
格式2预处理器遇到这种格式的包含指令后,首先在当前文件所在目录中进行搜索,如果找不到,再按标准方式进行搜索。这种方式适合于规定用户自己建立的头文件。
注:include文件可以嵌套,即在头文件中,还可以有包含指令。
2.# define宏定义指令
在C中,#define最常用的方法是建立常量,但已经被C十十的const定义语句所代替。
#define还可以定义带参数的宏;但也已经被C十十的inline内嵌函数所代替。
#define的一个有效的使用是在条件编译指令中。
3.条件编译指令
条件编译的指令有:#if,#else,#elif,#endif,#ifdef,#ifndef和#undif
条件编译的一个有效使用是协调多个头文件。
例如,符号NULL在6个不同的头文件中都有定义:locate.h,stddef.h,stdio.h,stdlib.h,string.h和time.h。一个源文件可能包含其中的几个头文件,这样会使得编译给出"一个符号重复定义多次"的错误。这时,需要在每个头文件中使用条件编译指令:
# ifndef NULL
# DEFINE NULL ((void*)O)
# endif
上面的代码能够保证符号NULL在一个程序中只有一次定义((void*)O)。而当再次遇到头文件时,一切定义的企图都被# ifndef给"挡驾"了。
使用 # undef可以取消符号定义,这样可以根据需要打开和关闭符号。
注:参数宏的弊端
给出下面的宏,请列出使用它时所有的潜在错误。
# define TWO_MAX(x,y) x>y?(2*x):(2*y)
这些问题最好怎么避免?
[解答]
(1)如果参数是一个表达式,且带有优先级低于乘法的运算符,结果将是错的
因为x没被括号括住,例如:
TWO_MAX(3 + a,b}= => 3 + a.>b?(2 * 3 + a} : (2 * b)
根据运算符的优先级,它将被解释成下面的式子:
TWO_MAX(3+a,b) = => ((3+a) >b)?((2 3) +a):(2b)
注意,(2* 3)+a替换了所想要的2*(3+ a)。
(2) 如果这个宏被用在一个表达式中,且该式中有相同优先级的运算符,结果将是错的,因为该表达式没有用括号括住,考虑下面的例子:
a=2 TWO_MAX(b,c); = => a=2*b>c?b:c;
它将被解释成:
a= (2*b) >c?b:c;
(3) 如果宏被一个有副作用的参数实现,该副作用将起两次作用,因为x出现了不止一 次,这可以表示为下面的程序:
a=TWO_MAX(b++ ,c) = => a=b++ >c?b++ :c;
如果b++比c的值大,那么b实际上作了两次增量。
尽管宏可以作如下修改:
TWO_MAX{x,y) ((x) > (y)?(2 * (x)): (2 * (y)))
但是,可读性受到了影响。而且,它还不利于程序运行调试。因为在被跟踪的源代码中,已由编译器作了宏代换预处理,没有对应的语句。
以上问题可以通过把宏改成内联函数来解决:
// TWOMax --- imp1ement as inline function
inline twoMax(int x, int y)
{
return x > y?2*x:2*y;
}
本章小结:
存储类型决定了名字在内存中的位置,存储类型也规定了在多文件程序中的连接特性。
非静态的全局名字具有外部存储类型的属性,它使得程序中的诸文件之间共享该名字,
前提是在程序的各文件中当且仅当只有一个名字定义而其他皆为声明。
静态就是让变量和函数在声明的区域内成为私有。这使得多文件协作编程的数据交错
使用状态下可靠性增强。
作用域规则规定了程序中名字的有效范围,它给名字的可见性提供了依据。所有的变
量都有作用域和可见性。
为使在不同源文件中保持声明的一致性,采用头文件。
预处理器对源文件进行初次处理,处理时,它忽略注释语句,加入.h头文件,并按宏定义进行替换。
在面向对象程序设计中,程序结构的意义扩展到了类。类作用域,名空间,类及对象的连接待性,以及程序的合理分解都是新的内容,这些内容将在后继章节中介绍。
第三部分 课堂练习
一、填空题:
1、全局变量和__________________若在定义时未进行初始化,则自动初始化为0。
2、设文件f.cpp中的一个函数要访问在另一个文件中定义的int型变量x,则在f.cpp中x应定义为__________________________。
3、如果一函数定义中使用了( )修饰,则该函数不允许其他文件中的函数调用。
4、在函数外定义的变量称为( )变量。
5、已知文件F.CPP中的一个函数要访问在另一个文件中定义的int型变量var,则在F.CPP中var应说明为( )。
二、选择题:
1、(单选)关于局部变量,下列说法正确的是( )。
A.定义该变量的程序文件中的函数都可以访问。
B.定义该变量的函数中的定义处以下的任何语句都可以访问。
C.定义该变量的复合语句中的定义处以下的任何语句都可以访问。
D.局部变量可用于函数之间传递数据。
2、(单选)关于全局变量,下列说法正确的是( )。
A.任何全局变量都可以被应用系统中任何程序文件中的任何函数访问。
B.任何全局变量都只能被定义它的程序文件中的函数访问。
C.任何全局变量都只能被定义它的函数中的语句访问。
D.全局变量可用于函数之间传递数据。
3、(单选)不进行初始化即可自动获得初值0的变量包括( )。
A.任何用static修饰的变量
B.任何在函数外定义的变量
C.局部变量和用static修饰的全局变量
D.全局变量和用static修饰的局部变量
3、(多选) 已知一函数中有下列变量定义,其中属于自动变量的有( )。
A.double k; B.register int i;
C.static char c; D.auto long m;
4、(多选)关于局部变量,下列说法正确的是( )。
A.局部变量只能定义于函数体的首部。
B.局部变量可以定义于函数体的任何位置。
C.局部变量允许同一函数中定义处之后的语句访问。
D.局部变量允许同一复合语句中定义处之后的语句访问。
5、关于全局变量,下列说法正确的是( )。
A.全局变量必须定义于文件的首部,位于任何函数定义之前。
B.全局变量可以在函数中定义。
C.要访问定义于其他文件中的全局变量,必须进行extern说明。
D.要访问定义于其他文件中的全局变量,该变量定义中必须用static加以修饰。
6、(多选)已知一函数中有下列变量定义,其中属于静态变量的有( )。
A.static k; B.register int i;
C.static char c; D.Auto long m;
三、回答题:
1、什么叫做作用域?有哪几种类型的作用域?
2、什么叫做可见性?可见性的一般规则是什么?
3、下面的程序的运行结果是什么,实际运行一下,看看与你的没想有何不同。
# include <iostream. h>
void myFunction () ;
int x= 5,y= 7;
int main()
{
cout << " x from main: " << x <<"/n";
cout << "y from main: " << y <<"/n/n";
myFunction ( ) ;
cout <<"Back from myFunction ! /n/n";
cout <<"x from main: " << x <<"/n";
cout <<"y from main: " << y <<"/n";
return 0 ;
}
void myFunction ( )
{
int y=10;
cout <<" x from myFunction : "<<x<<"/n";
cout <<" y from myFunction : "<<y<<"/n/n"; }
4、写出下列程序的运行结果:
//file1.cpp
static int i=20;
int x ;
static int g (int p)
{
return i+p;
}
void f (int v)
{
x=g (v);
}
//file2.cpp
#include <iostream.h>
extern int x;
void f (int);
void main ()
{
int i=5;
f (i);
cout<<x;
}
回答以下问题:
(1) 程序的运行结果是什么样的?
(2)为什么文件file2.cpp中要包含头文件<iostream.h>?
(3)在函数main()中是否可以直接调用函数g()?为什么?
(4)如果把文件file1.cpp中的两个函数定义的位置换一下,程序是否正确?为什么?
(5) 文件file1.cpp和file2.cpp中的变量i的作用域分别是怎样的?在程序中直接标出两个变量各自的作用域。

第五部分 复习要求:
1、 理解:标识符的作用域的含义,作用域与标识符的可见性之间的关系。能够指出给定程序中标识符的作用范围,包括局部作用域、函数作用域、文件作用域。
2、 理解:标识符的三种生命期:静态、局部和动态生命期,能指出给定程序中标识符的生命期。
3、 理解:多文件结构,能在程序设计中合理利用多文件来进行模块划分。能正确使用外部存储类型和静态存储类型。
4、 理解:编译预处理的概念,能在程序中合理使用#include预处理指令,看懂#define,#if,#ifndef,#else,#undef,#elif等指令。
5、 综合应用:能综合运用头文件、外部变量、静态变量来组织多文件结构的程序。


第五章 数组


第一部分 本章知识点
数组的概念、定义、初始化、访问数组元素
向函数传递数组
多维数组
第二部分 内容精讲
5.1、一维数组的定义和引用
一、 定义形式:类型说明符 数组名[常量表达式]
例如: int a[10]; 表示定义了一个一维数组,数组名为a,此数组共有10个元素,且均为整型,这10个元素分别为a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]。
说明:
(1)数组名定名与变量相同,都遵循标识符的定名规则。
(2)常量表达式表示此数组元素的个数,即数组长度。
(3)常量表达式中可以是常量和符号常量,但不能是变量。(c++不允许对数组的大小作动态定义,只能是静态定义)
(4)数组名有其特殊的含义:数组名代表此数组在内存中存储的起始位置。
二、数组元素的访问
数组元素的引用形式:数组名[下标]
说明:
(1)下标的含义:下标是数组元素到本数组起始的偏移量。
(2)下标可以是整常数或整型表达式。如a[2+1]、a[i+j](i和j均为整型)
(3)数组元素下标从0开始。
(4)引用数组时注意下标不要"出界"。如以上a数组中没有a[10]、a[11]……
例如: #include <iostream.h>
void main()
{char chArray[30];
cin.get(chArray,30);
for(int i=0;chArray[i]!=`/0'; i++)
cout<<chArray[i];
cout<<endl;}
此程序的说明:
1、get()是输入流成员函数。
原型:get(char *target, int count, char delimeter=`/n');
功能:从键盘输入一系列字符,直到输入流中出现结束符或所读字符个数已达到要求读的个数止。
说明:(1)target:存放一系列字符的空间地址,count:限制读取字符的最长个数,delimeter:规定的结束符。
(2)使用时,前面必须加"cin."
2、函数体中第1行为定义一个字符数组,第3、4行中的chArray[i]为引用chArray数组中的第i号元素。
3、 第2行中的chArray代表第1行定义过的chArray[30]数组的存储的首地址。
三、给一维数组元素赋值
1、一维数组的初始化
(1)、什么是初始化:在定义数组的同时即对其元素赋初值。
(2)、数组初始化的方法:
(A)在定义数组时对数组元素赋初值。例如:static int a[5]={1,2,3,4,5};
(B)可以只给部分元素赋值:static int a[5]={1,2};其中a[0]=1,a[1]=2,其它未赋值的元素自动赋0
(C)在对全部数组元素赋初值时,可以不指定数组的长度。例如:static int a[]={1,2,3,4,5}
(D)对字符数组初始化有两种方法。
第一种:例如:char array[10]={"hello"}; 将一串字符赋给array数组中的各个元素,等同于第二种方法。注意不能说将一串字符依次赋给array。
第二种:例如:char array[10]={'h', 'e', 'l', 'l', 'o', '/0'}; 逐个元素赋值。
2、先定义一个数组,然后再赋值。
通常是使用一个for循环实现,当然也可以一个元素一个元素地赋值。
例如:#include <iostream.h>
void main()
{int i,a[10];
for(i=0; i<=9; i++)
a[i]= i;
for(i=9; i>=0; i--)
cout<<a[i];}
四、向函数传递数组
1、 数组名作函数参数:传递的是数组的首地址。(请看典型例题2)
介绍一个c++函数:memset()
原型:void *memset(void *, int , unsigned);
功能:可以一字节一字节地把整个数组设置为一个指定的值。
说明: (1)memset()函数在mem.h头文件中声明。
(2)第1个参数void *表示地址,用于接收数组的起始地址
(3)第2个参数int 是设置数组每个字节的值。
(4)第3个参数是数组的长度(字节数,不是元素个数)。
2、 数组元素作函数参数:传递的是数组元素的值。(同一般传值调用)
5.2、二维数组的定义和引用
一、 定义形式:类型说明符 数组名[常量表达式1] [常量表达式2]
例如: int a[3] [4]; 表示定义了一个二维数组,数组名为a,此数组共有12个元素,且均为整型,这12个元素分别为a[0] [0], a[0] [1], a[0] [2], a[0] [3], a[1] [0], a[1] [1], a[1] [2], a[1] [3], a[2][0], a[2][1],a[2] [2], a[2] [3]。
说明:
(1)常量表达式1表示此数组的行数,常量表达式2表示此数组的列数。
(2)数组名有其特殊的含义:数组名代表此二维数组在内存中存储的起始位置。
(3)二维数组在内存中的存储是按行存储。即先存储第0行,后存储第1行,再存储第2行。
二、二维数组元素的访问(引用)
数组元素的引用形式:数组名[下标1] [下标2]
说明:(1)下标的含义:下标1表示此元素所在的行号,下标2表示此元素所在的列号。
(2)数组元素下标从0行0列开始,引用时注意下标不要"出界"。如以上a数组中最后一个元素为a[2] [3]。
三、给二维数组元素赋值
1、二维数组的初始化
方法:(1)分行给二维数组元素赋初值。
例如:static int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
(2)可以将所有数据写在一个花括号内,按数组排列顺序对各元素赋值。
例如:static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
(3)可以只给部分元素赋值:static int a[3][4]={{1,2},{5}};其中a[0] [0]=1,a[0] [1]=2, a[1] [0]=5其它未赋值的元素自动赋0
(4)在对全部数组元素赋初值时,可以不指定数组的行的长度,但列数不能省,否则c++不知道到底是几乘几数组。例如:static int a[ ][4]={1,2,3,4,5,6,7,8,9,10,11,12}; 由列数判断出是3×4数组
2、先定义一个二维数组,然后再赋值。
通常是使用两个for循环嵌套实现,当然也可以一个元素一个元素地赋值。
例如:#include <iostream.h>
void main()
{int i,j,a[3][4];
for(i =0; i <3; i ++)
for(j=0;j<4;j++)
cin>>a[i][j];
for(i =0; i <3; i ++)
for(j=0;j<4;j++)
cout<<a[i][j];}
四、向函数传递数组
1、 数组名作函数参数:传递的是二维数组的首地址。(参见典型例题例3方法一、例4)
2、 数组元素作函数参数:传递的是数组元素的值。
五、降维处理:由于二维数组在内存中是线性排列的,传递一维数组和传递二维数组都是传的地址,所以在被调用的函数中用单重循环来遍历二维数组中的所有元素,此时只要传递数组名和元素总个数。要注意被传递的数组地址不要用数组名表示,要用第一个元素的地址表示,因为数组名表示的是二维数组的首地址,尽管地址值相同,但操作不同,在第8章中将详细解释地址与指针的差别。(参见典型例题例3方法二)
5.3、典型例题:
例1、 设学生人数N=8,提示用户个人的考试成绩,然后计算出平均成绩并显示出来。
程序如下:#include <iostream.h>
#include <string.h>
#define N 8
float grades[N];
void main()
{
int i;
float total,average;
for (i=0; i<N; i++)
{
cout<< "enter grade # "<<(i+1)<< ":";
cin>>grades[i];
}
total=0;
for(i=0; i<N; i++)
total+=grades[i];
average=total/N;
cout<< " Average grade: "<<average<<endl;
}

例2:冒泡排序法
冒泡排序法可以形象地描述为:使较小的值像空气泡一样逐渐"上浮"到数组的顶部,而较大的值逐渐"下沉"到数组的底部。这种排序技术要排序好几轮,每一轮都要比较连续的数组元素对。如果某对数值是按升序排列的,那就保持原祥。如果按降序排列,就交换它们的值。
原始数据 55,2,6,4,32,12,9,73,26,37
第1遍结果 2,55,6,4,32,12,9,73,26,37
第2遍结果 2,6,55,4,32,12,9,73,26,37
第3遍结果 2,6,4,55,32,12,9,73,26,37
第4遍结果 2,6,4,32,55,12,9,73,26,37
第一轮 第5遍结果 2,6,4,32,12,55,9,73,26,37
第6遍结果 2,6,4,32,12,9,55,73,26,37
第7遍结果 2,6,4,32,12,9,55,73,26,37
第8遍结果 2,6,4,32,12,9,55,26,73,37
第9遍结果 2,6,4,32,12,9,55,26,37,73
第二轮开始数据 2,6,4,32,12,9,55,26,37,73
第1遍结果 2,6,4,32,12,9,55,26,37,73
第2遍结果 2,4,6,32,12,9,55,26,37,73
第3遍结果 2,4,6,32,12,9,55,26,37,73
第二轮 第4遍结果 2,4,6,12,32,9,55,26,37,73
第5遍结果 2,4,6,12,9,32,55,26,37,73
第6遍结果 2,4,6,12,9,32,55,26,37,73
第7遍结果 2,4,6,12,9,32,26,55,37,73
第8遍结果 2,4,6,12,9,32,26,37,55,73
第三轮 7遍
第四轮 6遍
第五轮 5遍
第六轮 4遍
第七轮 3遍
第八轮 2遍
第九轮 1遍
第一轮的目的是"沉"一个最大的数到最后,为完成此任务两两比较要比较N-1遍(9遍);第二轮的目的是"沉"倒数第二大的数到后面第二位,为完成此任务两两比较要比较N-2遍(8遍);第三轮要比N-3遍(7遍)......由此得出结论:若用pass控制轮(外循环),用i控制遍(内循环),则第pass轮中要执行内循环i =N-pass遍。
下面的程序把有10个元素的数组用冒泡排序法按升序排列:
# include <iostream.h>
void bubble(int[],int);
void main()
{int array[]={55,2,6,4,32,12,9,73,26,37};
int len=sizeof(array)/sizeof(int);
for(int i =0; i <len; i ++)
cout<<array[i]<< ",";
cout<<endl<<endl;
bubble(array,len);
}
void bubble(int a[],int size)
{int i,temp;
for (int pass=1;pass<size;pass++)
{for(i =0; i <size-pass; i ++)
if(a[i]>a[i +1])
{temp=a[i];
a[i]=a[i +1];
a[i +1]=temp; }
}
for(i =0; i <size; i ++)
cout<<a[i]<< ",";
cout<<endl;}

例3:定义一个3×4数组,表示3个学生,每个学生有4次测验成绩,求所有学生中的最好成绩。问题化作遍历二维数组找最大值,传递函数参数时,除了传递数组名外,还要传递行数和列数。
程序如下:
方法一:
# include <iostream.h>
int maximum(int[][4],int,int);
void main()
{
int sg[3][4]={{ 68,77,73,86},
{87,96,78,89},
{90,70,8l,86}};
cout <<"the max grade is"<<maximum(sg,3,4)<<endl;
}
int maximum(int grade[][4],int pupiles,int tests)
{
int max=0;
for(int i=0; i<pupiles; i++)
for(int j=0;j<tests;j++)
if(grade[i][j]>max)
max=grade[i][j];
return max;}
运行结果为: the max grade is 96

方法二: 主函数将第一个数组元素地址作为一维数组首地址传递给maximum()函数(实参),maximum()函数也以一维整型数组的首地址来接受,求取学生成绩的最大值。
# include <iostream.h>
int maximum(int[],int);
void main()
{
int sg[3][4]={{68,77,73,86},
{87,96,78,89},
{90,70,8l,86}};
cout <<"the max grade is"<<maximum(&sg[0][0],3*4)<<endl;
}
int maximum(int grade[],int num)
{
int max=0;
for(int i=0; i<num; i++)
if(grade[i] >max)
max=grade[i];
return max;}
运行结果为: the max grade is 96
例4:统计学生成绩
全局二维数组表示5个学生4门功课的成绩,通过函数调用求出:
(1)输入每个学生的成绩。
(2)求出每个学生的总分,并输出。
(3)求出每门功课的平均分,并输出。
[解答]
每个要求都设计一个函数,分别为Input()、Total()和Average()。
Input ()对全局数组中的元素赋值。
Total ()逐个求学生的总成绩并打印,所以有两重循环。
Average()逐个求每门功课的平均分,也是两重循环。
在主函数中分别调用这三个函数,他们都是先声明,在主函数后再定义
//--------------------------
# include < iostream.h >
//--------------------------
const int n=5;
const int m=4;
int a[n] [m] ;
//-----------------------------
void Input() ;
void Total () ;
void Average ( );
//--------------------
void main( )
{
Input ( ) ;
Total ( ) ;
Average ( ) ;
}
//--------------------
void Input ( )
{
cout << "请输入5组各4门功课成绩: / n ";
for(int i=0; i<n; i++)
for(int j =0; j<m; j ++)
cin >>a[i] [j];
cout << endl;
}
//----------------
void Total ( )
{
int sum;
for(int i=0; i<n; i++)
{
sum= 0;
for(int j =0; j<m; j ++ )
sum += a[i] [j];
cout << "第" << (i + l) << "个学生总分:"<< sum<< endl;
}
cout << endl ;
}
//------------
void Average ( )
{ int sum;
for{int i=0; i<m; i++)
{
sum= 0;
for(int j =0;j <n; j ++ )
sum += a[j] [i] ;
cout << "第" << (i + l) << "门平均成绩:"<< double (sum)/n<< eldl;
}
cout << endl;
}
//--------------------
运行结果:
请输入5组各4门功课成绩:
78 88 90 91
68 77 68 91
82 77 67 68
70 61 66 54
50 60 71 73
第1个学生总分:347
第2个学生总分:304
第3个学生总分:294
第4个学生总分:25l
策5个学生总分:254

第l门平均成绩:69.6
第2门平均成绩:72.6
第3门平均成绩:72.4
第4门平均成绩:75.4
例5:在主调函数中初始化一个矩阵并将每个元素都输出,然后调用子函数,分别计算每一行的元素之和,将和直接存放在每行的第一个元素中,返回主调函数之后输出各行元素的和。
程序如下: # include <iostream.h>
void Rowsum (int A[][4], int nrow)
{
for (int i=0; i<nrow; i++)
{
for ( int j=1;j<4;j++)
A[i][0]+=A[i][j];
}
}
void main(void)
{
int Table[3][4]={{1,2,3,4},{2,3,4,5},{3,4,5,6}}:
for (int i=0; i<3; i++)
{
for (int j=0;j<4;j++)
cout<<Table[i][j]<< " ";
cout<<endl;
}
RowSum(Table,3);
for (i=0; i<3; i++)
{
cout<< "Sum of row "<<i<< "is "<<Table[i][0]<<endl;
}
}
程序运行结果:
1 2 3 4
2 3 4 5
3 4 5 6
Sum of row 0 is 10
Sum of row 1 is 14
Sum of row 2 is 18

例6:矩阵乘法
如果矩阵A乘以B得到C,则必须满足如下规则:
(1)矩阵A的列数与矩阵B的行数相等; .
(2)矩阵A的行数等于矩阵C的行数;
(3)矩阵B的列数等于矩阵C的列数。
例如,下面的例子说明两个矩阵是如何相乘的:
5 7 88 29 79
12 3 6
8 3 = 108 30 69
4 2 7
7 4 100 29 70
在结果矩阵中,第1行第1列的元素是88,它通过下列计算得来:
5×12十7×4=88
即若矩阵Amn×Bn1=Cm1,则:
cij=∑aik×bkj (k=0,1,…n-1)
其中,Amn表示m×n矩阵,cij是矩阵C的第i行j列元素。
下列程序是求A34×B45=C35的矩阵乘法:
# include <iostream.h>
# include <iomanip.h>
int a[3][4]={{5,7,8,2},{-2,4,1,1},{1,2,3,4}};
int b[4][5]={{4,-2,3,3,9},{4,3,8,-1,2},{2,3,5,2,7},{1,0,6,3,4}};
int c[3][5];
int Multimatrix(int a[][4],int arow,int acol,int b[][5],int brow,int bcol,int c[][5],int crow,int ccol);
void main()
{if(Multimatrix(a,3,4,b,4,5,c,3,5))
{cout<< "illegal matrix multiply./n ";
return;}
for(int i=0; i<3; i++)
{for(int j=0;j<5;j++)
cout<<setw(5)<<c[i][j];
cout<<endl;
}}
int Multimatrix(int a[][4],int arow,int acol,int b[][5],int brow,int bcol,int c[][5],int crow,int ccol)
{if(acol!=brow)
return 1;
if(crow!=arow)
return 1;
if(ccol!=bcol)
return 1;
for(int i=0; i<crow; i++)
for(int j=0;j<ccol;j++)
{c[i][j]=0;
for(int n=0;n<acol;n++)
c[i][j]+=a[i][n]*b[n][j];}
return 0;}
第四部分 课堂作业:
一、 填空题:
1.已知数组a定义为int a[]={3,6,9,12};,则a的各元素的值分别是( ),最小下标是( ),最大下标是( )。
2.已知数组b定义为int b[6]={9,6,3};, 则b的各元素的值分别是( ),最小下标是( ),最大下标是( )。
3.已知数组c定义为long c[50];,则c的元素个数是( ),最小下标是( ),最大下标是( )。
4.已知数组A为一有14个单元的长整型数组,下面的语句试图从A[0]开始,隔一个输出一个A中的元素;请补充完整下面的语句:
for( ;j<14; ) cout<< ;
5.已知数组s为一有18个单元的整型数组,下面的语句试图求出这18个单元的平均值,并用s保存这个值;请补充完整下面的语句:
int s= ;
for(int ; ;j++) ;
s 18;
6.已知数组d定义为double d[4][11];,则d是一个( )行( )列的二维数组,总共有( )个元素,最大行下标是( ),最大列下标是( )。
7.已知数组e定义为int e[][5]={{1,2,3},{5,6},{8}};,则e是一个( )行( )列的二维数组,总共有( )个元素,最大行下标是( ),最大列下标是( ),其按行列出的各元素的值是( )。
8.已知数组f定义为doube f[4][3]={{1,2},{5},{6,7,8}};则e是一个( )行( )列的二维数组,总共有( )个元素,其按行列出的各元素的值是( )。
9.要使g成为具有如下初始值的二维int型数组:
1 2 3 4 5
1 2 3 4 0
1 2 3 0 0
0 0 0 0 0
0 0 0 0 0
则最简单的定义g的语句是( )。
10.下面的语句显示输出8行8列数组h的主对角线上的所有元素,请补充完整:
for( ; ;i ++) cout<< <<'';
11.下面的语句按行显示输出5行3列数组m的所有元素,从例数第一行开始输出,最后输出首行,请补充完整:
for(int i = ;i < ; ){
cout<<endl;
for( ; ;j++) cout<< <<'';
12.执行语句序列
char strl[]="ABCD ",str2[10]="XYZxyz ";
for(int i=0;str2[i]=strl[i];i ++);
后,数组str2中的字符串是( )。
13.执行语句序列
char s1[10]="abcdef ",s2[20]="inter ";
cin>>s1;
int k=0,j=0;
while(s2[k]) k++;
while(s1[j]) s2[--k]=s1[++j]);
时,若键盘输人的是net,则s1中的字符串是( ),s2中的字符串是( )。
14.已知s1,s2和s3是三个有足够元素个数的字符串变量,其值分别是"aaa","bbbb"和"ccccc ",执行语句strcat(s1,strcat(s2,s3));后,s1,s2,和s3的值分别是( )。
15.已知s1,s2和s3是三个有足够元素个数的字符串变量,其值分别是"aaa","bbbb"和"ccccc ",执行语句strcat(strcpy(s2;s3),s1);后,s1,s2和s3的值分别是( )。
16.已知s1,s2和s3是三个有足够元素个数的字符中变量,其值分别是"abc","abf"和"abcd ",执行语句strcat(s3,strcmp(s1,s2)>0?s1:s2);后,s1,s2和s3的值分别是( )。
二、 选择题:
1.(多选)要定义一个int型一维数组array,并使其各元素具有初值9,0,3,0,0,正确的定义语句有( )。
A.int array[]={9,0,3}; B.int array[5]={9,0,3};
C.int array[5]={9,0,3,0}; D.int array[] = {9,0,3,0,0};
2.(多选)要定义一个int型二维数组array2,并使其各元素具有初值
2 3 0 0
3 0 0 0
0 0 0 0
1 2 3 4
正确的定义语句有( )。
A.int array2[][4]={{2,3},{3},{0},{1,2,3,4}};
B.int array2[][4]={{2,3,0,0},{3,0,0,0},{0,0,0,0},{1,2,3,4}};
C.int array2[4][4]={{2,3},{3},{},{1,2,3,4}};
D.int array[4][4]={{2,3},{37,{1,2,3,4}};
3.(多选)要使字符串变量str具有初值"hello!",正确的定义语句有
A.char str[6]={'h','e','l','1','o','!'};
B.char str[6]="hello! ";
C.char str[]={'h','e','1','1','o','!','\0'};
D.Char str[]="hello! ";
4.(多选)要使字符串数组STR含有"ABC","EF"和"K"三个字符串,正确的定义语句有( )。
A.char STR[][6]={ "ABC ","EK ", "K "};
B.char STR[][5]={ "ABC ","EF ",'K'};
C.Char STR[][4]={{'A','B','C','\0'},"EF ","K "};
D.char STR[][3]={ "ABc ","Er ","K"};
回答题:
1、 在数组A[20]中第一个元素和最后一个元素是哪一个?
2、 数组A[10][5][15]一共有多少个元素?
3、 用一条语句定义一个有五个元素的整型数组,并依次赋予1~5的初值。
4、 已知有一个整型数组,数组名为oneArray,用一条语句求出其元素的个数。
5、 用一条语句定义一个有5×3个元素的二维整型数组,并依次次赋予1~15的初值。
6、 在字符串"Hello,world!"中结束符是什么?
编程:
1、输入8个整数,然后按输入的相反顺序显示这些数据。
2、一个10个整数的数组(34,91,83,56,29,93,56,12,88,72),找出最小数和其下标,并在主函数中打印最小数和下标。
3、 显示输出100以内的所有质数。
4、 编写一个函数,统计一个英文句子中字母的个数,在主程序中实现输入、输出。
5、 设学生人数N=8,提示用户输入N个人的考试成绩,然后计算出平均成绩并显示出来。
6、 设计一程序,它输入一个5行5列的矩阵,计算并显示输出该矩阵四周那一圈元素的合计值。
7、n个数,已按从小到大顺序排列。在主函数中键人一个数,调用一个函数,它把键入的数插入到原有数列中,保持原有顺序,并将被挤出的最大数(有可能就是被插入数)返回给主函数输出。
8、17个人围成圈,编号为1~17,从第1号开始报数,报到3的倍数的人离开,一直数下去。直到最后只剩下1人。求此人的编号。
9、输入一个n×n的矩阵,求出两条对角线元素值之和。
10、5个学生,4门课,要求主函数分别调用各函数实现:
(1)找出成绩最高的学生序号和课程。
(2)找出不及格课程的学生序号及其各门课的全部成绩。
(3)求全部学生各门课程的平均分数,并输出。
11、编程求矩阵的加法:
5 7 8 4 -2 3
2 -2 4 + 3 9 4
1 1 1 8 -1 2
12、设计一程序,它输入一个字符串,如果其中有子串"BOY",将之替为"CHILD";显示替换后的字符串。


第六部分 复习要求:
熟练掌握内容:
1、一、二维数组的定义、初始化、引用(特别是使用两个for循环嵌套来访问二维数组中的元素)。
2、深刻理解数组名所代表的含义。
3、向函数传递数组的几种情况。
4、认真读懂几个典型例题,并模仿编程课后作业。

第六章 指针


第一部分 本章知识点
指针的概念、定义、初始化、运用
指针变量的空间申请和释放
&和*运算符的使用
字符指针与字符串操作
命令行参数
第二部分 内容精讲
6.1 指针的概念
一、 指针的概念:
1、 变量的直接访问方式:按变量的地址存取变量值的方式。
例如:# include <iostream.h>
main()
{
int i ;
i =3;
cout<< i<<endl;
}
执行过程:(1)系统分配存储单元给变量i
(2)给相应的存储单元赋值
(3)根据变量名与地址的对应关系,找到变量i的地址,然后从该存储地址开始读出相应的数据值,输出。
2、 变量的间接访问方式:将变量地址存放在另一个内存单元中。
先定义一个变量如i _pointer用于存放某个整型变量的地址,然后将i 的地址赋给i _pointer,在输出i的值时,不是直接使用i,而是先找到i _pointer,然后根据i _pointer里的地址,找到i的存储地址,再输出其内的值。
例如:# include <iostream.h>
void main()
{
int i =3;
int * i _pointer;
i _pointer=&i;
cout<< * i _pointer<<endl;
}
二、 指针变量的定义:
1、 变量的指针:一个变量的地址称为该变量的指针。
2、 指针变量:专门用来存放另一变量的地址的变量称为指针变量。
3、 定义格式:类型标识符 * 变量标识符
例如 int * i _pointer; //定义一个指向整型变量的指针变量。
float * iptr; //定义一个指向实型变量的指针变量。
char * cptr; //定义一个指向字符型变量的指针变量。
4、 指针变量的指向:将某变量的地址赋给一个指针变量,我们就说该指针变量指向了某变量。
例如 int * iptr; //定义一个指向整型变量的指针变量。
int icount=18; //定义一个整型变量并赋初值。
iptr=&icount; //将变量icount的地址赋给指针变量iptr(使指针变量iptr指向整型变量icount)
注意:指针是有类型的。定义好一个指针变量后,该指针变量只能指向同一类型的变量,不能指向其它类型的变量。例如:
int * iptr;
float icount=18;
iptr=&icount; //错误:不能将一个实型变量的地址赋给一个只能指向整型变量的指针变量。
三、指针变量的引用:
例如: # include <iostream.h>
void main()
{int * iptr; //定义一个指向整型变量的指针变量。
int icount=18; //定义一个整型变量并赋初值。
iptr=&icount; //将变量icount的地址赋给指针变量iptr(使指针变量iptr指向整型变量icount)
cout<<* iptr<< endl; //引用指针变量iptr,以得到其所指向变量的值,即
icount的值。
cout<< iptr<< endl; //输出指针变量iptr自身的存储地址。
}
注意:以上程序中,两处出现* iptr,其作用是不同的。第一次是定义该指针变量,第二次是引用定义过的指针变量iptr,以得到其所指向变量的值。
指针变量里放的是其所指向对象的内存地址,与所指对象的值是两个概念。
又如:int icount=26;
int * iptr;
iptr =&icount; //正确:左边是指针变量iiptr,右边是变量icount 的地址。
两边类型匹配。
(若写成:*iptr =&icount; //错误:左边是iiptr所指向变量的值,而右边是变量icount
的地址。两边类型不匹配。)
* iptr=50; //正确:将数据50赋给iptr指针变量所指向的变量。
四、 指针变量的初始化:
在定义一个指针变量时,即将其指向某一变量。
例如:int * iptr=&icount;
注意:(1)指针变量的定义与引用的区别。如:int *iptr 和 * iptr的区别。
(2)指针在使用前,要进行初始化(有所指向)。
(3)定义指针变量时一定要说明其为指向什么类型的指针变量,一但定义好后,在赋值时,只能将同类型的变量的地址赋给其。
五、 指针运算:
指针可以进行加减运算。
可以把数组的起始地址赋给一指针,通过移动指针(加减指针)来对数组元素进行操作。
在16位机器上,浮点数占4字节,长整数占4字节,字符占1字节,双精度数占8字节,所以:
对浮点型指针加6实际加24;
对长整型指针加5实际加20;
对字符型指针加7实际加7;
对双精度型指针加2实际加16;
注意:只有加法和减法可用于指针运算。
6.2 指针与数组
数组由数组元素组成,每个数组元素在内存中都占有一定的存储单元。指针变量可以指向变量,也可以指向数组和数组元素。
一、概念
数组的指针:数组的起始地址。
数组元素的指针:是某个数组元素的地址。
数组名可以拿来初始化指针,数组名是数组的起始地址,即数组第一个元素的地址。
即对于一维数组a 有:
a等于&a[0]
此外,对于:
int a[100];
int * iptr=a; //数组名可以拿来初始化指针
对于第i个元素有:
a[i] 等价于 * (a+i) 等价于 iptr[i] 等价于 * (iptr+ i)
&a[i] 等价于a+i 等价于 &iptr[i] 等价于iptr+ i
注意:数组名是指针常量,区别于指针变量。
iptr++可以,但a++不行。(数组名表示内存中分配给数组的固定位置,修改了这个数组名,就会丢失数组空间。
例如:输出a数组的全部元素。(三种方法)
(1) 下标法: for (i =0; i <10; i ++)
cout<<a[i] <<","<<endl;
(2) 通过数组名计算数组元素地址,然后找出元素的值。(地址法)
for (i =0; i <10; i ++)
cout<<*(a+ i)<<","<<endl; //通过数组名先计算出数组元素地址,然后输出元素的值
(3) 用指针变量指向数组元素 (指针法)
for (p =a; p <(a+10); p ++)
cout<<*p<<","<<endl; //输出指针变量p所指向的数组元素的值。
二、传递数组的指针性质
一旦把数组作为参数传递到函数中,则在栈上定义了指针,可以对该指针进行递增、递减操作。
例如:下面的代码传递一个数组,并对其进行求和运算:
# include <iostream.h>
void sum(int *array[],int n)
{int sum=0;
for (int i =0; i <n; i ++)
{sum+=*array;
array++;
}
cout<<sum<<endl;
}
void main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};
sum(a, 10);
}
说明:
(1)、在主函数中,对sum()函数进行了调用。发生调用时,实参为数组名,是地址,因此要求在定义sum()函数时的形参也应为地址类型。本例sum(int *array[],int n)中的第一个参数为指针变量array,(注意:array不是数组名,若为数组名应对其说明为sum(int array[],int n),说明为sum(int *array[],int n)意即定义了一个指针变量,可以指向某一整型数组),当发生调用时,调用函数将a代表的地址传递给了array变量,即在内存中,array指向了调用函数中的a数组的起始位置,由于array为指针,因此可以通过指针array的下移(array++)来得到原主函数中的a数组中的各元素的地址。
(2) 本例中声明sum (int *array[], int n)与sum (int *array, int n)是等价的。前式中的 *array[]样子是数组,我们前面讲过的数组是直接使用数组名,这里未使用数组名,而是通过一指针指向某数组的起始位置,效果是一样的;但数组名不能作为左值,指针可以作为左值(数组名不能进行++或――运算,而指针可以)。
三、 多维数组的指针和指向多维数组的指针变量
1、 多维数组中的地址问题:见下文(刊登在《电脑知识》1998.8)。

2、指向数组元素的指针变量:同一般指针。
例如:用指针变量输出数组元素的值。
# include <iostream.h>
void main()
{
static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
int *p;
for (p=a[0];p<a[0]+12;p++) //将第0行的首地址a[0]赋给p,通过p++访问下一元素
{if (p-a[0]%4==0) cout<<"/n"; //每行输出4个元素
cout<<*p<<",";}
3、 指向包含m个数据的行指针
定义格式:类型说明符 (*p)[m]
例如:输出二维数组任一行任一列元素的值。
# include <iostream.h>
void main()
{
static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[4], i,j; //定义一个指向具有4个整型元素的行指针p,即可以指向数组的一行。
p=a; //将二维数组的首地址a(行地址)赋给p,p一旦变化则指向下一行。
cin>> i >>j>>endl;
cout<<*( *(p+ i)+j) >>endl;}
说明:最后一行中,(p+ i)确定要输出元素所在的行的首地址(行地址),*(p+ i)是将要输出元素所在的行的首地址(行地址)转化为列地址,*(p+ i)加上j得到要输出元素的确切地址,外面再加*得到其值。注意:若这样书写不行Cout<<*( (p+ i)+j) >>endl;为什么?
四、 多维数组的指针作函数参数
(a)二维数组的数组名(行地址)或行指针作函数参数:见典型例题2、3
(b)列地址作函数参数:见典型例题2
6.3、堆内存分配
一、堆内存
堆是内存空间。堆是区别于栈区、全局数据区和代码区的另一个内存区域。堆允许程序在运行时(而不是在编译时),申请某个大小的内存空间。
二、 获得堆内存
函数malloc()是C程序获得堆内存的一种方法。malloc()函数的原型为:
void *molloc(size_t size);
说明:(1)malloc()函数在alloc.h头文件中声明
(2)size_t为unsigned long
(3)该函数的功能为:从堆内存中开辟一块size大小的内存,将指向该内存的地址作为该函数的返回值。
三、 释放堆内存
函数free()释放由malloc()函数分配的堆内存。原型为:
void free(void * );
说明:free()中的参数是先前调用malloc()函数时返回的地址。
四、 new与delete
(1)new
格式1:new 类型说明[ 元素个数 ]
功能:申请一维数组空间。
格式2:new 类型说明[行数][列数]
功能:申请二维数组空间。
说明:第一维(元素个数或行数等)是在运行时求值的任意表达式,而第二维(列数为常数)开始必须是在编译时即可求值的常量表达式。
(2)delete
格式:delete[] 指针表达式
例如: *ap=new int [10];
double (*matrix)[20]=new double[20][20];
delete []ap,[]matrix;
注意:对于申请的数组空间无法进行初始化。
例如: int *p1=new int[n]; //正确
int *p2=new int[n][6]; //正确
int *p3=new int[m][n]; //错误
int *p4=new int[10][n]; //错误
(见下一章例题)
6.4 const指针(常值指针)
可以用const把一个指针定义为常值指针。
一、 指向常量的指针:在指针定义语句的类型前加const,表示指向的对象是常量。
例如:const char *s="Hello! "; //或char const *s="Hello! ";
说明:(1)定义指向常量的指针,必须将const放在*号之前。
(2)定义后,s所指向的数据是不可以改变的,但s本身可改变。即可以让s指向别的字符或字符串。例如
s="Hi! "; //正确
* s=='y'; //错误
(3)定义这样的常值指针时不必初始化。例如
const char *s;
s="Hello! ";
定义"const int *pi=&a;",即告诉编译,*pi是常量,定义后便不能将*pi作为左值进行操作。
二、 指针常量:在指针定义语句的指针名前加const,表示指针本身是常量。
例如:
char *const s="Hello! ";
说明:(1)可以改变s 所指向的数据,不可改变的是指针本身。(与前相反)
例如:s="Hello! "; //错误
*s='y'; //正确
(2)在定义指针常量时必须初始化。
定义 "int * const pc=&b;",即告诉编译,pc是常量,以后不能作为左值进行操作,但是允许修改间接访问值,即*pc可以修改。
三、 指向常量的指针常量:指针本身和指针所指向的数据都禁止改变。
例如:const char *const s="Hello! "; //或char const *const s="Hello! ";
定义 "const int * const pc=&b;"告诉编译,pc和*pc都是常量,它们都不能作为左值进行操作。


6.5 指针操作符的综合运用
假定指针p定义如下:
int d[]={3,6,9},*p=d;
请注意区分以下指针表达式含义:
*p十十:取p所指向单元的数据作为表达式的值,然后使p指向下一个单元;
(*p)十十:取p所指向单元的数据作为表达式的值,然后使该单元的数据值增1;
*十十p:使p指向下一个单元,然后取该单元的数据作为表达式的值;
十十*p:将p所指向单元的数据值增l并作为表达式的值。


6.6 指针与函数
一、 指针变量作为函数参数:
请看下面两个程序段
(1) # include <iostream.h>
void main()
{
int a=3,b=8;
cout << "a="<<a<<",b="<<b<<endl;
swap(a , b);
cout <<"a="<<a<<",b="<<b<<endl;
}
void swap(int x, int y)
{int temp=x;
x=y;
y=temp;
}
运行结果:
a=3,b=8
a=3,b=8
(2) # include <iostream.h>
void main()
{
int a=3,b=8;
cout << "a="<<a<<",b="<<b<<endl;
swap( &a , &b);
cout <<"a="<<a<<",b="<<b<<endl;
}
void swap(int *x, int * y)
{int temp=*x;
*x=*y;
*y=temp;
}
运行结果:
a=3,b=8
a=8,b=3
说明:
(1) 程序段1:尽管调用时a,b 分别将值传递给了x,y,但由于形参x,y 和实参a, b 是在编译时独立分配的单元,在swap()函数交换了x和y的值,但并不影响主函数中的a和b 的值。因此在主函数中两次输出a和b的值是一样的。若在swap()函数中输出x和y的值应该是x=8,y=3。
(2) 程序段2:使用了指针,x 和 y分别指向原主函数中的a和b 所在的内存地址,在swap()函数中改变了x和y所指向的单元的值,当回到主函数中时,实际上a 和 b所代表的内存单元中的值已发生了变化。
二、 指针函数:返回指针值的函数。
定义格式:类型修饰符 *函数名(形参表)
函数体
说明:
(1) 指针函数不能把在它内部说明的具有局部作用域的数据地址作为返回值。
(2) 可以返回堆地址,可以返回全局或静态变量的地址。
见典型例题3
三、 函数指针:指向函数的指针变量。
定义格式:类型修饰符 (*指针变量名)(形参表列)
例如:int (*func)(char a, char b);
说明:
(1)上式只定义了一个指向函数的指针变量,此时还汉有赋初值。
(2)在赋值时,只需将同类型函数的函数名赋给func即可。因为函数名代表了函数的入口地址。
(3) 对指向函数的指针变量,象func+n, func++,func-等运算是无意义的。
(4) 可以通过函数指针来调用函数。
(5) 可以用typedef来简化函数。
(6) 函数指针可用作函数参数。
(7) 函数指针可构成指针数组。
(8) 函数的返回类型可以是函数指针。
(9) 注意与指针函数的区别。
四、 void指针:是空类型指针,它不指向任何类型,即void指针仅仅只是一个地址。
说明:空类型指针不能进行指针运算,也不能进行间接引用。
6.7 字符指针
一、定义格式:char *指针变量名
例如: # include <iostream.h>
void main()
{
char *string ="I love china";
cout <<string<<endl ;
}
说明:(1)定义一个字符指针string ,将字符串"I love china"的首地址赋给string变量,或说使string指向了字符串"I love china",注意不是将字符串"I love china"赋给了string。
若有 cout <<*string 则输出的是"I"一个字符。
二、 字符串的赋值:
方法一:先定义一个字符数组,然后赋值。
例:char buffer[11];
for (int i =0; i <10; i ++)
cin>>a[i];
a[10]=`/0`;
或初始化:char buffer[11]= "I am a boy";
注意:不能这样:char buffer[11]; buffer="Hello";
方法二:使用字符指针
例:char *string="I love china"; 或 char *string; string=" I love china";
方法三:使用标准函数strcpy()
函数原型: char *strcpy (char *dest ,const char *src);
例如:
char buffer1[10];
char buffer2[10];
strcpy(buffer1,"hello");
strcpy(buffer2,buffer1);
注意:strcpy()仅能对以 '/0' 作结束符的字符数组进行操作。若要对其它类型的数组赋值可调用函数memcpy()。
例如:int intarray1[5]={1,3,5,7,9};
int intarray2[5];
memcpy(intarray2,intarray1,5*sizeof(int));
三、 字符串的比较:字符串的比较使用函数strcmp(),其原型为:
int strcmp(const char *str1 , const char *str2);
其返回值如下:
(1) 当str1串等于str2串时,返回值为0;
(2) 当str1串大于str2串时,返回一个正值;
(3) 当str1串小于str2串时,返回一个负值;
说明:两个字符串常量的比较是地址的比较。不能这样:if ("hello"= ="hello")
四、 字符指针与字符数组的区别
字符指针与字符数组都能实现字符串的存储和运算,但二者是有区别的:
(1) 字符数组由若干个元素组成,每个元素中存放一个字符,字符指针变量存放的是字符串的首地址。
(2) 赋值方式:
对字符数组各个元素赋初值,下列方式不行。
char str[14];
str=" I love china";
对字符指针赋值,下列方式是可以的。
char *str;
str="I love china";
(3) 在定义一个数组时,在编译时即分配内存单元,有确定的地址;对字符指针来说,由于没有给它赋初值,因此它没有确定的地址,其指向是不确定的。
(4) 指针变量的值是可以改变的,但对于数组变量,数组名的值是不能改变的。
见典型例题4、5
6.8 指针数组:一个数组中每个元素都是指针类型。
一、定义格式:类型说明符 *数组名[数组长度]
例如:int *p[4]; //定义了一个数组名为p的指针数组,其中有4个元素(p[0],p[1],p[2],p[3]),均为指针类型。
见典型例题6、7
二、 指针数组作main()函数的形参
形式:void main(int argc, char *argv[])
说明:argc 为参数个数。Argv是一个指向字符串的指针数组,用来存放参数的内容。如命令行:file1 china bejing
则argc=3, argv 为一指针数组,其中有三个元素argv[0],argv[1],argv[2]都为指针类型,分别指向三个字符串"file1","china","bejing"。
例如:# include <iostream.h>
void main(int argc,char *argv[])
{
while (argc>1)
{++argv;
cout<<*argv<<endl;
--argc; } }
输入命令行:file china beijing
运行结果:china
beijing
6.9 其他
1、 指向指针的指针
定义格式:类型说明符 **变量名
2、 NULL指针值:NULL是空指针值,它不指向任何地方。

6.10 典型例题:
例1:写一个函数,将一个3X3的矩阵转置。
程序如下:# include <iostream.h>
void move ( int *pointer )
{
int i,j,t;
for (i=0; i<3; i++)
for (j=i;j<3;j++)
{ t= * (pointer+3*i+j);
*(pointer+3*i+j)=*(pointer+3*j+i);
*( pointer+3*j+i)=t;
}
}
void main()
{
int a[3][3],*p, i;
for (i=0; i<3; i++)
cin>>a[i][0]>>a[i][1]>>a[i][2];
p=&a[0][0];
move(p);
for (i=0; i<3; i++)
cout<<a[i][0]<<a[i][1]<<a[i][2]<<endl;
}

例2:有一个班,3个学生,各学4门课,计算总平均分数,以及第n个学生的成绩。
程序如下:# include <iostream.h>
void average ( float *p, int n)
{ float *end;
float sum=0,aver;
end=p+(n-1)
for ( ; p<=end; p++)
sum=sum+(*p) ;
aver=sum/n;
cout <<aver<<endl;
}
void search (float (*p)[4] , int n) //第一个参数为一次指向具有4个
实型元素的指针变量p(行指针)
{int i;
for (i =0; i <4; i ++)
cout <<*(*(p+n)+ i)<<endl;
}
void main()
{
static float score[3][4]={65,67,70,60,80,87,90,81,74,67,88,92};
average (*score ,12); //第一个参数为列地址
search (score , 2); //第一个参数为行地址
}
例3:有若干个学生的成绩,要求在用户输入学生序号以后,能输出该学生的全部成绩。
程序如下:# include <iostream.h>
void main()
{
static float score[][4]={60,70,80,90,65,76,87,69,88,66,79,80};
float *search (float (* pointer )[4] , int n)
float * p;
int i, m;
cin<<m<<endl;
p = search (score ,m);
for (i =0; i <4; i ++)
cout<<*(p+ i)<< " ";
}
float *search (float (* pointer )[4] , int n) //定义一个返回值为指针
(指向实型数据)的函数searech。
{
float *pt;
pt = * ( pointer + n );
return (pt);
}

例4:编写函数int index (char *s,char *t), 返回字符串t 在字符串s中出现的最左边的位置,如果在s 中没有与t匹配的子串,就返回-1。
程序如下:# include <iostream.h>
int index (char *s, char *t)
{
int i,j,k;
for (i =0;s[i]!='/0' && t[0]!= '/0'; i ++)
{
for (j= i,k=0;t[k]!='/0' && s[j]==t[k];j++,k++)
;
if (t[k]=='/0')
return i;
}
return -1;
}
void main()
{
int n;
char str1[20],str2[20];
cout<<"输入一个英语单词:";
cin>>str1;
cout<<"输入另一个英语单词:";
cin>>str2;
n=index (str1,str2);
if (n>=0)
cout<<str2<<"在"<<str1<<"中左起第"<<n+1<<"个位置。"<<endl;
else
cout<<str2<<"不在"<<str1<<"中。"<<endl;
}
例5:编写函数reverse (char *s)的倒序递归程序,使字符串s倒序。
程序如下:
# include <iostream.h>
# include <sting.h>
void reverse (char *s, char *t)
{
char c;
if (s<t)
{
c=*s; s=t;*t=c;
reverse (++s, --t);
}
}
void reverse (char *s)
{
reverse( s,s+strlen(s)-1);
}
void main()
{
char str1[20];
cout<<"输入一个字符串:";
cin>>str1;
cout<<"原字符串为:"<<str1<<endl;
reverse (str1);
cout <<"倒序反转后为:"<<str1<<endl;
}

例6、先存储一个班学生的姓名,从键盘输入一个姓名,查找该人是否为该班学生?
解题思路:先设一个指针数组,使每个元素指向一个字符串,然后输入一个需要查 找的名字(字符串),将此名字与班上已有的名字比较.如果与其中之一相同,则使flag变量置1(表示已找到),如果在名单中找不到此名字则flag保持为零。最后根据flag的值决定输出的信息。
程序如下:
# include <iostream.h>
void main_ ( )
int i,flag=0;
char * name [5] = {"Li Fun","Zhang Li" ,"Ling Mao Ti" ,"Sun Fei","Wang Bio "} ;
char your_ name [20] ;
cout<<"enter your name : ") ;
gets (your _ name) ;
for (i=0;i<5;i+ + )
if (strcmp (name[i] ,your_name = = 0)
flag= l ;
puts (your _ name) ;
if (flag= = 1) .
cout<<"is in this class. " ;
else
cout<<"is not in this class. " ;
}
两次运行结果如下
enter your name :
Sun Fei
is in this class.
enter your name :
Zhang Bio
is not in this clas.
例7、 有三个字符串,要求按字母顺序输出。
本题用指针数组指向三个字符串。然后将字符串两两相比,进行排序。
程序如下:
# include "string. h"
main ( )
{ char *strtng [3] ={"Data structure", "Computer design" , "C Laguage"} ;
char * p;
int i;
if (strcmp (string [0] ,string [l ] ) >0)
{p=string [0] ;string [0] =string [l] ;string [l]= p; }
if (strcmp (string [0] ,string [2] ) >0)
{p= string [0] ;string [0] = string [2] ;string [2] = p; }
if (strcmp(string [l] ,string [2] ) >0)
{p= string [1] ;string [1] = string [2] ;string [2] = p; }
for (i=0;i<3; i+ + )
cout<<string [i]<<endl ;
}
运行结果如下:
C Language
Computer Design
Data structure
第四部分 课堂作业
一、 填空题:
1、若正常执行了如下语句:
int m[20], *p1=&m[5], *p2=m+17,n;
n=p2-p1;
则n的值为 ( )。
2、若正常执行了如下语句:
int m[]={1,2,3,4,5,6,7,8},*P1=m+7,*p2=&m[2];
p1-=3;
cout<<endl<<*p1<<` '<<*P2;
则程序的输出是( )。
3.执行
int a,*p=a;
cout<<p<<`,'<<sizeof(int);
cout<<endl<<(P十3);
若第一行的输出是:
0x0065FDF,4
则第二行的输出必然是( )。
4.p为一指针变量,使P指向前一个单元,然后取该单元的数据作为表达式的值的表达式是( )。
5.P为一指针变量,将p所指向单元的数据值减1并作为表达式的值的表达式是( )。
6.p为一指针变量,取p所指向单元的数据作为表达式的值,然后使p指向前一个单元的表达式是( )。
7.p为一指针变量,取p所指向单元的数据作为表达式的值,然后使该单元的数据值减1的表达式是( )。
8.若p指向字符串"ABCDEFGIJKLMN",则执行语句cout<<p+7;时显示在屏幕上的是( )。
9.已知变量1d定义为:1ong ld[30];,现需要一个指向1d的指针p,使得通过p能够取得数组1d的元素值,但又不能够修改ld中的元素,则p应定义为( )。
10.通过定义,p成为指向数组X的可用于访问X的元素的指针;已知X的定义是
double * X [7];
则p的定义是( )。
11.已知数组Inx定义为:int Inx[4][6];,并能顺利执行语句pl=Inx;,pl的定义是( )。
12.已知数组Jnx定义为:int Jnx[4][6];,并能顺利执行语句P2=&Jnx;,p2的定义是( )。
13.已知执行语句for(int i=0;i<=6;i++) cout<<WEEKDAY[i]<<',';后屏幕上显示的是
Sun,Mon,Tuc,Wed,Thu,Fri,Sat.
则一维数组WEEKDAY的定义是( )。
14.已知(*pfs)(10.57,"xYZ")是以正确的参数类型对函数fa的调用(fa无可选参数),则fa的原形是( ),pfa的定义是( )。
15.已知函数fb的原形是:void(*fb())(const char *);,pfb是指向fb的指针,则pfb的定义是( )。
16.已知变量x的定义是int x;,要申请一块能容纳x中数据且与x同值的动态空间,并使变量px指向这个动态空间,则px应定义为( ),申请动态空间的操作应表示为( )。
17.要使变量py指向一个具有5个单元的int型数组动态空间,且将该5个单元分别赋初值为1,2,3,4和5,则Py应定义为( ),申请动态空间的操作应表示为( ),数组赋初值操作应表示为( )。
18.已知变量z的定义是double(*z)[4][10];,要申请一块能容纳z中数据的动态空间,并使变量pz指向这个动态空间,则pz应定义为( ),申请动态空间的操作应表示为( )。
19.己知变量pl,p2和P3的定义是:
int *pl=new int[10];
int *p2=new int(10);
int **p3=new int *(P1);
要释放上面申请的所有动态空间的语句是( )。
20.下面的程序段包含了一系列指针操作,请在有关语句行注释区的括弧中填写对应于该行的输出内容:
int i,m[]={0,1,2,3,4,5,6,7,8,9,10,ll,12,13,14,15};
int P=m+2;cout<<p<<endl; //( )
p+=11;cout<<*p<<endl; //( )
p=m+11; cout<<*p<<endl; //( )
i=*p十8; cout<<i<<endl; //( )
i=*p一一;
cout<<i<<`, '<<*p<<`,'<<rn[10]<<endl; //( )
i=(*p)一一;
cout<<i<<`,'<<*p<<`,'<<m[10]<<endl; //( )
i=*++p;
cout<<i<<` ,'<<*p<<endl; //( )
i=*(p一=3);
cout<<i<<` ,'<<*p<<endl; //( )
*(p十1)=9;
cout<<*p<<` ,'<<*(p十1)<<endl; //( )
二、选择题:
1.下列说法不正确的是( )。
A.一个数组的地址就是该数组第一个元素(0号元素)的地址。
B.地址0专用于表示空指针。
C.地址值0可以用符号常量NULL表示。
D.两个指针相同是指它们的地址值相同。
2.空指针是指( )。
A.无指针值的指针。
B.不指向任何数据的指针。
C.无数据类型的指针。
D.既无指针值又无数据类型的指针。
3、设 int a=3, p=&a; , 则*p的值是( )。
A 变量a 的地址值 B 无意义 C 变量p的地址值 D 3
4、设int p2=&x,*p1=a;*p2=*b;则变量a和b的类型分别是( )
A int 和int B int *和int C int和int * D int *和int *
5、设p 为指针变量,则以下表达式正确的是( )
A --p++ B --++p C --(p+5) D (p-5)++
6.(多选)已知数组x定义为long X[10];,下面关于指针p的定义中,能用p访问X的首元素的有( )。
A.Long *p=X[0]; B.long * p=&X[0];
C.long *p=X; D.long *p=&x;
7.(多选)已知数组Y定义为int Y [10][20];,在下面关于指针p的定义中,能用p[i][j]访问Y[i][j]的有( )
A.int(*p)[20]=&Y[0]; B.int(*p)[20]=Y;
C.int p=&Y[0][0]; D.int(p)[10][20]=&Y:
8.已知一运行正常的程序中有这样两个语句:
int *pl, *p2=&a;
p1= *b;
由此可知,变量a和b的类型分别是( )。
A.int和int B.int和int *
C.int和int ** D.Int*和int*
9.已知一运行正常的程序中有这样两个语句:
int *p2=&x, *p1=a;
* P2= *b;
由此可知,变量a和b的类型分别是( )。
A.int和int B.int *和int
C.int和int* D.int * 和 int *
10.已知变量1d定义为:long ld;,在下面关于指针P的定义中,能通过语句*p=0;将1d置为0的是( )。
A.long *p; B. Long const *p=&1d;
C.long * const p=&1d; D. const long *p=&1d;
11.假定p是一指针变量,下列指针表达式中正确的是( )。
A.P+++=3 B. ++(P一一)
C.(++p)一一 D. 一一p++
12.假定p是一指针变量,下列指针表达式中正确的是( )。
A.p一一一一 B. 一一十十p
C.一-(p十3) D.(p一3)十十
13.(多选)已知k为整型变量,下列表达式中,与下标引用A[k]等效的是( )。
A. *(&A[0]十k) B.A十k
C. (A十k) D.A十 k
14.(多选)已知i,j都是整型变量,下列表达式中,与下标引用X[i][ j]等效的是( )。
A. *(*(X十i)十j) B. *( X十i十j)
C. *(&X [0]十i)[j] D. *(X[i]十j)
15.要使指针变量p指向l维数组A的第5个元素(下标为4的那个元素),正确的赋值表达式是( )。
A.p=&A或p=&A[4] B,p=A十4或p=&A[4]
C.p=&A十4或p=A[4] D.p=A十4或p=A[4]
16.要使指针变量p指向2维数组A的0行3列元素,正确的赋值表达式是( )。
A.p=A十3或p=A[0][3]
B.P=A[0]十3或p=A[0][3]
C.p=&A十3或p=&A[0][3]
D.p=A[0]十3或p=&A[0][3]
17.(多选)要想使语句cout<<STR;显示Hello!,STR不可定义为( )。
A.char STR[101="Hello! ";
B.char STR="Hello! ";
C.char STR[]={'H','e','r','1','o','!'};
D.# define STR "Hello! ";
18.要使语句p1=new double[1];能够正常执行,p1应定义为( )。
A.double(*p1)[] B.double *p1[12];
C.double p1; D.double (*p1)[12];
19.要使语句p2=new long *;能够正常执行,p2应定义为( )。
A. long *** p2; B.long **p2;
C. long *p2; D.long p2;
20.要使语句p3=new float *[15];能够正常执行、p3应定义为( )。
A.float(*p3)[15]; B.float(*p3)[];
C.float **p3; D.float *(*p3)[15];
21.要使语句p4=new int [10][5];能够正常执行,p4应定义为( )。
A.i nt (*p4)[][5]; B.i nt (*p4)[10][5];
C.i nt (*p4)[513 D.i nt *p4[5];
三、问答题:
1、 设int a[3][4]; double b[3][4][5];
请写出数组指针pa和pb的定义,使其分别与数组名a 和b 等价。
2、 设int f1 (int n,char * s);char *f2 (int n ,char *s);
请写出函数指针pf1和pf2的定义,使其分别指向上述两个函数。
3、请初始化一维指针数组week。
其初值表初值为"Sun","Mon","Tue","Wed","Thu","Fri","Sat"。
4、要使语句p1=new int(10);p2=new double [10];能正常执行,写出p1和p2的定义。
5、根据下面的要求一步步写出正确的C++语句,注意:各个步骤之间是有先后顺序的。
(1) 定义两个整型变量value1 ,value2;
(2) 定义一个指向整型变量的指针pValue,将该指针初始化为指向value;
(3) 将指针pValue改为指向变量value2;
(4) 通过pValue指针来间接地改变变量value2的值为20;
(5) 申请三个连续的整数空间,并将申请到空间的首地址赋值给pValue;
(6) 用cout输出所申请到的首地址值;
(7) 给第二个地址空间赋值10;
(8) 用cout输出第二个地址空间所在的地址;
(9) 释放所申请到的三个整数空间;
(10) 将指pValue设置为不指向任何地址空间;
6、运算符*和&的作用是什么?
7、什么叫做指针?指针中储存的地址和这个地址中的值有何区别?
8、定义一个整型指针,用new语句为其分配包含10个元素的地址空间。
9、在字符串"Hello,world!"中结束符是什么?
10、定义一个有五个元素的整型数组,在程序中提示用户输入元家值,最后再在屏幕上显示出来。
11、声明下列指针:float类型的指针pFloat,char类型的指针pString和struct customer型的指针prec。
12、给定Hoat类型的指针fp,写出显示fp所指向的值的输出流语句。
13、程序中定义一个double类型变量的指针。分别显示指针占了多少字节和指针所指的变量占了多少字节。
14、const int *p1和int * const p2的区别是什么?
15、下列程序有何问题,请仔细体会使用指针时应避免出现的这个问题
# include <iostream. h>
int main()
{ int *p;
*p=9;
cout << "The value at p: " << * p;
return 0;
}
16、下列程序有何问题,请改正;仔细体会使用指针时应避免出现的这个问题。
int Fnl();
int main()
{
int a = Fnl();
cout << "the value of a is; " << a;
return 0; }
int Fnl()
{
int * p = new int (5);
return * p;
}
四、编程题:
1、编写-个函数,统计一条英文句子中字母的个数,在主程序中实现输入、输出。
2、设学生人数N=8,提示用户输入N个人的考试成绩,然后计算出平均成绩并显示出来。
3、设计一个字符串类Mystring,尽可能地完善它,使之能满足各种需要。


第六部分 复习要点
1、 理解:指针的含义和适用场合,能够在程序设计中合适地应用指针。
2、 理解:多维数组中的指针。
3、 理解:程序命令行参数的意义和定义方式,能够正确地使用命令行参数。
4、 应用:能够编制字符串操作的程序,能够利用标准库函数中的字符串操作函数进行编程。
5、 应用:能够在程序中动态申请空间,并进行合适的指针操作。
6、 综合应用:在程序中综合应用指针。

第七章 引用


第一部分 本章知识点
引用的定义、初始化
用引用向函数传递参数
引用的返回值
第二部分 内容精讲
7.1、 引用的概念
定义一个引用就是为一个变量、函数等对象规定一个别名,此后,任何施加于这个别名上的操作都如同直接施加于别名所代表的对象上一样。
格式:类型修饰符 & 别名〖=别名所代表的对象〗
说明:(1)如果引用不是在参数表中定义的,就必须初始化,即必须在定义时就确定这个别名所代表的变量、函数等对象。
(2)别名就是一变量名,别名所代表的对象就是一个变量名或函数名等。
例如:int i=0;
int & ir =i; //定义引用ir作为i的别名
ir=2; //形式上向ir 赋值,实际上是向i 赋值,等同于i=2;
int *p=&ir; //形式上取ir 的地址,实际上是取i 的地址,等同于int *p=&i,
(3)引用所代表的对象不同,定义引用的格式有所变化。
例如: int a[10], *p=a;
int &ra1=a[6]; //ra1代表数组元素a[6]
int (&ra2)[10]=a; //ra2代表数组a,定义ra2
int * &rp1=p; //rp1代表指针变量p,此句为定义rp1
int &rp2=*p; //rp2代表p所指向的那个对象,即数组元素a[0],定义rp2
(4)引用不是值,不占存储空间。引用只有声明,没有定义。
7.2、 引用的操作
引用一旦初始化,它就维系在一定的目标上,再也不分开。任何对该引用的赋值,都是对引用所维系的目标赋值,而不是将引用维系到另一个目标上。
(1)如果程序寻找引用的地址,它返回目标的地址。
(2)如果程序给引用赋值,即给目标赋值。
(3)引用与指针不同:指针是变量,可以把它再赋值成其它的地址; 建立引用时必须进行初始化且决不会再指向其它不同的变量,所代表对象不能改变。
7.3、 什么能被引用
1、 一般变量可以被引用
若一个变量声明为T &,即引用时,它必须用T类型的变量或对象,或能够转换成T类型的对象进行初始化。
如果引用类型T的初始值不是一个左值,那么将建立一个T类型的目标并用初始值初始化,那个目标的地址变成引用的值。
2、 指针变量可以被引用
3、 对void进行引用是不允许的。
例如:void & a=3; //error
4、 不能建立引用的数组。
5、 引用本身不是一种数据类型,所以没有引用的引用,也没有引用的指针。
6、 引用不能用类型来初始化。
7、 有空指针,无空引用。
7.4、 用引用传递函数参数:
1、 引用传递参数:传递引用给函数与传递指针的效果一样,传递的是原来的变量或对象,而不是在函数作用域内重新建立的变量或对象的副本。
见典型例题1
2、 引用存在的问题:引用隐藏了函数所使用的参数传递的类型,所以无法从所看到的函数调用判断其是值传递还是引用传递。
7.5、 引用可以返回多个值
函数只能返回一个值,而引用和指针都可以返回多个值。
见典型例题2
7.6、 用引用返回值
函数返回值时,要生成一个值的副本。而用引用返回值时,不生成值的副本。
例如:下面的程序是有关引用的4种形式:
# include <iostream.h>
float temp;
float fn1 ( float r )
{
temp=r*r*3.14;
return temp;
}
float & fn2 ( float r)
{
temp=r*r*3.14;
return temp;
}
void main()
{
float a = fn1(5.0);
float &b = fn1(5.0);
float c = fn2(5.0);
float &d = fn2(5.0);
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
cout<<d<<endl;
}
运行结果:
78.5
78.5
78.5
78.5
7.7、 函数调用作为左值
返回一个引用使得一个函数调用表达式成为左值表达式。
注意:要避免局部栈中变量的地址返回。
见典型例题3
7.8、 用const 限定引用:保护实参不被修改的办法是传递const指针和引用
传递指针和引用更大的目的是效率。当一个数据类型很大时,因为传值要复制副本,所以不可取。
传递指针和引用存在传值所没有的危险。
说明:C++不区分变量的const引用和const变量的引用。程序决不能给引用本身重新赋值,使它指向另一个变量,因此引用总是const的。如果对引用应用关键词const,其作用就是使目标成为const变量。即没有:const double const & a=1;只有:const double & a=1;
7.9、 返回堆中变量的引用
对引用的初始化,可以是变量,可以是常量,也可以是一定类型的堆空间变量。但是,由于引用不是指针,所以,不能直接从堆中获得的变量空间来初始化引用。
如: int & a=new int(2); // error : a 不是指针
注意:用堆空间来初始化引用,要求该引用在适当时候释放堆空间。
见典型例题4

7.10 典型例题
例1:通过swap()函数调用交换两个整数值。
# include <iostream.h>
void swap ( int & x, int & y );
void main ()
{
int x=5;
int y=6;
cout << "before swap, x" << x <<"y: " <<y << endl;
swap ( x,y );
cout <<" after swap, x: " << x <<"y: " <<y << endl;
}
void swap ( int & rx, int & ry )
{
int temp;
temp=rx;
rx=ry;
ry=temp;
}
运行结果:
before swap,x:5 , y:6
after swap, x:6 , y:5
说明:在主函数中,调用swap()函数的参数是x和y ,简单地传递变量而不是它们的地址;而事实上,传递的是它们的地址。引用传递的内存布局与指针相仿,只是操作完全不同。

例2:利用引用返回多个值
# include < iostream.h >
int Factor ( int , int &, int & );
void main ()
{
int number , squared , cubed , error;
cout << "Enter a number (0-20 ): "
cin >> number ;
error = Factor (number , squared , cubed );
if ( error )
cout << "Error encountered!/n";
else
{
cout << "Number: "<< number << endl;
cout <<"Squared: "<< squared << endl;
cout <<" Cubed: "<<cubed<<endl;
}
}
int Factor ( int n, int & rSquared, int & rCubed)
{
if ( n>20 || n<0)
return 1 ;
rSquared=n*n;
rCubed=n*n*n;
return 0;
}
运行结果:
Enter a number(0-20):3
Number:3
Squared:9
Cubed:27
例3:统计学生中A类学生与B类学生各占多少。A类学生的标准是平均分在80分以上,其余都是B类学生。
# include <iostream.h>
int array[6][4]={{60,80,90,75},
{75,85,65,77},
{80,88,90,98},
{89,100,78,81},
{62,68,69,75},
{85,85,77,91}};
int & level ( int grade[],int size , int &tA, int &tB );
void main()
{
int typeA=0,typeB=0;
int student =6;
int gradesize=4;
for ( int i=0; i<student; i++)
level (array[i],gradesize,typeA,typeB)++;
cout<<"number of typeA is"<<typeA<<endl;
cout<<"number of typeB is"<<typeB<<endl;
}
int & level ( int grade[], int size, int & tA, int &tB)
{
int sum=0;
for ( int i=0; i<size; i++)
sum+=grade[i];
sum/=size;
if(sum>=80)
return tA;
else
return tB;
}
由于返回的是引用,所以可以作为左值直接进行增量操作。该函数调用代表typeA 还是typeB的左值视具体的学生成绩统计结果而定。
例4:下面的程序在堆中分配空间,求值,然后释放堆空间:
# include <iostream.h>
int CircleArea()
{
double * pd=new double;
if(!pd)
{
cout << "error memory allocation! ";
return 1;
}
double & rd=*pd;
cout<<"the radius is: ";
cin>>rd;
cout<<"the area of circle is"<<rd*rd*3.14<<endl;
delete pd;
return 0;
}
void main()
{
if (CircleArea())
cout<<"program failed./n";
else
cout<<"program successed./n";
}
运行结果:
the radius is:12
the area of circle is 452.16
program successed.
例5、字符串排序
根据下面的概念给出解决串排序问题的方法:
while read a string
insert the string in sorted position
print the strings
[解答]
设计几个函数,分别为读入一个字符串ReadString(),插入一个字符串Insert(),搜索一个字符串Search(),输出一组字符串output()。将这些函数放在单独的文件中,其原型在strarray.h中声明。于是该程序的工程文件由下面几项组成:
strsort.cpp //其中包含了头文件strarray.h
strarray.cpp //也包含了头文件strarray.h
源文件分别如下:
strarray.h
//――――――――――
typedef char * String; //定义string为char * ,以后再用到char * 即用string
代替。
int ReadString(String & s); //返回1表示成功读入
void Insert (String strList [],String & s) ;
void output (String strList [] ) ;
int Search(String strList [] , String& str);
//.------------------
//
// strsort .cpp //
//
//--------------------
# include < iostream.h >
# include "strarray.h
# include < string.h>
# include < stdlib.h >
//--------------------
const int Max =100;
String stringList [max] ;
int size=0;
//------------------
void main ()
{
String s =NULL;
while (ReadString(s ));
Insert (stringList, s) ;
Output ( stringList) ;
}
//----------------------
//
// strarray. cpp //
//
//--------------------
int ReadString (String & s) //读入若干串,输入过程以^Z结束
{
const int bufsize =100;
static cbar buffer [bufsize] ;
cin. getline(buffer, 100) ;
if(cin.eof() )
return 0;
if(!s)
delete s;
s = new char [strlen(buffer) + l] ;
if(!s){
cerr << "内存分配出错! / n ";
return 0 ;
}
strcpy ( s, buffer);
return l;
}
//---------------------
void Output (String a[] )
cout <<" / nSorted string list: / n" ;
for(int i=0; i< size; i++;
cout << a[i] << endl;
}
//---------------------
void Insert (String strList [] , String & str)
{
if(size == Max) {
cerr << "数组溢出 ! / n ";
exit (l) ;
}
int i, j;
i= Search(strList, str) ;
for(j=size-l; j>= i; j -- )
strList[j + l]=strList[j] ;
strList[i]=str;
size ++ ;
}
//---------------------
int Search(String strList [] , String & str)
{
for(int i=1; i<size; i++)
if(strcmp((strList [i] , str) > 0)
return i; //found
return size; //not found
}
//---------------------
运行结果:
I am a student
l am a teacher
Hello world
Good afternoon
^Z
Sorted string list:
Good afternoon
Good evening
Good moming
Hello World
I am a student
I am a teacher

第四部分 课堂作业:
一、填空题:
1.要使引用kj代表变量char j;,kj应定义为( )。
2.要使引用kp代表变量char (*p)[4];,kp应定义为( )。
3.已知变量a,b定义为:long a=66,&b=a;,则cout<<a-l<<','<<b十1;的输出是( )。
4.已知变量x,y定义为:int x[]={99,98,97},&y=x[1];,则cout<<y+1<<','<<y+x[2];的输出是( )。
二、选择题:
1.已知函数f1的原形是:void fl (int &a,char *b);,变量v1,v2的定义是:int v1;char v2[]="ABCDW";,把vl和v2分别作为第一参数和第二参数来调用函数f1,正确的调用语句是( )。
A.f1(&v1,&v2); B.f1(&vl,v2);
C.fl (vl,v2); D.fl (vl,&v2);
2.已知函数f2的原形是:void f2 (int * a,double(&b)[5]);,变量vl,v2的定义是:int v1;double v2[5];,把v1和v2分别作为第一参数和第二参数来调用函数f2,正确的调用语句是( )。
A. f2(vl,&v2); B. f2(&v1,v2);
C. f2(v1,v2); D. f2(&v1,&v2);
3.已知函数fl的原形是:
void f1(int *a,long &b);
变量v1,v2的定义是:
int v1;long v2;
把v1和v2分别作为第一参数和第二参数来调用函数f1,正确的调用语句是( )。
A.f1 (vl,v2); B.fl ( &v1,v2);
C.fl(v1,&v2); D.f1(&v1,&V2);
4.已知函数f2的原形是:
void f2 (int &a,double (&b)[5]);
变量v1,v2的定义是: int v1;double v2[5];
把vl和v2分别作为第一参数和第二参数来调用函数f2,正确的调用语句是( )。
A.F2(vl,v2); B.F2(&v1,v2);
C.F2(v1,&v2); D.F2(&vl,&v2);
5.设vod f1 ( int &x, char *p); int m;char s[]="c++";以下调用合法的是( )。
A f1 (&m,&s); B f1 (&m,s); C f1 (m,s); D f1 (m,&s);
三、回答题:
1、引用和指针有何区别?何时只能使用指针而不能使用引用?
2、定义一个整型变量a,一个整型指针p,一个引用r,通过p把a的值改为10,通过r把a的值改为5。
3、读下列程序
(1) 将其改写为传递引用参数;
(2) 说出其功能;
(3) 将findmax()函数改写成非递归函数(重新考虑参数个数)。
# include <iostream.h>
const size=10;
void findmax ( int *a, int n, int *pk );
void main()
{
int a[size];
int n=0;
cout<<"please input "<<size <<"datas:/n";
for ( int i=0; i<size; i++)
{
cin>>a[i];
}
findmax ( a,size , 0,&n);
cout <<"the maximum is"<<a[n]<<endl<<"It`s index is"<<n<<endl;
}
void findmax ( int *, int n, int i, int *pk)
{
if (i<n)
{
if (a[i]>a [*pk]);
*pk=i;
findmax ( a , n , i+1 ,& (*pk ));
}
}
四、编程题:
1、读下列程序,该程序生成有十个整数的安全数组。要把值放入数组中,使用put()函数,然后取出该值,使用get()函数,put()和get()中若遇下标越界立即终止程序运行。其运行结果为后面所示,请完成两个未写出的函数定义。
# include <iostream.h>
int & put (int n);
int get ( int n );
int vals[10];
int error=-1;
void main ()
{
put (0)=10;
put (1)=20;
put (9)=30;
cout<<get(0)<<endl;
cout<<get(1)<<endl;
cout<<get(9)<<endl;
put (12 )=1;
}
//…
运行结果:
10
20
30
range error in put value!
2、 编制程序,调用传递引用的参数,实现两个字符串变量的交换。例如开始:
char *ap="hello";
char *bp="how are you? ";
交换的结果使得ap和bp指向的内容分别为:
ap:"how are you? "
bp:"hello"

第六部分 复习要点
1、 理解:数组、指针和引用各自的含义和适用场合,能够在程序设计中根据需要选择合适的数据类型。
2、 应用:能够编制将引用作为函数参数和返回值的应用程序。
3、 综合应用:在程序中综合应用数组、指针和引用。

第八章 结构


第一部分 本章知识点
结构类型的定义、结构类型变量的定义、初始化与使用
结构指针、结构数组
结构作为函数参数与返回值
用结构实现链表
第二部分 内容精讲
8.1、 结构:
1、 结构的概念:结构是用户自定义的新的数据类型,在一个组合项中包含若干个类型不同的数据项的数据结构。结构变量中的成员可以不同类型。
2、 结构类型的定义:一个结构类型的定义以保留字struct开始,后跟一个作为结构类型名的标识符,然后从左花括号后进行成员定义,以右花括号结束成员定义,左右花括号之间称为结构体,最后以分号结束整个结构的定义。
格式:struct 结构类型名
{ 成员定义1
成员定义2
……
成员定义n ;
};
例如:定义一个职工Employee结构数据类型,它包括姓名,职工编号,工资,地址,电话。
struct Employee
{
char name[20];
long code;
float salary;
char address[50];
char phone[11];
}; //分号是必需的
说明:(1)此例定义了一个结构类型"Employee类型",此类型中包含5个成员,分别为字符数组name、长整型code、单精型salary、字符数组address、字符数组phone。
(2)此类型一旦被定义后,可与int、float等基本数据类型同等看待。即可用Employee类型定义其它变量(定义出的变量为Employee结构类型的变量)。
(3)定义一个结构并不分配内存,内存分配发生在用定义的类型来定义变量中。
3、 结构变量的定义:一种结构类型定义后,就可以利用它在其作用域内定义变量并进行必要的初始化。
(1) 先定义结构类型,再定义变量
struct 结构类型名
{ 成员定义1
成员定义2
……
成员定义n ;
};
结构类型名 变量名表列;
例如:上例中定义了结构类型Employee,现在可以用其定义结构变量:
Employee person; //定义一个Employee结构的变量person,并为其分配变量空间。
注:可一次性定义若干个结构变量,变量名之间用逗号隔开。
如:Employee person1,person2; //定义两个Employee结构的变量person1和person2
(2)、在定义结构类型的同时定义变量
struct 结构类型名
{
成员定义1
成员定义2
……
成员定义n ;
}变量名表列;
例如:struct AAA
{
char s[20];
int top;
}a1; //定义一个具有AAA结构类型的变量a1
(3)、定义无名结构类型的同时定义变量
struct
{ 成员定义1
成员定义2
……
成员定义n ;
}变量名表列;
说明:此形式由于没有结构类型名,所以只能在定义类型的同时定义变量,以后无法利用它再定义其它的变量或函数。
关于结构类型和结构变量的说明:
(1) 区分结构类型与结构变量两个概念:类型只一个框架,只是对一个结构的声明,在编译时不分配存储空间,无法对其进行直接操作,变量是在定义了结构类型之后,对其所作的类型说明,可以对结构变量进行赋值、算术运算等操作,但不能对结构类型进行操作。
(2) 结构成员也可以是结构变量
例如:struct date
{
int month;
int day;
int year;
};
struct student
{
int num;
char name[20];
char sex;
int age;
date birthday; //定义birthday是一个具有data类型的结构变量
char addr[30];
}student1,student2;
4、结构变量的引用:
定义结构变量之后就可以利用它存取具体结构数据,系统对结构变量所提供的运算有赋值(=)、直接指定成员(.)和间接指定成员(->)三种,这三种运算符(即=,.和->)分别称为赋值运算符、直接成员运算符(又称点运算符)和间接成员运算符(又称箭头运算符)。它们都是双目运算符,并且成员运算符同下标运算符和函数运算符一样具有最高的优先级,而赋值运算符的优先级较低。
赋值运算符的两边为同类型的结构变量,即为同一结构类型标识符所定义的变量,运算功能是把右边变量的值拷贝到左边变量中,即拷贝到左边变量所对应的存储空间中,运算的结果为左边的变量。赋值号可以连续使用,并且规定结合性为从右到左,所以若z1,z2和z3为同类型的结构变量,则赋值语句z3=z2=zl的执行过程是首先把zl赋给z2,再接着把z2赋给z3,使得z3和z2都具有z1的值。 注意:不同类型的结构变量不能进行此运算。
直接成员运算符的左边是一个结构变量(包括结构数组中的元素),右边是该结构变量中的一个成员,运算结果是一个结构(变量)中的成员变量。如x.a表示x中的成员变量a;x.b.t表示x中b成员内的成员变量t,其中b又是x中的结构成员;vec[5].name表示结构数组vec中第5号元素内的成员变量name。一旦定义了结构变量,就可以使用点操作符"."来访问结构中的成员。
间接成员运算符的左边是一个结构指针变量,右边是该结构指针变量所指结构中的一个成员,运算结果是一个指针所指结构中的一个成员变量。如p->a表示p指针所指向结构中的成员变量a,它可以等价表示为(*p).a,其中括号内为p指针所指的结构变量,此处用括号是必须的。若写成*p.a是错误的,因为成员运算符的优先级高于取内容运算符的优先级,这样先做的是点运算,而不是星号运算;p->c->n表示p指针所指结构中的指针成员c,再接着得到由c所指结构中的成员变量n,它可以等价表示为(*p).c->n,(*(*p).c).n或(*p->c).n;1ist[n]->wage表示结构指针数组1ist中第n号元素所指结构中的成员变量wage。
C十十中的其他运算符,如算术运算符和关系运算符等,只有通过以后学习的运算符重载函数定义后才能够应用到结构变量上,否则是不能施加于结构类型的变量的。
通过成员运算符(直接或间接)能够得到结构中的成员变量,每个成员变量与相同类型的简单变量或数组元素一样,能够作为左值或右值参与该类型所具有的各种运算。
引用时应注意的问题:
(1) 不同类型的结构变量不能进行赋值运算。
(2) 当用点操作符时,它的左边应是一个结构变量,当用箭头操作符时,它的左边应是一个结构指针。
(3) 箭头操作符与点操作符是可以互换的。
(4) 指针是有类型的:定义结构指针时,必须指明是指向哪种结构变量的指针,一旦定义,此指针只能指向规定结构类型的变量,不能指向其他类型的变量。
(5) 结构是一个数据类型,所以可以拥有结构数组。要定义结构数组,必须先声明一个结构,然后定义这个结构类型的数组。此数组中的所有元素均具有相同的结构。
(6) 结构可以按值传递,这种情况下整个结构值都将被复制到形参中去。
例如:下面的程序说明怎样将结构值作为参数传给函数:
# include <iostream.h>
struct Person
{
char name[20];
unsigned long id;
float salary;
};
void print (Person pr)
{
cout<<pr.name<<" "<<pr.id<<" "<<pr.salary<<endl;
}
Person allone[4]={{ "jone",12345,339.0},{"david",13916,449.0},
{"marit",27519,311.0},{"yoke",12335,511.0}};
void main()
{
for(int i =0; i <4; i ++)
{
print(allone[i]);
}
}
运行结果:
jone 12345 339
david 13916 449
marit 27519 311
yoke 12335 511

(7) 结构也可以引用传递,这种情况下仅仅把结构地址传递给形参。
例如,下面的程序修改上例,以引用传递结构:
# include <iostream.h>
struct Person
{
char name[20];
unsigned long id;
float salary;
};
void print (Person &pr)
{
cout<<pr.name<<" "<<pr.id<<" "<<pr.salary<<endl;
}
Person allone[4]={{ "jone",12345,339.0},{"david",13916,449.0},
{"marit",27519,311.0},{"yoke",12335,511.0}};
void main()
{
for(int i =0; i <4; i ++)
{
print(allone[i]);
}
}
运行结果:
jone 12345 339
david 13916 449
marit 27519 311
yoke 12335 511
(8)、一个函数可以返回一个结构值,即若干数据类型值的一个聚集。
一个函数可以返回一个结构的引用和结构的指针。但,不要返回一个局部结构变量的引用或指针。
8.2、链表结构
在结构类型中有一种特殊类型,它除了包含有一般的数据域外,还包含有一个指向自身结构的指针域。这种类型的对象又称为结点,每个结点的指针域用来指向下一个结点,由此形成一个链表。
结构成员不能是自身的结构变量,但可以用结构指针作为成员。
(1)链表结构
通过含有一个自身结构的指针,我们可以实现随机分布的结构变量的遍历。
对于下面的结构:
struct List
{
char name[20];
List *pN;
};
name成员含有结构中的实际信息,pN成员是指向另一个List的指针。这种结点通过每个List的pN成员链接起来,能用于构造任意长的结构链,这样的结构链称为链表。链表中的每个List结构变量称为结点。链表中的第一个List结点经常由一个指向List的指针引导。这个结构指针不是结构成员,但它指向一个结点,而该结点的pN成员又指向另一个结点,而另一个结点的pN成员又指向一个结点,这样一直指下去,直到最后一个结点。最后一个结点的pN成员值为空(NULL),见图


链表的组成是一个个有序的结点,每个结点是同类型的结构变量。可以通过程序的办法,来建立和显示链表,可以插入、删除及增加结点来维护一个链表。
一个链表总是包含一个链首指针。操作链表时,一般都先由链首指针引导。
(2)、创建与遍历链表
例如,下面的例子建立一个链表,并输出链表:
# include < iostream.h >
struct Student
{
long number ;
float score;
Student * next;
};
Student * head; //链首指针
Student * Create ( )
{
Student * pS; //创建的结点指针
Student * pEnd; //链尾指针,用于在其后面插入结点
pS = new Student; //新建一个结点,准备插入链表
cin >> pS -> number >> pS-> score; //给结点赋值
head = NULL; //开始链表为空
pEnd=pS;

while(pS -> number! = 0)
{
if (head = NULL)
head = pS;
else
pEnd -> next =pS;
pEnd = pS;
pS= new Student; .
cin >> pS -> number >> pS -> score;
}
pEnd -> next =NULL;
delete pS;
retum ( head) ;
}

void ShowList ( Student * this) //遍历整个链表,输出每个结点的值
{ .
cout <<"now the items of list are / n" ;
while (this)
{
cout<<this->number<<","<< this ->score<<endl;
this = this -> next;
}
}
void main ()
{
ShowList (Create () ) ;
}
54 3.4
23 3.2
24 3.5
15 4.l
66 4.0
0 0.0
now the items of list are
54, 3.4
23, 3.2
24, 3.5
15, 4.l
66, 4.0
(3)、删除链表结点
链表结点的删除操作要保证不破坏链表的链接关系。一个结点删除后,它的前一结点的指针成员应指向它的后一结点,这样就不会因删除而使链接中断。
删除往往还包含查找子过程,因为首先要找到想要删除的结点。它包含找不到的处理。
例如,下面的代码是删除结点的函数,它删除学号为number的结点:
void Delete ( Student *head, long number)
{
Student *p;
if ( !head)
{ cout <<"/ nList null! / n";
return;
}
if (head -> number = = number)
{
p = head;
head = head -> next;
delete p;
cout << number << "the head of list have been deleted / n";
return;
}
for ( Student *pGuard = head; pGuard -> next; pGuard = pGuard -> next)
//pGuard始终指向欲被判断结点的前一结点
{ .
if (pGuard -> next -> number == number)
{
p = pGuard -> next;
pGuard -> next = p - > next;
delete p;
cout << number << "not found/ n";
return;
}
}
cout << number <<"have been deleted / n";
}
对于创建与遍历链表程序,如果主函数改成:
void main()
{ .
head = Create( ) ;
Delete (head, 54 ) ;
}
将得到下面的输出:
54 3.4
23 3.2
24 3.5
15 4.1
66 4.0
0 0.0
54 the head of list have been deleted
相应的操作见图

删除链首结点的步骤为:
(1)P指向链首结点;
(2)head指向链首结点的下一个结点;
(3)删除p指向的结点。
这里顺序是重要的。如果p不指向链首结点,则当head指向链首结点的下一个结点后,原链首结点脱链,导致结点地址丢失,以致无法释放堆空间。如果先删除链首结点,则head指针无法指向由链首结点导出的下一个结点地址。
Delete()函数中的循环语句处理非链首结点的删除。如果程序中的主函数改成:
void main()
(
head=Create();
Delete(head,15);
}
将得到下面的输出:
54 3.4
23 3.2
24 3.5
15 4.1
66 4.0
0 0.0
15 have been delete
相应的操作见图

如果主函数中对Delete()的调用改成Delete(head,11),将会得到下面的输出:
54 3.4
23 3.2
25 3.5
15 4.1
66 4.0
0 0.0
11 not found!
当Delete()函数找到要删除的结点时,删除的步骤为:
(A)p指向待删的结点;
(B)pGuard所指向结点的next成员指向待删结点的下一个结点;
(C)删除p指向的结点。
在找到待删结点时,pGuard指向待删结点的前一个结点是重要的。否则,待删结点的前一结点地址丢失,其next成员无法与待删结点的后一结点链接。在数据结构课程中,通常称pGuard指针为"哨兵"。
(4)、插入链表结点
插入操作不能破坏链接关系。应将插入结点的next成员指向它的后一个结点,然后将前一结点的next成员指向插入的结点,这样就得到了新的链表。
插入操作也包含一个插入位置查找的子过程,同样也面临链表是空表或插入到链首的特殊情况。
假设创建(调用Create())链表时,结点是按number值由小到大顺序排列,则插入结点函数Insert()设计如下:
void Insert(Student *head, Student *stud)
{
if(head==NULL) //空表时,将结点置在head指针下即可
{
head=stud; //表示链首
stud->next=NULL; //表示链尾
return;
}
if(head->number>stud->number) //结点插入的位置在链首
{
stud->next=head; //指向链首结点
head=stud; //插入结点成为链首
return;
}
for ( Student * pGuard =head; pGuard ; pGuard = pGuard -> next )
{
if (pGuard -> next -> number> stud -> number) //找到插入位置
{ stud->next=pGuard->next;
pGuard -> next = stud;
return;
}
}
}
前面程序中,如果主函数改成:
void main ( )
{
Student ps ;
ps . number = 36 ;
ps.score= 3.8;
head = Create ( ) ;
Insert (head, &ps) ;
ShowList (head) ;
}
将得到下面的输出:
15 4.l
23 3.2
24 3. 5
54 3.4
66 4.0
0 0.0
now the items of list are
15, 4.l
23, 3.2
24, 3.5
36, 3.8
54, 3.4
66, 4.0
相应的操作见图:
函数Insert()中,如果链表为空,则只要将结点挂接在head下,然后置插入的结点为链尾即可。
head=stud;
stud一>next=NULL;
如果插入的结点值比链首结点值小,即插入位置在链首,则只要将插入结点的next成员指向链首,然后head指针指向插入结点即可。
stud一>next=head;
head=stud;
当要插入在链表中的非链首时,首先要找到插入位置。当发现当前结点(pGuard所指向的结点)的下一个结点值比插入结点值大时,即找到了插入位置在当前结点之后。具体的插入操作为:
(1)将插入结点指向当前结点的下一个结点:stud一>next=pGuard一>next;
(2)将当前结点指向插入结点:pGuard一>next=stud;
这两步操作的顺序是重要的,如果先将当前结点的next成员修改,则将失去与后继结点的联系,插入结点无法找到当前结点的下一个结点。
6、结构与函数
结构是一种类型,它能够使用在允许简单类型使用的所有地方,当然也允许作为函数的参数类型和返回值类型,下面通过例子说明使用情况。见典型例题5

8.3 典型例题
例1、 已知人员记录的结构如下:
struct Person { //人员记录结构
char name[10]; //姓名
bool isMale; //性别
int age; //年龄
float pay; //工资
};
设计一程序,输入若干人员记录并保存在一个结构数组中。
程序如下:
#include <iostream.h>
#include <string.h>
#define MaxPersons 10 //允许输入的人员记录的最大数量
struct Person { //定义人员记录的结构
char name[10]; //姓名
bool isMale; //性别:true 表示男,false表示女
int age; //年龄
float pay; //工资
};
Person a[MaxPersons]; //定义全局结构数组a ,用来存放输入的人员记录
void input ( int n ) //向全局结构数组a中输入n个记录

cout<<"从键盘上输入具有Person结构的"<<n<<"个记录:"<<endl;
int i,k;
Person x; //定义局部结构变量x
for(i=0; i<n; i++)
{cin>>x.name; //输入一个人的名字
cin>>k;
x.isMale=(k!=0); //赋值时0自动转换为false,非0自动转换为true
cin>>x.age>>x.pay; //输入年龄和工资
a[i]=x; //将x 赋给a[i]元素,此为结构赋值


void output ( int n ) //显示出全局结构数组a 中的n个记录

cout<<"显示具有Person结构的"<<n<<"个记录: "<<endl;
for(int i=0; i<n; i++)
{cout<<a[i].name<<' '; //显示姓名
cout<<(a[i].isMale ? "男":"女")<<' '; //显示性别
cout<<a[i].age<<' '<<a[i].pay<<endl; //显示年龄和工资


void main()
{
int n;
cout << "请输入一个正整数(1<=n<=10): ";
cin>>n;
input(n);
output(n);
}
假定程序运行后从键盘上输入数值3到变量n中,则程序输入和运行结果如下:
请输入一个正整数(1<=n<=10):3
从键盘上输入具有Person结构的3个记录:
Xujian 1 45 460
HeSiJin 1 50 440
WangLi 0 44 450.5
显示具有Person结构的3个记录:
Xujian 男45 460
HeSiJin 男 50 440
WangLi 女 44 450.5


例2、从一个保存人员记录的结构数组中查找工资最高的记录。
# include <iostream.h>
# include <string.h>
struct Person {……}; //定义人员记录的结构类型
Person a[5]={{ "赵宁",1,42,386},{"李环芬",0,45,482},{"张建国",1,40,420},{"金进全",1,36,530},{"程洁",0,46,475}}; //定义全局结构数组a并初始化
void output ( int n ) {……}   //显示结构数组a中的n个记录,同例1
void find ( int n )   //从全局结构数组a的前n个记录中查找并显示出具有最大工资的记录

int k=0; //用k指示当前具有最大工资值元素的下标,初值为0
//采用顺序比较的方法进行查找,循环结束后下标为k的元素具有最大工资值
for ( int i=1; i<n; i++)
if(a[i].pay>a[k].pay) k=i;
cout<<endl<<"显示数组a中具有最大工资值的记录:"<<endl;
cout<<a[k].name<<' '<<(a[k].isMale ? "男":"女")<<' ';
cout<<a[k].age<<' '<<a[k].pay<<endl;
}
void main()
{
output(5);
find(5);
}
此程序首先输出数组a中的5个记录,然后从数组a的前5个记录中查找并显示出具有最大工资值的记录。程序运行如下:
显示具有Person结构的5个记录:
赵宁 男 42 386
李环芬 女 45 482
张建国 男 40 420
金进全 男 36 530
程洁 女 46 475
显示数组a 中具有最大工资值的记录:
金进全 男 36 530
例3、 对一个Person结构数组进行"冒泡法"排序,工资高的排在后面。
程序如下:
#include <iostream.h>
struct Person
{
char name[20];
unsigned long id;
float salary;
};
Person allone[6]={{ "jone",12345,339.0},
{"david",13916,449.0},
{"marit",27519,311.0},
{"jasen",42876,623.0},
{"peter",23987,400.0},
{"yoke",12335,511.0}};
vodi main()
{
Person temp;
for ( int i=1; i<6; i++)
{
for(int j=0;j<=5-i;j++)
{
if(allone[j].salary>allone[j+1].salary)
{
temp=allone[j];
allone[j]=allone[j+1];
allone[j+1]=temp;
}
}
}
for(int k=0;k<6;k++)
{
cout<<allone[k].name<<" "<<allone[k].id<<" "<<allone[k].salary<<endl;
}
}
运行结果为:
marit 27519 311
jone 12345 339
peter 23987 400
david 13916 449
yoke 12335 511
jasen 42876 623
例4、下面的程序建立一个结构指针数组,实现上例的结构排序:
#include <iostream.h>
struct Person
{
char name[20];
unsigned long id;
float salary;
};
Person allone[6]={{ "jone",12345,339.0},
{"david",13916,449.0},
{"marit",27519,311.0},
{"jasen",42876,623.0},
{"peter",23987,400.0},
{"yoke",12335,511.0}};
vodi main()
{
Person *pA[6]={&allone[0],&allone[1], &allone[2], &allone[3], &allone[4], &allone[5];
Person *temp;
for ( int i =1; i <6; i ++)
{
for(int j=0;j<=5- i;j++)
{
if(pA[j]->salary> pA [j+1]->salary)
{
temp= pA [j];
pA [j]= pA [j+1];
pA [j+1]=temp;
}
}
}
for(int k=0;k<6;k++)
{
cout<< pA [k] ->name<<" "<< pA [k] ->id<<" "<< pA [k] ->salary<<endl;
}
}
运行结果为:
marit 27519 311
jone 12345 339
peter 23987 400
david 13916 449
yoke 12335 511
jasen 42876 623
例5、从保存有学生记录的结构数组中查找一个给定学号的记录,若能够找到,返回记录位置(即元素下标)以示查找成功。否则返回-l以示查找失败。
分析:按照题目要求,可以编写一个函数来实现,假定函数名用标识符search表示,函数返回值类型应为整型,函数参数应包括三个:其一为结构数组,假定用s表示,其二为数组长度(即数组中所含元素的个数),假定用n表示,其三为保存给定学号的结构变量,假定用x表示。查找过程为:从数组s中第一个元素s[0]起,依次使每一个元素的num域的值同x的num域的值(即给定的学号)进行比较,若相等则表明查找成功,返回该元素的下标,否则继续向后比较,直到比较完最后一个元素,若仍不成功则返回-1。search函数的实现见下面的程序:
# include <string.h>
# include <iostream.h>
struct Student {
char num[8]; //学号
char name[10]; //姓名
short grade; //成绩
}; //定义学生记录结构
int search (Student s[],int n,Student x) //第一个参数也可用Student *s代替

for(int i =0; i <n; i ++)
if(strcmp(s[i].num,x.num)==0)
return i;
return -1;
}
void main()
{
Student a[5]={{ "ch231","王广敏",69},{"ec115","刘文",82},
{"dt327","古明",72},{"cs102","张平",78},
{"bx214","张文远",65}};
Student x= {"ch231" };
int k=search(a,5,x);
if (k>=0)
cout<<a[k].num<< ' ' <<a[k].name<<a[k].grade<<endl;
Else
cout<<"学号为"<<x.num<<"的记录不存在"<<endl;
}
程序运行结果如下:
ch231 王广敏 69

例6、对保存有学生记录的结构数组中的某一给定学号的记录进行更新,若更新成功则返回1,否则说明要更新的记录不存在,应将新记录插入到数组中记录序列的末尾,并修改记录序列长度为已有长度加1,同时返回0表示完成插入。
分析:此题同样可以用-个函数来实现,假定函数名用update表示,函数类型为整型,函数参数有三个,分别为结构指针、记录序列长度和提供更新值的结构变量,假定依次用s,n和x表示;其中n应定义为引用,因为要用它带回修改后的记录序列长度,即反映到实参变量中;最后一个参数x可以定义为值参,也可以定义为引用。对于引用参数,若只需要在函数中取用其值,而不需要改变它的值,则最好在参数说明前加上保留字const,这样当进行非法修改时,能够被编译器发现,避免人为的错误。此题的更新过程为:从数组s的第-个元素起顺序查找,若查找到s[i].num的值与x.num的值相等,就用x的值更新(即修改)s[i]元素的值并返回l,否则表明没有查找到待更新的元素,应将x的值插入到数组s中下标为n的位置上,并将n的值增l (即数组长度增1)后返回0。update函数的实现见下面的程序:
# include <string.h>
# include <iomanip.h>
# include <iostream.h>
struct Student {
char num[8]; //学号
char name[10]; //姓名
short grade; //成绩
}; //定义学生记录结构
void output(Student *s,int n) //显示出结构数组s中的n个记录

cout.setf(ios::left);
for(int i =0; i <n; i ++)
{cout<< i <<' '<<setw(8)<<s[i].num<<setw(12)<<s[i].name;
cout<<setw(5)<<s[i].grade<<endl;
}
cout<<endl;
}
//更新数组s中学号为x.num的记录,若不存在记录x添加到s数组中
int update(Student s[],int &n, const Student &x)
{
for(int i =0; i <n; i ++)
if(strcmp(s[i].num, x.num)==0)
{ s[i]=x; //用x更新s[i]的值
return 1;
}
s[n++]=x; //将x值插入到s数组中最后一个记录的后面
return 0;
}
void main()
{
//为了给插入记录留有空间,应将定义的数组长度大于初始化元素常量的个数
Student a[8]={{ "ch231","王广敏",69},{"ec115","刘文",82},
{"dt327","古明",72},{"cs102","张平",78},
{"bx214","张文远",65}};
//定义并初始化两个结构变量x和y,作为查找对象
Student x= {"dt327","古明",86},y={"sr203","田飞",74};
int n=5; //定义n为数组a中当前保存的记录个数,即被称为a的当前长度
if (update(a,n,x)==1)
cout<<"完成更新操作"<<endl;
Else
cout<<"完成插入操作"<<endl;
if (update(a,n,y)==1)
cout <<"完成更新操作"<<endl;
else
cout<<"完成插入操作"<<endl;
output(a,n); //输出数组a中当前保存的全部记录
}
程序运行结果如下:
完成更新操作
完成插入操作
0 ch231 王广敏 69
1 ec115 刘文 82
2 dt327 古明 86
3 cs102 张平 78
4 bx214 张文远 65
5 sr203 田飞 74
第四部分 课堂练习
1、利用结构类型编制程序,实现输入一个学生的数学期中和期末成绩,然后计算并输出其平均成绩。
2、已知head指向一个带头结点的单向链表,链表中每个结点包含字符型数据和指向本结构结点的指针。编写函数实现在值为"jone"的结点前插入值为"marit"的结点,若没有值为"jone"的结点,则插在链表最后。
3、已知head指向一个带头结点的单向链表,链表中每个结点包含数据long和指向本结构结点的指针。编写函数实现如图所示的逆置。
原链表为:

4、读下面的链表操作程序。
(1) 将函数ShowList()和AddToEnd()改成非递归形式(可以修改函数原型)。
# include < iostream.h >
struct Lnode
{
double data;
Lnode *next;
};
void ShowList (Lnode * list)
{ if(list)
{
cout << list -> data << endl;
if (list -> next)
ShowList (list -> next) ; //递归调用
}
}
void AddToEnd(Lnode * new, Lnode * head)
{
if (head = = NULL)
head = new;
else
AddToEnd(new, head -> next) //递归调用
}
Lnode * GetNode ( )
{
Lnode * item;
Itean = new Lnode;
if (item)
{
item -> next = NULL;
item-> data = 0.0;
}
else
cout << "Nothing allocated / n ";
return item;
}
void main ( )
{
Lnode * head;
Lnode * temp;
temp = GetNode() ;
while (temp)
{
cout << "data? ";
cin >> temp -> da.ta;
if(temp -> data > 0)
AddToEnd (temp, head) ;
else
break ;
temp = GetNode() ;
}
ShowList (head) ;
}
(2)、写出下面运行输入之后的运行结果。
Data? 3
Data? 5
Data? 7
Data? 6
Data? 4
Data? 8
Data? -3
(3)、在主函数结束之前,增加一个删除整个链表的函数调用DeleteList(),使得从堆空间分配得到的结构变量能够返还。
5、定义两个同种单向链表(结点中包含一个整型数和一个指向本结点类型的指针),该两链表中数据都已排序好,编制程序,合并这两个链表。
6、建立一个10结点的单向链表,每个结点包括:学号、姓名、性别、年龄。对其进行排序,采用插入排序法,按学号从小到大排列。

[基本要求]
用一个自定义函数,负责输入5个学生数据;用另一个自定义函数求总平均分;再用一个自定义函数求出最高分学生数据;总平均分和最高分的学生的数据都在主函数中输出。
用多文件程序结构实现。
第六部分 复习要点
1、 理解:使用结构的场合、结构的概念。结构成员的访问方式,给结构赋值的方式、结构作为函数参数与返回值的方式,能够在程序中正确选择使用结构。
2、 理解:结构和指针、结构数组的使用方式,能在程序中熟练使用。
3、 应用:利用结构实现链表操作。能够实现链表结构的定义、链表的创建、遍历操作。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值