2.指针(二)
在计算机中,内存是以字节为单位的连续存储空间,每一个字节都有一个编号,这个编号称为地址。
由于内存的存储空间是连续的,因此地址也是连续的。
这就像教学楼中的每个教室都有编号一样。在地址所标志的内存单元中存放数据,这相当于教室中的学生一样。
系统为变量分配内存单元地址,地址是一个无符号的整型数。
一般把内存中的一个字节称为一个内存单元。
为了正确的访问这些内存单元,必须为每个内存单元编上号,根据一个内存单元的编号即可准确的找到该内存单元。 内存单元的编号也叫做地址,通常把这个地址称为指针。
如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)。
一个变量的内存地址称为该变量的指针。
如果一个变量用来存储指针,则称该变量为指针类型的变量(一般称为指针变量)。
变量的地址就是变量的指针,存放变量地址的变量就是指针变量。
指针的本质是一个有类型的地址(指向了最低位的地址),是一个有类型的常量。
用以存放指针的量,我们叫做指针变量。
指针的类型决定了该指针的寻址能力,即从指针所代表的地址处的寻址范围。
#include <stdio.h>
int main(int argc, const char * argv[]) {
char a = 1;
short b = 2;
int c = 10;
double d = 123.45;
printf("&a = %p \n", &a);
printf("&b = %p \n", &b);
printf("&c = %p \n", &c);
printf("&d = %p \n", &d);
// 如果没有类型的强转是不行的
printf("%d\n", *((char *)0x7ff7bfeff29f));
printf("%d\n", *((short *)0x7ff7bfeff29c));
printf("%d\n", *((int *)0x7ff7bfeff298));
printf("%f\n", *((double *)0x7ff7bfeff290));
return 0;
}
结果:
&a = 0x7ff7bfeff29f
&b = 0x7ff7bfeff29c
&c = 0x7ff7bfeff298
&d = 0x7ff7bfeff290
1
2
10
123.450000
为什么要有指针
#include <stdio.h>
#include <stdlib.h>
/*--自定义函数:交换两个变量的值--*/
void swap(int a, int b) {
int temp;
temp = a;
a = b;
b = temp;
}
int main() {
system("color 70");
int dA, dB;
puts("请输入两个整数:");
printf("整数dA:");
scanf("%d", &dA);
printf("整数dB:");
scanf("%d", &dB);
swap(dA, dB);
puts("互换以后的变量值:");
printf("dA=%d dB=%d\n", dA, dB);
system("pause");
return 0;
}
上面的执行结果是:
请输入两个整数:
整数dA:10
整数dB:8
互换以后的变量值:
dA=10 dB=8
sh: pause: command not found
Process finished with exit code 0
为什么dA和dB没有变化呢?
main()函数调用swap()函数时,实参dA、dB的值分别传给形参a和b值传递,在swap()函数内变量a和b的值进行了互换。
但是,变量a和b只是实参变量dA、dB的副本,变量dA、dB本身的值并没有改变。因此,在此代码中使用函数并没有实现变量a和b的值的交换。
C语言是以传值的方式将参数值传递给被调用函数,因此,被调用函数不能直接修改主调用函数中变量的值。

