指针
是C语言中最强大的工具之一
可以存储地址的变量称为指针(pointers)
存储在指针
中的地址通常是另一个变量
首先看看变量在内存中是如何存储和访问的
代码示例:
<!-- lang: cpp -->
//在程序中使用变量名引用这块内存
//但是一旦编译执行程序,计算机就使用内存位置的地址来引用它(其中存储了变量值)
//这条语句会分配一块内存来存储一个整数,使用`number`名称可以访问这个整数
//值5存储在这个区域中,计算机用一个地址引用这个区域
//存储这个数据的地址取决于所使用的计算机、操作系统和编译器
//这个地址在不同的系统上是不同的
int number = 5;
//指针pnumber包含另一个变量number的地址
//变量number是一个值为5的整数变量
//存储在pnumber中的地址是number第一个字节的地址
//&是寻址运算符
int *pnumber = &number;
char
类型的指针指向占有1个字节的值,long
类型值的指针指向占有4个字节的值
每个指针都合某个变量类型相关联,也只能用于指向该类型的变量
所以如果指针的类型是int,它就只能指向int类型的变量
一般给定类型的指针写成type*
,其中type
是任意给定的类型
类型名void
表示没有指定类型,所以void*
类型的指针可以包含任意类型的数据项地址void*
通常用于参数和返回值类型,任意类型的指针都可以传送为void*
类型的值,在使用它时,再转换为合适的类型
代码示例:
<!-- lang: cpp -->
int *pnumber; //它可以存储任意int类型的变量地址
int* pnumber; //与上一句语句完全相同,要确保从一开始就使用一种写法
//没有初始化的指针是非常危险的,比没有初始化的变量要危险,所以要初始化它,哪怕它不指向任何对象
//NULL是在标准库中定义的一个常量,对于指针它表示0,NULL是一个不指向任何内容的内存位置的值
int *pnumber = NULL;
//可以用相同的语句声明一般的变量和指针
//其中只有pVal是指针
double value, *pVal, fnum;
*
称为取消运算符
它用于取消对指针的引用
<!-- lang: cpp -->
int number = 15;
int *pointer = &number;
int result = 0;
//由于pointer变量含有number变量的地址,所以可以在下面表达式中计算result的值
//*pointer等于存储在pointer中的地址的值,就是存储在number中的值15
result = *pointer + 5; //计算结果:20
输出变量和指针的地址和包含的值
<!-- lang: cpp -->
#include <stdio.h>
int main(void){
int number = 0;
int *pnumber = NULL;
number = 10;
//%p输出十六进制的内存地址
printf("number的地址为:%p\n", &number);
printf("number的字节大小为:%zd 字节\n", sizeof(number));
printf("number的值为:%d\n\n", number);
//使用&寻址运算符获取变量number的地址,将该地址存储到pnumber指针中
//pnumber只能存储地址
pnumber = &number;
//输出pnumber所占的内存位置的第一个字节
//指针本身也有一个地址
//由于%说明符需要某种指针类型的值,但&pnumber的类型是指向int指针的指针
//所以使用(void*)来进行转换,使它可以包含任何类型,防止编译器发出警告
printf("pnumber的地址为:%p\n", (void*)&pnumber);
//输出所占的字节数
printf("pnumber的字节大小为:%zd 字节\n", sizeof(pnumber));
//pnumber所存储的内存地址,就是&number
printf("pnumber保存的地址值为:%p\n", pnumber);
//在pnumber所含的地址中存储的值,就是存储在number中的值,使用*来取消对指针的引用
printf("指针的值为: %d\n", *pnumber);
return 0;
}
输出结果:
number的地址为:0x7fff5420dbd8
number的字节大小为:4 字节
number的值为:10pnumber的地址为:0x7fff5420dbd0
pnumber的字节大小为:8 字节
pnumber保存的地址值为:0x7fff5420dbd8
指针的值为: 10
使用指针修改存储在其他变量中的值,并进行运算:
<!-- lang: cpp -->
int main(void){
long num1 = 0L;
long num2 = 0L;
long *pnum = NULL;
pnum = &num1;
//下面的语句修改了num1的值为2L
*pnum = 2L;
++num2;
//下面等于num2 = num2 + *pnum
//num2 = 1L + 2L = 3L
num2 += *pnum;
//把指针存储的地址改为num2的地址
pnum = &num2;
//下面这句自增语句,修改了num2的值,现在为4L
++*pnum;
//计算表达式*pnum + num2 = 4L + 4L = 8L
printf("num1=%ld num2=%ld *pnum=%ld *pnum+num2=%ld\n",num1,num2,*pnum,*pnum+num2);
return 0;
}
接受输入时使用指针:
<!-- lang: cpp -->
#include <stdio.h>
int main(void) {
int value = 0;
//把value的地址存储到pvalue中
int *pvalue = &value;
//使用指针value将value的地址传递给scant()
//pvalue和&value是相同的,所以可以用任何一个
printf("You entered %d.\n", value);
scanf(" %d", pvalue);
printf("You entered %d.\n", value);
return 0;
}
可以使用
const
关键字指定常量指针,该指针指向的值不能改变
虽然指针在设置为常量后,不能改变其值,但可以改变指针指向的变量的值
代码示例:
<!-- lang: cpp -->
long value = 999L;
const long *pvalue = &value;
//下面这句是错的,因为pvalue指针是常量,无法修改
//*pvalue = 888L;
//但可以通过修改指针所指向的变量的值
value = 777L;
//也可以改变指针所指向的变量地址,就是不能通过指针改变常量指针的值
long number = 777L;
pvalue = &number;
常量指针可以使指针中存储的地址不能改变
代码示例:
<!-- lang: cpp -->
int count = 43;
//这句语句声明并初始化了pcount,指定该指针存储的地址不能改变
int *const pcount = &count;
//下面的语句是错误的
//int item = 34;
//pcount = &item;
//但使用pcount,仍可以改变count指向的值
*pcount = 345;
//还可以创建一个常量指针,它指向一个常量值
int item = 25;
//pitem是一个指向常量的常量指针,所以所有的信息都是固定不变的
//不能改变存储在pitem中的地址,也不能使用pitem改变它指向的内容
//但仍可以直接修改item的值,如果希望所有的信息都固定不变,可以把item指定为const
const int *const pitem = &item;
数组时相同类型的对象集合,可以用一个名称引用,例如:数组scores[50]
指针式一个变量,它的值是给定类型的另一个变量或常量的地址
使用指针可以在不同的时间访问不同的变量,只要它们的类型相同即可
数组与指针的区别在于:数组可以改变指针包含的地址,但不能改变数组名称引用的地址
通过下面的示例就可以看出区别所在
代码示例1:
<!-- lang: cpp -->
/*
&multiple[0]会产生和multiple表达式相同的值
因为multiple等于数组第一个字节的地址
&multiple[0]等于数组第一个元素的字节
所以它们会相同
*/
int main(){
char multiple[] = "My string";
char *p = &multiple[0];
printf("The address of the first array element:%p\n", p);
p = multiple;
printf("The address obtained from the array name:%p\n", multiple);
return 0;
}
代码示例2:
<!-- lang: cpp -->
#include <stdio.h>
#include <string.h>
/*
输出:
multiple[0] = a * (p+0) = a &multiple[0] = 0x7fff5ebdebc3 p+0 = 0x7fff5ebdebc3
multiple[1] = * (p+1) = &multiple[1] = 0x7fff5ebdebc4 p+1 = 0x7fff5ebdebc4
multiple[2] = s * (p+2) = s &multiple[2] = 0x7fff5ebdebc5 p+2 = 0x7fff5ebdebc5
multiple[3] = t * (p+3) = t &multiple[3] = 0x7fff5ebdebc6 p+3 = 0x7fff5ebdebc6
multiple[4] = r * (p+4) = r &multiple[4] = 0x7fff5ebdebc7 p+4 = 0x7fff5ebdebc7
multiple[5] = i * (p+5) = i &multiple[5] = 0x7fff5ebdebc8 p+5 = 0x7fff5ebdebc8
multiple[6] = n * (p+6) = n &multiple[6] = 0x7fff5ebdebc9 p+6 = 0x7fff5ebdebc9
multiple[7] = g * (p+7) = g &multiple[7] = 0x7fff5ebdebca p+7 = 0x7fff5ebdebca
p+n就等于multiple+n,所以multiple[n]与*(multiple+n)是相同的
对于元素占用一个字节的数组来说,上面表达式成立
*(p+n)是给p中的地址加上整数n,在对得到的地址取消引用,就可以得到对应的元素值
*/
int main(void){
char multiple[] = "a string";
char *p = multiple;
for (int i = 0; i < strnlen(multiple,sizeof(multiple)); ++i) {
printf("multiple[%d] = %c * (p+%d) = %c &multiple[%d] = %p p+%d = %p\n",
i, multiple[i], i, *(p+i), i, &multiple[i], i, p+i);
}
return 0;
}
代码示例3:
<!-- lang: cpp -->
/*
输出:
address p+0 (&multiple[0]):140734747675568 *(p+0) value: 15
address p+1 (&multiple[1]):140734747675576 *(p+1) value: 25
address p+2 (&multiple[2]):140734747675584 *(p+2) value: 35
address p+3 (&multiple[3]):140734747675592 *(p+3) value: 45
Type long occupies: 8 bytes
这次得到了完全不同的结果
指针p设置为multiple的地址
而multiple是long类型的数组
该指针最初包含数组中第一个字节的地址,也就是元素multiple[0]的第一个字节
这次地址转换为unsigned long long后,用%llu转换说明符显示,所以他们是十进制的
可以看到,每个地址的值都比上一个地址大了8
这说明,给地址加1时,就表示要访问该类型的下一个变量
由于将long类型强制转换为了无符号的longlong类型,所以占8个字节,就会给地址加8
*/
int main(void){
long multiple[] = {15L, 25L, 35L, 45L};
long *p = multiple;
for (int i = 0; i < sizeof(multiple) / sizeof(multiple[0]); ++i) {
printf("address p+%d (&multiple[%d]):%llu *(p+%d) value: %ld\n",
i, i, (unsigned long long)(p+i), i, *(p+i));
}
printf("\n Type long occupies: %d bytes\n", (int)sizeof(long));
return 0;
}