目录
一、指针与数组
1.导入问题
2.深入理解数组地址(int a[] = {1,2,3,4,5};)
3.指针与数组的等价用法
4.字符串拾遗
5.指针移动组合拳:int v = *p++
6.小结
二、指针与函数
1.导入问题
2.深入函数之旅
3.函数指针(Type func(Type1 a,Type2 b))
4.函数指针参数
5.再论数组参数
6.小结
三、指针与堆空间
1.再论内存空间
2.堆空间的本质
3.预备知识——void*
4.堆空间的使用
5.堆空间的使用原则
6.小结
我们来思考一个问题,数组的本质是一片连续的内存,那么,数组的地址是什么?要如何获取?
先来了解一些事实
—使用取地址操作符 & 可以获取数组的地址
—数组名可看作一个指针,代表数组中0元素的地址
—当指针指向数组元素时,可进行指针运算(指针移动)
int a[] = {1,2,3,4,5};
int* p = a;
p = p + 1;
—&a与a在数值上相同,但是意义上不同
—&a代表数组地址,类型为:int (*)[5]
—a代表数组 0号元素地址,类型为: int*
—指向数组的指针:int (*pName)[5] = &a;
代码示例:
#include
int main()
{
int a[] = {1, 2, 3, 4, 0};
int* p = a; //&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;
}
注意:数组名并不是指针,只是代表0号元素的地址,因此可以当做指针使用
int a[] = {1,2,3, 4,5};
int* p = a;
a + i 即数组中第i个元素的地址
故 a[i] <--> *(a +i) <--> *(p +i) <--> p[i]
代码示例:
#include
int main()
{
int a[] = {1, 2, 3, 4, 5};
int* p = a;
int i = 0;
// a[i] <==> *(a+i) <==> *(p+i) <==> p[i]
while(i<3)
{
printf("a + %d = %p, a[%d] = %d\n", i, a+i, i, a[i]);
printf("p + %d = %p, p[%d] = %d\n", i, p+i, i, p[i]);
}
printf("&a = %p, &p = %p\n", &a, &p);
return 0;
}
—问:C语言中的字符串常量是什么类型
—答:char * 类型,是一种指针类型
#include
int main()
{
printf("%p\n", "D.T.Software");
printf("%p\n", "D.T.Software");
return0;
}
—解读·
指针访问操作符(*)和自增运算操作符(++)优先级相同
所以,先从p指向的内存中取值,然后p进行移动
即int v = *p++等价于
int v= *p;
p++;
代码示例:
#include
int main()
{
char* s = NULL;
printf("First = %c\n", *"D.T.Software");
s = "D.T.Software";
while( *s ) printf("%c", *s++);
printf("\n");
return 0;
}
运行结果:
—数组名可看作一个指针,代表数组中 0元素的地址
—&a 与a在数值上相同,但是意义上不同
—C语言中的字符串常量的类型是 char*
—当指针指向数组元素时,才能进行指针运算
我们来思考一个问题,函数调用时会跳转到函数体对应的代码处执行,那么,如何知道函数体代码的具体位置?
所以,我们接下来深入学习指针与函数之间的关系。
—函数的本质是一段内存中的代码(占用一片连续内存)
—函数拥有类型,函数类型由返回类型和参数类型列表组成的
例如:
函数声明 | 类型 |
int sum(int n); | int (int) |
void swap(int* pa, int* pb); | void (int*, int*) |
void g(void); | void (void) |
又一些事实
—函数名就是函数体代码的起始地址(函数入口地址)
—通过函数名调用函数,本质为指定具体地址的跳转执行
—因此,可定义指针,保存函数入口地址
—函数名即函数入口地址,类型为Type(*)(Type1,Type2)
—对于 func 的函数,&func与 func 数值相同,意义相同
—指向函数的指针:Type(*pFunc)(Type1,Type2)= func;
代码示例:
#include
int add(int a, int b)
{
return a + b;
}
int mul(int a, int b)
{
return a * b;
}
int main()
{
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));
return 0;
}
函数指针的本质还是指针(变量,保存内存地址)
可定义函数指针参数,使用相同代码实现不同功能
例如以下:
int calculate(int all, int len, int(*cal)(int, int))
{
int ret = a[0];
int i = 0;
for(i=l; i
return ret;
}
注意
—函数指针只是单纯的保存函数的入口地址
—因此,只能通过函数指针调用目标函数,不能进行指针移动(指针运算)
函数的数组形参退化为指针!因此,不包含数组实参的长度信息。
使用数组名调用时,传递的是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
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;
}
一般来说ret += *arr++;是存在语法错误的,但因为arr[]数组作为函数参数传入函数,函数的数组形参退化为指针,故可以通过对数组名加减来移动该指针对应的数组。
—函数名的本质是函数体的入口地址
—函数类型由 返回类型 和 参数类型列表 组成
—可定义指向函数的指针:Type(*pFunc)(Type1,Type2);
—函数指针只是单纯的保存函数的入口地址(不能进行指针运算)
内存区域不同,用途不同
—全局数据区:存放全局变量,静态变量
—栈空间:存放函数参数,局部变量
—堆空间:用于动态创建变量(数组)
在内存空间中,我们之前学过了全局数据区,栈空间,所以我们接下来学习新的内容——堆空间。
—备用的“内存仓库”,以字节为单位预留的可用内存
—程序可在需要时从“仓库”中申请使用内存(动态借)
—当不需要再使用申请的内存时,需要及时归还(动态还)
所以我们接下来的问题就是如何从堆空间申请内存?如何归还?
在回答这个问题之前,我们还需要学习预备知识——void*
—void 类型是基础类型,对应的指针类型为 void*
—void*是指针类型,其指针变量能够保存地址
—通过 void*指针无法获取内存中的数据(无长度信息)
代码示例:
#include
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;
}
"void* 总结
—不可使用 void* 指针直接获取内存数据。
—void*指针可与其它数据指针相互赋值。
接下来,我们开始学习堆空间的使用。
—工具箱: stdlib.h
—申请:void* malloc(unsigned bytes )
—归还:void free( void*p)
{
int* p = malloc(4);
*p = 100;
printf("号d\n",*p);
free(p);
}
代码示例:
#include
#include
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;
}
—有借有还,再借不难(杜绝只申请,不归还)
—malloc 申请内存后,应该判断是否申请成功
—free 只能释放申请到的内存,且不可多次释放
—堆空间是程序中预留且可用的内存区域
—void*指针只能能够保存地址,但无法获取内存数据
—·void*指针可与其它数据指针相互赋值
—"malloc 申请内存后,应该判断是否申请成功
—free 只能释放申请到的内存,且不可多次释放