时间:2022-07-07 09:49:48 | 栏目:C代码 | 点击:次
指针是C语言中的变量
需要弄清楚的事实
内存示例
获取地址
下面看一个简单的例子:
#include<stdio.h> int main() { int var = 0; printf("var value = %d\n", var); printf("var address = %p\n", &var); return 0; }
下面为输出结果:
注意事项
指针定义语法:
type *point;
例如:
int main() { char* pChar; short* pShort; int* pInt; float* pFloat; double* pDouble; return 0; }
指针内存访问:
* pointer
即:sizeof(type*) == 4或 sizeof(type*) == 8
下面看一段代码,感受一下:
#include <stdio.h> int main() { int var = 0; int another = 0; int* pVar = NULL; printf("1. var = %d\n", var); printf("1. pVar = %p\n", pVar); pVar = &var; // 使用指针保存变量的地址 *pVar = 100; // *pVar 等价于 var , var = 100; printf("2. var = %d\n", var); printf("2. pVar = %p\n", pVar); pVar = &another; // 改变了 pVar 的指向,使得 pVar 保存 another 的地址 *pVar = 1000; // another = 1000; printf("3. another = %d\n", another); printf("3. pVar = %p\n", pVar); printf("4. add ==> %d\n", var + another + *pVar); // 100 + 1000 + 1000 ==> 2100 return 0; }
下面为输出结果:
注意 NULL 地址为 00000000
小结
灵魂三问
初学指针的军规
注意:指针保存的地址必须是有效地址
下面看一段代码:
#include <stdio.h> int main() { int i = 10; float f = 10; int* pi = &f; // WARNING float* pf = &f; // OK printf("pi = %p, pf = %p\n", pi, pf); printf("*pi = %d, *pf = %f\n", *pi, *pf); pi = i; // WARNING *pi = 110; // OOPS printf("pi = %p, *pi = %d\n", pi, *pi); return 0; }
下面为输出结果:
这个程序犯了两个错误:
1、将不同类型的指针相互赋值,虽然 int 类型的指针变量保存的地址是对的,但是其所保存的值是错的。
2、 将普通数值当作地址赋值给指针,这会导致严重的错误,不能正确输出
编写函数交换两个变量的值
看下面的代码:
#include <stdio.h> void func(int* p) { *p = 100; // 修改内存中 4 字节的数据,即:修改一个整型变量的值 } void swap(int* pa, int* pb) { int t = 0; t = *pa; *pa = *pb; *pb = t; } int main() { int var = 0; int a = 1, b = 2; printf("1. var = %d\n", var); func( &var ); printf("2. var = %d\n", var); printf("3. a = %d, b = %d\n", a, b); swap(&a, &b); printf("4. a = %d, b = %d\n", a, b); return 0; }
下面为输出结果:
小结论
可以利用指针从函数中“返回”多个值 (return只能返回一个值)!!
下面看一段代码:
#include <stdio.h> int calculate(int n, long long* pa, long long* pm) { int ret = 1; if( (1 <= n) && (n <= 20) ) { int i = 0; *pa = 0; *pm = 1; for(i=1; i<=n; i++) { *pa = *pa + i; *pm = *pm * i; } } else { ret = 0; } return ret; } int main() { long long ar = 0; long long mr = 0; if( calculate(5, &ar, &mr) ) printf("ar = %lld, mr = %lld\n", ar, mr); return 0; }
下面为输出结果:
这段代码中的子函数通过指针,计算了1加到5以及1乘到5的值,这就间接地通过指针从子函数“返回”多个值
小结
指针是变量,因此赋值时必须保证类型相同
指针变量保存的地址必须是有效地址
通过指针参数
问题
一些事实
深入理解数组地址( int a[]= {1, 2, 3, 4, 5}; )
下面看一段代码:
#include <stdio.h> int main() { int a[] = {1, 2, 3, 4, 0}; int* p = a; // a 的类型为 int*, &a[0] ==> int* int (*pa) [5] = &a; printf("%p, %p\n", p, a); p++; *p = 100; // a[1] = 100; printf("%d, %d\n", *p, a[1]); printf("%p, %p\n", &a, a); p = pa; // WARNING !!!! p = a; while( *p ) { printf("%d\n", *p); p++; } return 0; }
下面为运行结果:
需要注意的是,p 和 pa不是一个指针类型,所以令 p = pa 这种做法是不正确的。
注意
指针与数组的等价用法
假如:
int a[ ] = {1, 2,3, 4,5}
int* p = a;
则以下等价:
a[i] <--> *(a + i) <--> *(p + i) <--> p[i]
下面看一段代码,加深理解:
#include <stdio.h> int main() { int a[] = {1, 2, 3, 4, 5}; int* p = a; int i = 0; // a[i] <==> *(a+i) <==> *(p+i) <==> p[i] for(i=0; i<5; i++) { printf("%d, %d\n", a[i], *(a + i)); } printf("\n"); for(i=0; i<5; i++) { printf("%d, %d\n", a[i], p[i]); } printf("\n"); for(i=0; i<5; i++) { printf("%d, %d\n", p[i], *(p + i)); } printf("\n"); printf("a = %p, p = %p\n", a, p); printf("&a = %p, &p = %p\n", &a, &p); return 0; }
下面为输出结果:
这里可以看到 a和 p的地址不同,因为它们是两个不同的指针变量。
字符串拾遗
指针移动组合拳:
int v = *p++;
解读:
指针访问操作符(*)和自增运算操作符(++) 优先级相同
所以,先从p指向的内存中取值,然后p进行移动
等价于:
int v = *p;
p++;
下面看一段代码,体会一下:
#include <stdio.h> int main() { int a[] = {1, 2, 3}; int* p = a; int v = *p++; char* s = NULL; printf("%p\n", "D.T.Software"); printf("%p\n", "D.T.Software"); printf("v = %d, *p = %d\n", v, *p); printf("First = %c\n", *"D.T.Software"); s = "D.T.Software"; while( *s ) printf("%c", *s++); printf("\n"); return 0; }
下面为输出结果:
因为D.T.Software 在全局数据区的起始地址一样,所以两次打印出来的地址一样。
小结
问题
深入函数之旅
函数申明 | 类型 |
int sum(int n); | int (int) |
void swap(int* pa, int* pb) | void (int*, int*) |
void g(void); | void (void) |
函数的一些事实
函数指针( Type func (Type1 a,Type2 b))
函数指针参数
注意
函数指针只是单纯的保存函数的入口地址
因此
下面看一段代码,理解一下:
#include <stdio.h> int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } int calculate(int a[], int len, int(*cal)(int, int)) { int ret = a[0]; int i = 0; for(i=1; i<len; i++) { ret = cal(ret, a[i]); } return ret; } int main() { int a[] = {1, 2, 3, 4, 5}; int (*pFunc) (int, int) = NULL; pFunc = add; printf("%d\n", pFunc(1, 2)); printf("%d\n", (*pFunc)(3, 4)); pFunc = &mul; printf("%d\n", pFunc(5, 6)); printf("%d\n", (*pFunc)(7, 8)); printf("1 + ... + 5 = %d\n", calculate(a, 5, add)); printf("1 * ... * 5 = %d\n", calculate(a, 5, mul)); return 0; }
下面为输出结果:
这里注意,只有调用的时候,才能确定 calculate() 子函数中的 cal 是什么函数。
再论数组参数
函数的数组形参退化为指针!因此,不包含数组实参的长度信息。使用数组名调用时,传递的是0号元素的地址。
void func(int a[ ]) <--> void func(int* a)
<--> void func (int a[1])
<--> void func (int a[10)
<--> void func(int a[100)
下面看一段代码:
#include <stdio.h> int demo(int arr[], int len) // int demo(int* arr, int len) { int ret = 0; int i = 0; printf("demo: sizeof(arr) = %d\n", sizeof(arr)); while( i < len ) { ret += *arr++; i++; } return ret; } int main() { int a[] = {1, 2, 3, 4, 5}; // int v = *a++; printf("return value: %d\n", demo(a, 5)); return 0; }
下面为输出结果:
定义的形参arr[]可以进行 *arr++ 的操作,这就说明函数的数组形参退化为指针,因为数组不可以进行 ++ 的运算。
小结
再论内存空间
内存区域不同,用途不同
堆空间的本质
问题
预备知识-- void*
void* 总结
下面看一段代码:
#include <stdio.h> int main() { char c = 0; int i = 0; float f = 2.0f; double d = 3.0; void* p = NULL; double* pd = NULL; int* pi = NULL; /* void* 指针可以保存任意类型的地址 */ p = &c; p = &i; p = &f; p = &d; printf("%p\n", p); // void* 类型的指针无法访问内存中的数据 // printf("%f\n", *p); /* void* 类型的变量可以直接合法的赋值给其他具体数据类型的指针变量 */ pd = p; pi = p; // void* 是例外,其他指针类型的变量不能相互赋值 // pd = pi; return 0; }
下面为输出结果:
注意几个问题:
1.void* 指针可以保存任意类型的地址
2.void* 类型的指针无法访问内存中的数据
3.void* 类型的变量可以直接合法的赋值给其他具体数据类型的指针变量
4.void* 是例外,其他指针类型的变量不能相互赋值
堆空间的使用
堆空间的使用原则
下面看一段代码感受一下:
#include <stdio.h> #include <stdlib.h> int main() { int* p = malloc(4); // 从堆空间申请 4 个字节当作 int 类型的变量使用 if( p != NULL ) // 如果申请失败 p 为 0 ,即:空值 { *p = 100; printf("%d\n", *p); free(p); } p = malloc(4 * sizeof(int)); if( p != NULL ) { int i = 0; for(i=0; i<4; i++) { p[i] = i * 10; } for(i=0; i<4; i++) { printf("%d\n", p[i]); } free(p); } return 0; }
下面为输出结果:
小结
多级指针
如:
Type v;
Type *pv = &v;
Type** ppv = &pv;
type*** pppv = &ppv;
下面看一段代码:
#include <stdio.h> #include <stdlib.h> int main() { int a = 0; int b = 1; int* p = &a; int** pp = &p; **pp = 2; // a = 2; *pp = &b; // p = &b; *p = 3; // b = 3; printf("a = %d, b = %d\n", a, b); return 0; }
下面为输出结果:
*pp 就是取 pp 里面的内容,而 pp 里面存的内容是 p 的地址,所以 *pp 就相当于p 的内容,而 p 的内容就是 a 的地址,所以说 **p 就相当于 a,**p = 2 也就是把 2 赋值给 a,*pp = &b 即为 p = &b,所以 *p = 3,就是把 3 赋值给 b。
下面再看一段代码:
#include <stdio.h> #include <stdlib.h> int getDouble(double** pp, unsigned n) { int ret = 0; double* pd = malloc(sizeof(double) * n); if( pd != NULL ) { printf("pd = %p\n", pd); *pp = pd; ret = 1; } return ret; } int main() { double* p = NULL; if( getDouble(&p, 5) ) { printf("p = %p\n", p); free(p); } return 0; }
下面为输出结果:
这里特别注意:函数外的一个一级指针指向了这里申请的堆空间
再论二维数组
二维数组的本质是一维数组 ,即:数组中的元素是一维数组!!
因此:
int a[2][2];
a 就是 &a[0]
a[0] 的类型是 int[2]
可知 a 的类型是 int (*)[2]
下面看一段代码:
#include <stdio.h> #include <stdlib.h> int main() { int b[2][2] = {{1, 2}, {3, 4}}; int (*pnb) [2] = b; // b 的类型是 int(*)[2] *pnb[1] = 30; printf("b[0][0] = %d\n", b[0][0]); printf("b[0][1] = %d\n", b[0][1]); printf("b[1][0] = %d\n", b[1][0]); printf("b[1][1] = %d\n", b[1][1]); return 0; }
下面为输出结果:
pnb[0]是[1,2],pnb[1]经过赋值后是[30,4],所以*(pnb[1])就是取该数组所代表的第0个元素,也就是30。
下面再看一个代码:
#include <stdio.h> #include <stdlib.h> int* func() { int var = 100; return &var; } int main() { int* p = func(); // OOPS!!!! // p 指向了不合法的地址,这个地址处没有变量存在 // p 是一个野指针,保存不合法地址的指针都是野指针 printf("*p = %d\n", *p); *p = 200; // 改变 func 函数中局部变量 var 的值,是不是非常奇怪??? printf("*p = %d\n", *p); return 0; }
这段代码是有问题的, func() 函数执行后, var 这个变量就会被销毁,所以 p 指向了一个不合法的地址。
小结