为了解决这个问题,我们可以使用C语言提供的指针(pointer)。
指针实质上是一个变量,该变量里面存储的是某个特定变量在内存中的地址,我们把它表述为指向该特定变量的指针变量(指针),该指针变量的类型与它指向的变量的类型一致。
scanf函数
scanf()和printf()类似,也是用格式字符串和参数列表。scanf()中的格式字符串表明字符输入流的目标数据类型。
两个函数主要的区别在参数列表中。printf()函数使用变量、常量和表达式。而scanf()函数使用指向变量的指针:
- 如果用scanf()读取基本变量类型的值,在变量名前加上一个&;
- 如果用scanf()把字符串读入字符数组中,不要使用&;
参数
将参数值或数据传递给C语言函数有两种方法:
- 通过值调用
- 通过引用调用
原始值在通过值调用时不会修改,但通过引用调用时,在函数中可进行修改。
形式参数
声明参数就创建了被称为形式参数(formal argument或formal paramter),简称形参的变量。
我们称函数调用传递的值为实际参数(actual argument或actual paramter),简称实参数。
double avaerage(double a, double b) {
return (a + b) / 2;
}
上面函数中的标识符a和标识符b(即函数的形式参数(parameter))表示在调用avaerage函数时需要提供的两个数。
每一个形式参数都必须有类型。而我们在调用该函数的时候,为了调用函数,需要写出函数名及跟随其后的实际参数(argument)列表。例如,average(x, y)是对average函数的调用。实际参数用来给函数提供信息。在此例中,会把变量x和y的值复制给形式参数a和b。
在C语言中,实际参数是通过值传递的:调用函数时,计算出每个实际参数的值并且把它复制给相应的形式参数。
在函数执行过程中,对形式参数的改变不会影响实际参数的值,这是因为形式参数中包含的是实际参数值的副本。
通过值调用
在通过值调用函数时,原始值不被修改。
在通过值调用时,传递给函数的值由函数参数在堆栈内存位置中本地存储。如果更改函数参数的值,则仅对当前调用函数中的值进行临时更改。它不会改变调用者方法(如:main())中的变量的值。
即: 函数只是使用参数值的一个副本,函数执行完成副本删除,而原始值不会有任何影响。
#include <stdio.h>
void change(int num) {
printf("Before adding value inside function num=%d \n", num);
num = num + 10;
printf("After adding value inside function num=%d \n", num);
}
int main() {
int x = 10;
// 更新之前参数x的值
printf("Before function call x=%d \n", x);
change(x); // passing value in function
// 更新之后参数x的值
printf("After function call x=%d \n", x);
return 0;
}
执行上面示例代码,得到以下结果 -
Before function call x=10
Before adding value inside function num=10
After adding value inside function num=20
After function call x=10
通过引用调用
在通过引用的调用中,原始值可被修改并反映到外部调用者。
这里,使用值的地址传递到函数中,所以实际和形式的参数共享相同的地址空间。
因此,函数内部的值被改变,都会反映在函数内部和外部,因为函数内部和外部此时都指向参数的地址空间。
#include <stdio.h>
// 定义函数,注意这里的参数名称前加了一个星号(*)
void change2(int *num) {
printf("Before adding value inside function num=%d \n", *num);
(*num) += 100;
printf("After adding value inside function num=%d \n", *num);
}
int main() {
int x = 200;
printf("Before function call x=%d \n", x);
// 注意,下面传递的参数名称前加了一个符号(&)
change2(&x);// passing reference in function
printf("After function call x=%d \n", x);
return 0;
}
执行上面示例代码,得到以下结果:
Before function call x=200
Before adding value inside function num=200
After adding value inside function num=300
After function call x=300
指针
指针是一种保存变量地址的变量。
每个变量都有一个内存位置,每个内存位置都定义可使用(&)访问的地址,它表示了在内存中的地址。
和Go语言中的指针一样,Go完整的保留了C中的指针部分。
虽然用数表示地址,但是地址的取值范围可能不同于整数的范围,所以一定不能用普通整型变量存储地址。
但是,可以用特殊的指针变量(pointer variable)存储地址。地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式、常量或register类型的变量。
指针变量中存放的就是变量在内存中的地址。
指针是一个变量,其值是另一个变量的地址(一个代表内存地址的十六进制数),即内存位置的直接地址,就像其他变量或常量一样,你必须在使用指针存储其他变量地址之前,对其进行声明。
指针就是地址,而指针变量就是存储地址的变量。
指针变量的声明与普通变量的声明基本一样,唯一的不同就是必须在指针变量名字前放置星号,声明格式:
type *varname // varname是指向type类型对象的指针变量
例如:
int *ip;
double *dp;
char *ch;
为使用指针,C语言提供了一对特殊设计的运算符。为了找到变量的地址,可以使用&(取地址)运算符。
如果x是变量,那么&x就是x在内存中的地址。为了获得对指针所指向对象的访问,可以使用*(间接寻址)运算符。如果p是指针,那么*p表示p当前指向的对象。
访问指针所指向的地址对应的变量的值
通过*运算符来返回位于操作数所指定地址的变量的值。
#include <stdio.h>
int main () {
int var = 20; /* 实际变量的声明 */
int *ip; /* 指针变量的声明 */
ip = &var; /* 在指针变量中存储 var 的地址 */
printf("Address of var variable: %p\n", &var);
printf("Address stored in ip variable: %p\n", ip);
printf("Value of *ip variable: %d\n", *ip);
*ip = 666;
printf("Value of var variable: %d\n", var);
return 0;
}
执行结果:
Address of var variable: 0x7ffee0c5e8b8
Address stored in ip variable: 0x7ffee0c5e8b8
Value of *ip variable: 20
Value of var variable: 666
指针变量中存放的就是变量在内存中的地址。
指针的定义类似于普通变量的定义,只是需要在指针(变量)名前添加指针运算符*。
将指针运算符*写在指针之前(如*pa),可以获取指针指向的变量(对象)内存储的内容。
即指针pa指向变量a时,*pa就是变量a的别名,给*pa赋值就相当于给a赋值,*pa=80;等价于a=80;。

