以下为个人学习笔记和习题整理
课程:计算机程序设计(C++)- 西安交通大学 @ 中国大学 MOOC
https://www.icourse163.org/course/XJTU-46006
# 课堂笔记
# 一维数组的指针
数组占据内存中一块连续的存储空间,每个数组元素都有确定的内存地址;
可通过定义指向数组元素类型的指针变量,间接访问数组中的各个元素。
C++ 语言规定,数组名代表数组的首地址(即数组中第一个元素的地址),它是一个常量指针。
如
int a[10], *p=a; |
说明 a 是一个整型数组, p 是一个整型的指针变量,且 p 指向 a 数组,其中 a 为数组名,代表数组的首地址即为 &a[0] 。
# 指针类型的算术运算
- 指针 ± 整数 ➡️ 指针
假设:
int a[10]={10,20,30}, *p=a, i; | |
cout<<*p<<endl; | |
p++; //p 指向 a [1] | |
cout<<*p<<endl; | |
p++; //p 指向 a [2] |
a 数组内存分配示意:
| 指针位置 | 地址 | 值 | 变量名 |
|---|---|---|---|
| p ➡️ | 1000 | 10 | a[0] |
| 1004 | 20 | a[1] | |
| 1008 | 30 | a[2] | |
| ... | ... | ... | |
| 1024 | a[9] |
p+i:表示p所指元素之后的第i个元素的指针p-i:表示p所指元素之前的第i个元素的指针
- 指针的算术运算与数学中运算不同。
如p的初值为a[0]的地址,即1000,执行p++后,p将指向a[1]元素,这时p的值为1004,而不是1001。- 实际上,在 C++ 中指针的算术运算与指针指向的变量类型有关。
如p指向int型,因int型变量为 4 字节长度,所以,p+1相当于p+4,同样p+i相当于p+i*4。
- 指针 2 - 指针 1 ➡️ 整数
两个同类型的指针做减法运算,常用于计算两个指针之间包含元素的个数。
计算方法:(指针 2 - 指针 1)/ 元素的字节长度
如: p1 、 p2 是整型指针, p1 中的地址为 1000 , p2 中的地址为 1008 。
那么 p2-p1 ,相当于 (1008-1000)/4 ,结果为 2 ,说明 p1 到 p2 之间包含 2 个元素。
# 指针类型的关系运算
即两个指针可以比较大小。(将指针看作整数)
例如:使用指针输出数组各元素值。
double x[5]={1,2,3.5,4,5.7},*p; | |
for(p=x;p<x+5;p++) { | |
cout<<*p<<"\t"; | |
} | |
cout<<endl; |
这里使用指针变量
p作循环控制变量,循环条件p<x+5为指针的关系运算,其中x+5表示&x[5]。
# 数组元素的指针法表示
设:
int a[10],*Ptr=a,i; |
∵ a[i] 的地址可以用 a+i 表示(即数组的首地址 + i),
对地址 a+i 进行间接访问运算,即 *(a+i) ,
而 *(a+i) 又解释为指针 a+i 所指向的对象,即 a[i] ,
∴ a[i] 与 *(a+i) 的表示是等价的 。
其中: a[i] 称为数组元素的下标法表示,而 *(a+i) 称为数组元素的指针法表示。
又 Ptr 指向数组首地址,所以 *(a+i) 与 *(Ptr+i) 等价。
从而: a[i] , *(a+i) , *(Ptr+i) 和 Ptr[i] 的四种形式均等价。
例如:使用数组元素不同的表示形式输出数组元素的值
int a[10]={1,2,3,4,5,6,7,8,9,10},*p=a,i; | |
// 下标法 | |
cout<<"a[i]"<<"\t"<<"p[i]"<<endl; | |
for(i=0;i<10;i++) { | |
cout<<a[i]<<"\t"<<p[i]<<endl; | |
} | |
// 指针法 | |
cout<<"*(a+i)"<<"\t"<<"*(p+i)"<<endl; | |
for(i=0;i<10;i++) { | |
cout<<*(a+i)<<"\t"<<*(p+i)<<endl; | |
} |
# 二维数组的指针
设二维数组:
int a[3][4],i,j; |
- 二维数组
a中i行j列元素的地址:&a[i][j]
二维数组在内存中映射为一个一维数组,因此可以通过指向元素的指针,快速访问二维数组中的每个元素。
例子:利用指向数组元素类型的指针变量 p ,寻找 a 数组中元素的最大值
假设:
int *p, max=a[0][0]; //max 为最大值 | |
for(p=&a[0][0];p<&a[0][0]+12;p++) | |
{ | |
if(*p>max) | |
{ | |
max=*p; | |
} | |
} | |
cout<<"max="<<max<<endl; |
- 二维数组的行地址
int a[3][4]; |
由 3 行元素组成,即 a - a[0] 、 a[1] 、 a[2]
而每行又由 4 个类型相同的元素组成,分别对应一个一维数组。
| 行 | 对应数组 | |||
|---|---|---|---|---|
a[0] | a[0][0] | a[0][1] | a[0][2] | a[0][3] |
a[1] | a[1][0] | a[1][1] | a[1][2] | a[1][3] |
a[2] | a[2][0] | a[2][1] | a[2][2] | a[2][3] |
其中:a 为行元素数组的名字,即 a 代表 &a[0] , 即 0 行的地址a+1 代表 &a[1] ,即 1 行的地址a+2 代表 &a[2] ,即 2 行的地址。
# 二维数组的指针表示法
由于 a[0] 是由 a[0][0] , a[0][1] , a[0][2] 和 a[0][3] 四个元素构成的一维数组。
因此, a[0] 代表 &a[0][0] ,即 0 行的首元素的地址。
这样 a[0]+1 代表首元素的下一个元素的地址,即 &a[0][1] ;而 a[0]+j 就代表 &a[0][j] 。
同理, a[1] 代表 &a[1][0] ,即 1 行的首元素的地址;而 a[1]+j 代表 &a[1][j] 。a[i] 代表 &a[i][0] ,即 i 行的首元素的地址;而 a[i]+j 代表 &a[i][j] 。
由此我们得到: *(a[i]+j) 等价于 a[i][j]
又由于 a[i] 等价于 *(a+i) ,
因此, *(a[i]+j)) 也等价于 *(*(a+i)+j)
即: *(*(a+i)+j) 与 a[i][j] 等价。
我们将 *(*(a+i)+j) 称为二维数组元素 a[i][j] 的指针法表示。
其中, a 为首行地址, a+i 为 i 行的行地址,而 *(a+i) 为 a 的 i 行 0 列元素的
地址, 而 *(a+i)+j 为 a 的 i 行 j 列元素的地址。
# 指向具有 M 个元素的一维数组指针
<类型> (*<指针变量>)[M]; //M 为一整型常量 |
通常利用该指针变量,指向二维数组的行地址,其中 M 表示二维数组的列数。
例如:利用行指针变量,按行输出二维数组各元素值。
int a[3][4]={ {1,3,5,7},{2,4,6,8},{1,2,3,4} },(*p)[4]; | |
for(p=a;p<a+3;p++) | |
{ | |
// 输出 p 所指行的各列元素值 | |
for(int j=0;j<4;j++) | |
{ | |
cout<<*(*p+j)<<"\t"; | |
} | |
cout<<endl; | |
} |
其中:
p是一个行指针,初值为0行的行地址;p++后,p指向下一行;*p代表该行0列元素的地址,*p+j为该行j列元素的地址。
int a[3][4]={ {1,3,5,7},{2,4,6,8},{1,2,3,4} },(*p)[4]; | |
for( p=a;p<a+3;p++) | |
{ | |
// 输出 p 所指行的各列元素值 | |
for(int *q=*p;q<*p+4;q++) { | |
cout<<*q<<"\t"; | |
} | |
cout<<endl; | |
} |
其中:
p是一个行指针,初值为0行的行地址;p++后,p指向下一行;*p代表该行0列元素的地址。q是指向列元素类型的指针,初值为*p,即为该行0列元素的地址。
# 字符串指针
字符串指针是字符串的首地址,即第 1 个字符(索引为 0)的地址。
使用 char 型指针变量存放其首地址。
char *str; | |
str="Hello"; | |
// 或 | |
char *str="Hello"; |
字符串常量可看成存放在一个一维的字符数组中。
当指针指向字符串后,引用字符串中的字符可使用下面的形式:
*(指针变量+下标) | |
// 或 | |
指针变量[下标] |
# 应用:在字符串中查找某字符
- 函数原型:
char *strchr(char *str, char c); |
- 算法:
从str的第一个字符查起,查到字符后就返回该字符的地址;
查不到则返回空值NULL。
// 函数 strchr:在 str 指向的字符串中查找 c 中的字符 | |
char *strchr(char *str, char c) { | |
while(*str!='\0') | |
{ | |
if(*str==c) | |
{ | |
return str; | |
} | |
str++; | |
} | |
return NULL; | |
} | |
int main() | |
{ | |
char *str="abcdefghij"; | |
char *p; | |
p=strchr(str,'j'); // 查找 j 的位置 | |
if(p==NULL) | |
{ | |
cout<<"字符串中无该字符。"<<endl; | |
} | |
else | |
{ | |
cout<<"该字符在串中的位置是:"<<p-str<<endl; | |
//p-str 为一整数值,该值正是所找到字符的索引号:9 | |
} | |
return 0; | |
} |
# 特别注意
字符型指针与字符数组在使用中异同,如:
char str[81]="abcde", *pstr="abcde";// 正确
char str[81],*pstr;
str="abcde"; // 错误
pstr="abcde"; // 正确
char str[81], *pstr;
cin>>str; // 正确
cin>>pstr; // 错误
# 指针数组
<类型> *<数组名>[<元素个数>]; |
// 定义 5 个 int 型的指针数组 | |
int *ptr[5]; | |
// 定义指向多个字符串的指针数组 | |
char *str[]={"Basic", "Fortran", "C++", "Java"}; |
# 例子:将月份数值转换为相应的英文名称
char *month_name( int n ) | |
{ | |
// 定义一个静态指针数组 | |
static char *month[]= | |
{ | |
"Illegal month", // 月份值错 | |
"January", // 一月 | |
"February", // 二月 | |
"March", // 三月 | |
"April", // 四月 | |
"May", // 五月 | |
"June", // 六月 | |
"July", // 七月 | |
"August", // 八月 | |
"September", // 九月 | |
"October", // 十月 | |
"November", // 十一月 | |
"December" // 十二月 | |
}; | |
return (n>=1 && n<=12)?month[n]:month[0]; // 返回字符串的指针 | |
} | |
int main() | |
{ | |
int n; | |
cin>>n; | |
cout<<n<<"-"<<month_name(n)<<endl; | |
return 0; | |
} |
# 指针与结构体
结构体变量的指针:
&<结构体变量名> |
定义指向结构体变量的指针:
<结构体类型> *<指针变量名>; |
使用结构指针访问结构变量中的成员:
// 格式 1: | |
(*<指针变量>).<成员名> | |
// 格式 2: | |
<指针变量>-><成员名> |
其中: -> 称为结构指向运算符。
# 例子:使用结构指针访问日期结构类型变量
// 定义日期结构类型 | |
struct Date { | |
int year; | |
int month; | |
int day; | |
}; | |
int main() | |
{ | |
Date d={2015,4,8},*p=&d; // 定义日期结构变量和指针变量 | |
cout<<(*p).year<<"-"<<(*p).month<<"-"<<(*p).day<<endl; | |
cout<<p->year<<"-"<<p->month<<"-"<<p->day<<endl; | |
return 0; | |
} |
# 动态数组
问题:如何定义 n 个元素的一维数组?
int n; | |
cin>>n; | |
int array[n]; // 标准 C++ 语言不支持该定义。 |
# new 运算符
用于动态申请所需的内存空间
- 动态申请单个变量
<指针变量> = new <类型>; |
例子:动态申请一个 double 型变量,初值为 100.0
double *p; | |
p=new double; | |
*p=100.0 | |
// 或 | |
p=new double(100.0); |
- 动态申请数组
<指针变量> = new <类型>[<元素个数>]; | |
// < 元素个数 > 通常为常量 \ 变量 \ 表达式 |
例子:动态申请存放 80 个字符的数组
char *str; | |
str=new char[80]; |
例子:动态创建 n 个元素的一维整型数组
int n,*p; | |
cout<<"请输入n值:"; | |
cin>>n; // 键盘输入 n | |
p=new int[n]; // 动态建立 n 个元素的一维数组 | |
if(p==NULL) // 如果申请失败,则 返回的指针值为 NULL 即 0 值。 | |
{ | |
cout<<"空间申请失败!"; | |
return 1; | |
} | |
cout<<"请输入n个数:"; | |
for(int i=0;i<n;i++) { | |
cin>>p[i]; | |
} |
# delete 运算符
释放动态申请到的存储空间
- 动态释放单个变量
delete <指针变量>; // 释放单个动态变量 |
- 动态释放数组
delete [] <指针变量>; // 释放动态数组 |
# 课堂讨论
- 指向数组首元素的指针变量有哪些算术运算,意义是什么?
- 指针 ± 整数 ➡️ 指针
表示指针所指元素之后或之前的第整数个元素的地址。- 指针 2 - 指针 1 ➡️ 整数
这个整数除以元素的字节长度表示两指针之间包含的元素个数。
- 指针的关系运算的意义是什么?
当两个指针指向同一个数组中的元素时,指向数组后面的元素的指针减去指向数组前面的元素的指针的意义是这两者之间相差的元素的个数。
比较元素的前后位置
可以作为循环的控制条件
- 请总结二维数组(如
int A[N][M])和指向元素的指针(如int *p;)以及指向一维数组的指针(行指针,如int (*q)[M];)关系、数组元素的引用方法。
二维数组相当于一个特殊的一维数组,里面每个元素又是一个一维数组。
例如:int a[3][3],可以看成 3 行的一维数组,每一行的元素有 3 个,数组名是一个特殊一维数组的首地址。二维数组的指针分为两种:
- 行指针,如:
a,是一个指向数组的指针;- 列指针,如:
&a[0][0],a[0],*a, 是一个指向单个变量的指针。a,&a[0][0],a[0],*a表示的都是指针,并且是同一块内存的地址,但它们表示的意义是不同的。
a表示行指针(指向一个数组),*a,a[0],&a[0][0]表示列指针(这三个是等价的,都指向数组中的一个元素)。以取
a[2][1]的值为例:
如果是行指针,则先要对它进行解引用,变为一个列指针,然后在解引用得到其值a[2][1]=*(*(a+2)+1)。
如果是列指针,则直接进行解引用即可a[2][1]=*(*a+2*4+1)。
- 如果动态申请大小为
n*(m+1)的一维字符数组,如char *p=new char[n*(m+1)];。如何在其中保存多个(小于 n)长度不超过 m 的字符串(比如保存多个单词)?请写出输入和输出的格式例句。
for (i=0;i<n;i++) | |
{ | |
cin >> p+(m+1)*i; | |
} | |
for (i=0;i<n;i++) | |
{ | |
cout << p+(m+1)*i << endl; | |
} |
# 随堂练习
设数组 a 的首地址是
0x28fe74,则下列程序的执行结果是 。int a[10]={10,20,30};
int *p;
p=a;
p++;
cout<<p<<endl;
int a[10]={1,2,3,4,5,6,7,8};
int *p=a;
和
a[i]起的作用不同的表达式是 。设有二维数组
int a[3][4];和指针int *p=&a[0][0];,下列哪项是与a[i][j]的作用相同的表示法设有二维数组
int A[3][4];下列哪项声明的指针可以赋值A。声明指向字符串首元素的指针
char *p="warrant";,下列哪个语句的执行是不正确的。有下列声明语句
char s[6][10]= {"China's", "first", "football", "textbooks", "for","schools"};
char *p[5],(*q)[10],*r;
下列哪个赋值语句是不正确的?
有下列表示按钮的结构体和声明语句
struct BUTTON{
int x,y;
int width,height;
char name[20];
};
struct BUTTON top[10],*p=top;
下列哪个输入 “按钮” 名称的语句是正确的?
有声明语句
int *p,*q[10],(*r)[10];
int n=10;
下列哪个申请大小为 n 的一维动态数组的语句是正确的?