《计算机程式设计》Week5 课堂笔记
本笔记记录自 Coursera课程 《计算机程式设计》 台湾大学 刘邦锋老师
Week5 Pointer
5-1 Pointer Definition and Declaration
指针和一般变量的区别。一般变量的值就是代表数据,而指针变量的值则代表另一个变量的记忆体位址。一般变量有数据类别,比如说整数,浮点数等。指针变量也有数据类型,比如说指向整数的指针,指向浮点数的指针等。
申明指标变量的方法
int *iptr;
float *fptr;
double *dptr;
在变量名称前加一个*号代表这是一个指标变量。而*之前的数据类型就是这个指标变量所能指到的变量数据类型。
例子:(size.c)指针变量所占的位元组数
#include <stdio.h>
int main(void)
{
int *iptr;
float *fptr;
double *dptr;
printf("sizeof(iptr) = %d\n", sizeof(iptr));
printf("sizeof(fptr) = %d\n", sizeof(fptr));
printf("sizeof(dptr) = %d\n", sizeof(dptr));
return 0;
}
输出结果
sizeof(iptr) = 8
sizeof(fptr) = 8
sizeof(dptr) = 8
所以说,不管是指向4个位元组的整数,还是8个位元组的倍准浮点数,指针变量都是占8个位元组,因为他们都是存8个位元组(64 bit)的记忆体位址。
指定整数指标变量的值
int i;
int *iptr1;
int *iptr2;
iptr1 = &i;
iptr2 = iptr2;
- 当一个整数指针变量iptr的值是另一个整数变量i的记忆体位址时,称iptr指向i。
- 一个整数变量的记忆体位址可以指定给一个整数指针变量当作值,也可以将一个整数指针变量的值指定给另一个整数指针变量当作值。
使用指标变量所指到的变量
i = *iptr;
*iptr = i;
- 当一个指针变量前加上*时,就代表从这个记忆体位址取值(dereference)。
- *iptr可以出现在=的右边或左边。
NULL绝不指向任何有效的记忆体位址
#include <stdio.h>
...
ptr = NULL;
- 有时我们希望程序能够让一个指针变量不指向任何有效的记忆体位址,方法就是将它初始化成一个“特殊值”。
- 为此C语言定义了NULL作为这样的用途。一般这个值是定义成0,因为0这个记忆体位址通常是保留给系统使用,一般程序是不能使用的。
- NULL是在<stdio.h>标头档定义的,所以必须引入。
5-2 Pointer Usage
使用更加复杂一点的例子来讲解指针与普通变量的关系。可以使用指针以及指针的取值进行一些操作。
5-3 Pointer Reference and Dereference
使用更加复杂一点的例子来讲解指针与指针位址与普通变量位址的关系。
总之只要了解*是取值,&是取位址。
5-4 Pointer Parameter Passing
例子:(pointer-parameter.c)指针参数传递
#include <stdio.h>
void pointer_inc(int *p1, int *p2)
{
printf("The address of p1 is %p\n", &p1);
printf("The value of p1 is %p\n", p1);
printf("The address of p2 is %p\n", &p2);
printf("The value of p2 is %p\n", p2);
*p1 += 1;
p1 = p2;
*p1 += 2;
}
int main(void)
{
int i, j;
int *iptr = &i;
scanf("%d", &i);
scanf("%d", &j);
printf("The address of i is %p\n", &i);
printf("The address of j is %p\n", &j);
printf("The address of iptr is %p\n", &iptr);
printf("i = %d, j = %d\n", i, j);
pointer_inc(iptr, &j);
printf("i = %d, j = %d\n", i, j);
*iptr += 5;
printf("i = %d, j = %d\n", i, j);
return 0;
}
输入
10 20
输出
The address of i is 0x7fff1592a9c0
The address of j is 0x7fff1592a9c4
The address of iptr is 0x7fff1592a9c8
i = 10, j = 20
The address of p1 is 0x7fff1592a9a8
The value of p1 is 0x7fff1592a9c0
The address of p2 is 0x7fff1592a9a0
The value of p2 is 0x7fff1592a9c4
i = 11, j = 22
i = 16, j =22
所以总结一下:
- 实际参数与形式参数使用不同的记忆体,所以实际参数iptr和对应的形式参数p1位于不同的记忆体位址。
- 虽然p1已经改指向j,但是iptr还是指向i,因为p1和iptr是位于不同记忆体位址的不同变量,改变一个并不会改变另一个。
5-5 Pointer and Array
指针指向数组的起始记忆体位址
int array[100];
int *iptr;
...
iptr = array;
...
use *iptr as array[0]
...
- iptr = array;就是使iptr指向数组的起始记忆体位址。
- 因为数组的起始记忆体位址是array[0]的位址,所以*iptr就如同array[0]。有如用指针的语法从数组内取元素。
将指针加1
iptr++;
- C语言中有一套指针的算术语法,让指针可以加减一个整数,最常用的就是将指针加1。
- 这里的加1是指加一个元素的大小。所以如果是整数,指针变量就是加sizeof(int) = 4,而如果是倍准浮点数,指针变量就是加sizeof(double) = 8。可以想象成指向下一个元素。
例子:(inc3-with-pointer.c)利用指针将数组元素加3
#include <stdio.h>
int main(void)
{
int a[5];
int i;
int *ptr;
for (i = 0; i < 5; i++)
scanf("%d", &(a[i]));
for (i = 0, ptr = a; i < 5; i++, ptr++){
printf("%p\n", ptr);
*ptr += 3;
}
for (i = 0; i < 5; i++)
printf("a[%d] = %d\n", i, a[i]);
return 0;
}
输入
1 2 3 4 5
输出
0x7fff4b931d90
0x7fff4b931d94
0x7fff4b931d98
0x7fff4b931d9c
0x7fff4b931da0
a[0] = 4
a[1] = 5
a[2] = 6
a[3] = 7
a[4] = 8
5-6 Pointer and Relative Index
指针也可以使用数组的语法
*(iptr + i)
iptr[i]
- 指针变量加i就是指向下i个的意思。
- 指针变量iptr指向数组a的起始位置,再加i,再用星号取值,那*(iptr + i)就正是a[i]。
但是注意:数组的是绝对坐标,而指针变量的是相对坐标。
5-7 Pointer Arithmetic
例子:(arith.c)指针算术
#include <stdio.h>
int main(void)
{
int array[10];
int *iptr1 = &(array[3]);
int *iptr2 = iptr1 + 4;
printf("iptr1 = %p\n", iptr1);
printf("iptr2 = %p\n", iptr2);
printf("iptr2 - iptr1 = %d\n", iptr2 - iptr1);
return 0;
}
输出
iptr1 = 0x7fffa00d9f8c
iptr2 - 0x7fffa00d9f9c
iptr2 - iptr1 = 4
- 指针算术可以将一个指针加一个常数得到一个指针,自然也可以减去一个常数得到一个指针,而且两个指针也可以相减得到一个常数。
- 这里的常数都不是指一般以位元组为单位的记忆体位址,而是以指针所指到元素大小为单位。
5-8 Pointer as Return Value
例子:(first-positive.c)将iptr所指到的记忆体中的第一个正整数的记忆体位址返回
#include <stdio.h>
int *first_positive(int *ptr)
{
while (*ptr <= 0)
ptr++;
return ptr;
}
int main(void)
{
int i;
int array[10];
int *iptr;
for (i = 0; i < 10; i++)
scanf("%d", &(array[i]));
iptr = first_positive(array);
printf("*iptr = %d\n", *iptr);
printf("iptr -array = %d\n", iptr - array);
iptr = first_positive(array[5]);
printf("*iptr = %d\n", *iptr);
printf("iptr -array = %d\n", iptr - array);
return 0;
}
输入
0 0 0 5 9 0 0 6 0 2
输出
*iptr = 5
iptr - array = 3
*iptr = 6
iptr - array = 7
5-9 Caution in Using Pointer
指针的用途
- 在动态分配记忆体时,我们可以直接向作业系统要求一块记忆体使用,所以我们需要一个机制,让程序有办法能记住要来的记忆体位址,这个机制就是指针变量。
- 动态数据结构会将数据串联起来形成结构。此时数据结构的大小与形状都是依动态要求而调整,此时我们就需要指针来描述数据之间的连结关系。
- 在处理字串时,程序常常需要以记忆体位址来沟通。此时沟通的双方未必能够使用数组的标注语法,因为其中一方未必能够知道数组的起始记忆体位址。此时使用指针直接指到记忆体是最有效的沟通方法。
注意事项
- 除非是上述的动态配置记忆体,动态数据结构及字串处理,否则尽量避免使用指针。
- C语言中对指针的使用没有安全机制,初学很容易弄错,而且难以除错。
- 很多指针的使用是可以用数组语法代替的,这样不但容易月度,也容易除错。
- 有人认为使用指针可增加效率,但是现在的编译器已经能产生非常好的执行档。为了效率牺牲可读性也许并不值得。
测验代码
老师的
#include <stdio.h>
void shuffle ( int *deck[] )
{
int i = 0;
int buf[10000];
while ( deck[i] ) {
buf[i] = *deck[i];
i++;
}
int sec_s = i / 2 + i % 2;
int fir = 0, sec = sec_s, deck_p = 0;
while ( deck[deck_p] ) {
*deck[deck_p++] = buf[fir++];
if ( buf[sec] )
*deck[deck_p++] = buf[sec++];
}
}
void print ( int *deck[] )
{
int i = 0;
while ( deck[i] ) {
if ( i ) printf ( " " );
printf ( "%d", *deck[i++] );
}
}
int main()
{
int card[10000];
int *deck[10000];
int index = 0;
while (scanf("%d", &(card[index])) != EOF) {
deck[index] = &(card[index]);
index++;
}
deck[index] = NULL;
shuffle(deck);
print(deck);
return 0;
}