在程序中,我们可以利用指针(变量的别名)来修改变量的值。
NULL指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋值一个NULL值是一个良好的编程习惯。赋值为NULL值的指针被称为空指针。
NULL指针是一个定义在标准库中的值为零的常量。
int *pr = NULL;
printf("*pr: ", pr);
执行结果为:
*pr : 0x0
在大多数操作系统上,程序不允许放位地址为0的内存,因为该内存是操作系统预留的。
然而,内存地址0有特别重要的意义,它表明该指针不指向一个可以访问的内存地址。
如需检查一个空指针,可以使用if语句:
if (ptr) {
// 如果ptr为空则...
}
if (!ptr) {
// 如果ptr不为空则...
}
指针的算术运算
C指针是一个用数值表示的地址。因此,你可以对指针执行算术运算。
可以对指针进行四种算术运算: ++、--、+、-。
假设ptr是一个指向地址1000的整型指针,是一个32位的整数,让我们对指针下面的运算:
ptr++
在执行完上述的运算之后,ptr将指向位置1004,因为ptr每增加一次,它都将指向下一个整数位置,既当前位置往后移4个字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。
如果上面ptr指向一个地址为1000的字符,上面的运算会导致指针指向位置1001,因为下一个字符位置在1001。
指针的算术运算:
pp + 1的实质就是pp + sizeof(type)。 例如:
int *p = 0x0001;
int *q = 0x0005;
if (pp + 1 == q) {
// true
}
if (q - p == 1) {
// true
}
函数指针
函数是C程序的基本模块,在编译C程序时,系统会为每一个函数分内存单元,函数既然占有内存单元,那么函数也是有地址的,这个地址称为函数的入口地址。
编写的源程序经过编译以后,其中的函数将编译成一段程序并存放在内存中,该函数有一个入口地址,调用函数时,将转向这个入口地址继续执行这里的函数,直到遇到返回语句,结束函数的调用。
函数的入口地址称为函数的指针。同数组名代表数组的地址相似,函数名也代表函数的入口地址。
函数指针是指向函数的指针变量。
函数指针可以像函数一样,用于调用函数、传递参数。
声明:
int(*func_ptr)(int, int);
例如:
#include <stdio.h>
int max(int x, int y) {
return x > y ? x : y;
}
int main(void) {
// &可以省略
int (*p)(int, int) = &max;
int a = 1, b = 2, c = 3, d;
// 与直接调用函数等价,d = max(max(a, b), c)
d = p(p(a, b), c);
printf("最大的数字是: %d\n", d);
return 0;
}
执行结果:
最大的数字是: 3
指针作为函数返回值
int *max(int *a, int *b) {
if (*a > *b) {
return a;
} else {
return b;
}
}
回调函数
函数指针作为某个函数的参数。
函数指针变量可以作为某个函数的参数来使用,回调函数就是一个通过函数指针调用的函数。
简单的讲回调函数就是由别人的函数执行时调用你实现的函数。
#include <stdlib.h>
#include <stdio.h>
// 第三个参数是回调函数
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void)) {
for (size_t i=0; i<arraySize; i++) {
array[i] = getNextValue();
}
}
// 获取随机值
int getNextRandomValue(void) {
return rand();
}
int main(void) {
int myarray[10];
// 第三个参数传入getNextRandomValue函数的指针,这里可以省略&
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
执行结果:
16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709
数组指针
一个变量有地址,一个数组同样有地址。不同的是,一个数组包含有若干个元素,每个元素都要占据内存空间,且数组占据的内存空间也是连续的。指针变量既然可以指向变量,也可以指向数组。因此,在引用数组元素的时候,可以用下标法(如a[6]),也可以使用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能够使目标程序质量高:占内存少,运行速度高。
指针和数组之间的关系十分密切。通过数组下标能完成的任何操作都可以通过指针来实现。一般来说,用指针编写的程序比用数组下标编写的程序执行速度快。
int a[] = {3, 0, 3, 4, 1};
int *p = &a[0];// 数组第一个元素的指针
*++p = 2; // ++p表示第二个元素,这里是将第二个元素的值修改为2,数组变成3 2 3 4 1
因为数组名所代表的就是该数组最开始的一个元素的地址,所以,赋值语句 p = &a[0]也可以写成 p = a;。
对数组元素a[i]的引用也可以写成*(a+1)这种形式。
当把数组名传递给一个函数时,实际上传递的是该数组第一个元素的地址。
在被调用函数中,该参数是一个局部变量,因此,数组名参数必须是一个指针,也就是一个存储地址值的变量。
因为可以用数组的名字作为指向数组第一个元素的指针。所以数组名在传递给函数时,总是被视为指针。
对于形式参数而言,声明为数组跟声明为指针是一样的。但是对变量而言,声明为数组跟声明为指针是不同的。
声明int a[10];会导致编译器预留10个整数的空间,但是声明int *a;只会导致编译器为一个指针变量分配空间。
指向数组的指针
定义方式: 类型名称(*指针名)[数组长度]
例如:
int a[5] = {1, 2, 3, 4, 5}
int (*p)[5] = &a;
定义的时候不能丢掉括号,因为[]优先级比*高,如果丢掉括号,会变成指针数组。
数组指针定义时的数组长度、元素类型必须与指向的数组给出的长度、类型相同。
递增一个指针
我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,因为数组是一个常量指针。
下面的程序递增变量指针,以便访问数组中的每一个元素:
int main () {
int var[] = {10, 100, 200};
int *ptr;
// 指针中的数组地址,注意这里是var不是&var
ptr = var;
for (int i = 0; i < MAX; i++) {
printf("存储值:var[%d] = %d\n", i, *ptr );
// 移动到下一个位置
ptr++;
}
return 0;
}
执行结果:
存储值:var[0] = 10
存储值:var[1] = 100
存储值:var[2] = 200
递减指针
#include <stdio.h>
const int MAX = 3;
int main () {
int var[] = {10, 100, 200};
int i, *ptr;
// 指针中最后一个元素的地址
ptr = &var[MAX-1];
for ( i = MAX; i > 0; i--) {
printf("存储值:var[%d] = %d\n", i-1, *ptr );
// 移动到下一个位置
ptr--;
}
return 0;
}
执行结果:
存储值:var[2] = 200
存储值:var[1] = 100
存储值:var[0] = 10
指针的指针
在C语言中,指针可以指向另一个指针的地址。指针的指针就是指针指向另一个指针的地址。
#include <stdio.h>
#include <conio.h>
void main() {
int number = 50;
int *p;//pointer to int
int **p2;//pointer to pointer
p = &number;//stores the address of number variable
p2 = &p;
printf("Address of number variable is %x \n", &number);
printf("Address of p variable is %x \n", p);
printf("Value of *p variable is %d \n", *p);
printf("Address of p2 variable is %x \n", p2);
printf("Value of **p2 variable is %d \n", **p2);
}
执行上面示例代码,得到以下结果:
Address of number variable is e737c8b8
Address of p variable is e737c8b8
Value of *p variable is 50
Address of p2 variable is e737c8b0
Value of **p2 variable is 50
- 邮箱 :charon.chui@gmail.com
- Good Luck!