一、指针
1.1 一种特殊的变量--指针
指针保存的值是内存中的 "地址"
内存地址:内存是计算机中的存储部件,每个存储单元有固定唯一的编号
内存中存储单元的编号即内存地址。
程序中的一切元素都存储在内存中,因此,可通过内存地址访问程序元素。
1.2 获取地址
c语言中通过 & 操作符,获取程序元素的地址
&(取地址操作符)可获取变量,数组,函数的起始地址
内存地址的本质是 一个无符号数(4字节或者8字节)
int main()
{
int var = 0;
printf("var_value = %d\n", var); // var_value = 0;
printf("var_address = %p\n", &var); // var_address = var的地址
return 0;
}
注意:只有通过 内存地址 + 长度 才能确定一个变量中保存的值。
1.3 指针定义语法:type* pointer
type -- 数据类型,决定可访问内存时的长度范围
* -- 标志,意味着定义一个指针变量
pointer -- 指 变量名,遵循c语言命名规则
【 int* pint : 定义一个指针变量,名为pint, 类型是int(4个字节)
,所指向的变量类型是 int型】
int main()
{
char* pchar; // 1字节
short* pshort; // 2字节
int* pint; // 4字节
float* pfloat; // 4字节
double* pdouble; // 8字节
return 0;
}
1.4 指针内存访问: *pointer (访问内存中的变量)
指针访问操作符(*)作用于指针变量,即可访问内存数据
指针的类型 决定 通过 地址 访问内存时 的 长度范围
指针的类型统一占用4个字节或者8个字节
即:sizeof(type*) == 4或者 sizeof(type*) == 8;
***** int* 这个整体可看作是一个指针类型
实例:
int main()
{
int var = 0; //普通变量
int* pVar = null; // 指针变量
int another = o;
printf("1.var = %d\n", var); //1.var = 0;
printf("1.pVar = %p\n", pVar); //1.pVar = 0000 0000, null的地址;
pVar = &var; //使用指针保存变量var的地址
*pVar = 100; //*pVar等价于var, var = 100;
printf("2.var = %d\n", var); //2.var = 100;
printf("2.pVar = %p\n", pVar); //2.pVar = var的地址;
pVar = &another; // 改变指针pVar的指向
*pVar = 200; //*pVar等价于var, var = 100;
printf("3.another = %d\n", another); //3.another = 200;
printf("3.pVar = %p\n", pVar); //2.pVar = another的地址;
printf("4.add ==》 %d\n", var + another + *pVar); //4.add ==》 500;
return 0;
}
总结:
指针是c语言中的变量(本质是容器)
指针专门用于保存程序元素的内存地址
可使用 ” *操作符 “ 通过指针访问程序元素本身
指针也有类型,指针类型由 数据类型 + * 构成
二、深入理解指针与地址
2.1、函数调用、传参
#include<stdio.h>
void func(int* p)
{
*p = 120;
}
int main()
{
int var = 0;
printf("var = %d\n", var); //0
func( &var );
printf("var = %d\n", var); //120
return 0;
}
2.2、编写函数交换两个变量的值---传值调用
#include<stdio.h>
void func(int* a, int* b)
{
int z = 0;
z = *a;
*a = *b
*b = z;
}
int main()
{
int a = 1, b = 2;
printf("1.a = %d, b = %d\n", a, b);
func( &a, &b );
printf("2.a = %d, b = %d\n", a, b);
return 0;
}
总结: 1. 可以利用指针从函数中 “返回” 多个值 (return 只能返回一个值)
2.两个变量值的相互交换,其实就是指针(int* a = &b,被调函数的形参要是一个指针,这个指针在主函数中的实参用&a(地址)来表示。)
把指针定义成被调函数的参数,然后在主函数调用时传递变量的地址就ok,其实就是int* a = &b;
2.3 连加、 连减 典型案例
int calculate(int n, longlong* pa, longlong* 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()
{
int a = 5;
longlong ar = 0;
longlong mr = 0;
calculate(a, &ar, &mr);
printf("ar= %d, mr= %d\n", ar, mr);
return 0;
}
总结: 指针式变量。 因此赋值时必须保证类型相同
指针变量保存的地址必须是有效地址
通过指针参数,能够实现函数交换变量的值,能够从函数中 ‘返回‘多个值
三、 指针与数组
3.1、 数组的本质,是一片连续的内存。
数组的地址,就是内存的起始位置
使用取地址操作符 & 获取数组的地址
数组名可看作一个指针,代表数组中0元素的地址 (数组中,数组名代表了一个地址,是0号元素的地址)
int a[] = {1, 2, 3, 4, 5}; a是数组名,也是指针,代表数组a的首地址。
&a表示整个数组的地址
当指针指向数组元素时,可进行指针运算(指针移动)
int a[] = {1, 2, 3, 4, 5};
int* p = a; //指针变量p保存了数组a的首地址
p = p + 1; // p加1 表示的是指针变量p所指向的数组a的首地址 + 1
示例:
#include<stdio.h>
int main()
{
int a[] = { 1, 2, 3, 4, 5 };
int* p = a; // a <==>&a[0]
p++; // 指针移动 也就是指向的数组的首地址移动,因为保存的是数组首地址
*p = 100;
printf("a[1] = %d\n,*p = %d\n", a[1], *p); //a[1] = 100; *p = 100;
printf("%p\n%p\n", &a[0], a); // 地址相同
printf("a[0] = %d\n", a[0]); //a[0] = 1;
printf("a[2] = %d\n", a[2]); //a[2] = 3;
return 0;
}
!!!!!
深入解释数组地址(int a[] = {1, 2, 3, 4, 5})
&a 与 a在数值上相同,但是意义上不同
&a 代表数组地址,类型为:int (*)[5] <<===>> 指针类型,指向数组int[5]
a代表数组0号元素地址,类型是int* // 指向数组的指针: int(*pName)[5] = &a;
#include<stdio.h>
int main()
{
int a[] = { 1, 2, 3, 4, 0 };
int(*pa)[5] = &a; //指向数组的指针
int* p = a; // 指向数组元素首地址(0号元素的指针)
printf("%p\n%p\n", pa, p); // 地址相同
//pa = p;// 错误,会报警,因为类型不一致
while (*p)
{
printf("%d\n", *p);
p++; // 指针移动
}
return 0;
}
//000000C90F97F5D8
//000000C90F97F5D8
//1
//2
//3
//4
注意!!!
数组名不是指针,只是代表了0好元素的地址,因此可以当作指针使用
3.2、指针与数组的等价用法
int a[] = {1, 2, 3, 4, 5};
int* p = a; // p++指 地址移动1
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;
for (i = 0; i < 5; i++)
{
printf("%d, %d\n", a[i], *(a + i));
}
for (i = 0; i < 5; i++)
{
printf("%d, %d\n", a[i], p[i]);
}
for (i = 0; i < 5; i++)
{
printf("%d, %d\n", p[i], *(p + i));
}
return 0;
}
总结: a[i] = *(a+i) = *(p+i) = p[i]
3.3 字符串
c语言中的字符串常量(“swwqdef”)是什么类型? char* 类型,一种指针类型
字符串常量本质是一个字符数组。
char s[] = {'s', 'w', 'w', 'q', 'd', 'e', 'f'}; //char*
%p 》 地址,指针的值 //指针的值是地址
3.4 指针移动组合拳:int v = *p++;
v先与*p结合,后+1;
原因是:指针访问操作符(*)与自增运算操作符(++)优先级相同
所以,先从p指向的内存中取值,然后p进行移动
等价于:int v = *p;后 p++;
! 当指针指向数组元素时,才可以进行指针运算
扩展:
int main()
{
char* s = null;
printf("first = %c\n", *"D.T.Software"); // D
S = "D.T.Software";
while(*s) printf("%c", *s++);
return 0;
}
指针与数组总结: 数组名可以看作指针,代表数组中的0号元素地址
&a与a在数值上相同,但是意义不同
c语言中的字符串常量的类型是char*
当指针指向数组元素时,才可以进行指针运算
四、指针与函数
4.1.函数的本质是一段内存中的代码(占用一片连续的内存,数组也是 )
函数拥有类型,函数类型由 返回类型 + 参数类型列表 组成
函数名 代表函数体代码的起始地址(函数入口地址)
通过函数名调用函数,本质为指定具体地址的跳转执行
因此可定义指针,保存函数入口地址
函数指针( type* func(type1a, type2b) )
函数名即函数入口地址,类型为type(*)(type1, type2)
对于func的函数,&func与func数值相同,意义相同
指向函数的指针:type(*pfunc)(type1, type2) = add
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int mul(int a, int b)
{
return a * b;
}
int main(void)
{
int (*pfunc)(int, int) = 0;
pfunc = add;
printf("add = %d\n", pfunc(1, 2));
printf("add = %d\n", pfunc(5, 6));
printf("add = %d\n", (*pfunc)(1, 2));
printf("add = %d\n", (*pfunc)(5, 6));
printf("\n");
pfunc = mul;
printf("mul = %d\n", pfunc(1, 2));
printf("mul = %d\n", pfunc(5, 6));
printf("mul = %d\n", (*pfunc)(1, 2));
printf("mul = %d\n", (*pfunc)(5, 6));
return 0;
}
4.2 函数指针参数
函数指针的本质还是指针(变量,保存内存地址)
可定义函数指针参数, 使用相同代码实现不同功能
如下代码:
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;
}
完整成程序实例如下:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int cheng(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(void)
{
int q[] = {1, 2, 3, 4};
printf("1+...+4 = %d\n", calculate(q, 4, add));
printf("1*...*4 = %d\n", calculate(q, 4, cheng));
return 0;
}
注意:函数指针只是单纯的保存了函数的入口地址,因此,只能通过函数指针调用目标函数
不能进行指针移动(指针运算)
4.4 再论数组参数
函数的数组形参退化为指针!!因此,不包含数组的形参长度信息。使用数组名调用时,传递的是0号元素的地址。
void func(int a[]) <--> void func (int* a)
<--> void func (int a[1])
<--> void func (int a[10])
<--> void func (int a[100])
.......都是void func (int* a)
实例:数组作为形参被调用进行运算
#include <stdio.h>
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;
}
五、指针与推空间
5.1 内存区域不同,用途不同
》全局数据区:存放全局变量,静态变量
》栈空间:存放函数参数,局部变量
*》堆空间:用于动态创建变量(数组)
堆空间的本质:1.备用的 内存仓库 ,以字节为单位预留的可用内存
2.程序可在需要时从 仓库 中申请使用内存(动态借)
3.当不需要再使用申请的内存时,需要及时归还(动态还)
如何从堆空间申请内存?如何归还?
预备知识: void*
void*类型是基础类型,对应的指针类型是void*
void*是指针类型,其指针变量能够保存地址
通过void*指针无法获取内存中的数据(无长度信息)(长度+类型)
#include<stdio.h>
int main()
{
int i = 2;
char j = 1;
float e = 0.3f;
double d = 3.0;
void* p = NULL;
double* pb = NULL;
p = &i;
p = &j;
p = &e;
p = &d; //void* 类型的指针可以保存任意类型的地址
printf("%f\n", *p); //报警!! void* 类型的指针无法访问内存中的数据,因无长度信息。
pb = p;// void*类型的数据变量可以直接合法的赋值给其他具体数据类型的指针变量!!
printf("%f\n", *pb);
return 0;
}
// void* 类型的指针无法访问内存中的数据但可以通过其他指针的相互转换可以实现
//如创建一个double* pb 指针,将指针p赋值给pb
这里我用vs提示是不行的,需要后期进行验证一下!!!!!
5.2 堆空间的使用
工具箱:stdlib.h
申请:void* malloc(unsigned bytes)
归还:void free(void* p)
int* p = malloc(4); //从堆空间申请4个字节当作 int类型的变量使用
*p = 100;
printf("%d\n", *p);
free(p);
详细实例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = malloc(4); //从堆空间申请4个字节当作 int类型的变量使用
if(p != NULL)
{
*p = 100;
printf("%d\n", *p);
free(p); // 释放 申请后就要释放!!
}
return 0;
}
应用:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = malloc(4 * sizeof(int)); //从堆空间申请4*4个字节(4个int变量的数组)
int i = 0;
if(p != NULL)
{
for(i=0; i<4; i++)
{
p[i] = i * 10; //P[i] 表示申请的4个int类型的数组
}
for(i=0; i<4; i++)
{
printf("%d\n", *p);
}
free(p); // 释放 申请后就要释放!!
}
return 0;
}
堆空间使用原则:
1.有借有还,再借不难
2.malloc申请内存后,应该判断是否申请成功
3.free只能释放申请到的内存,且不可多次释放
小结:
1.堆空间是程序中预留且可用的内存区域
2.void* 指针只能够保存地址, 但无法获取内存数据
3.void*指针可与其他数据指针相互赋值
4.malloc申请内存后,应该判断是否申请成功
5.free只能释放申请到的内存,且不可多次释放
六、 内存单元地址强制转换为指针! *(unsigned int*)(0x1001000c) = 0xffff;
将内存器的地址强制转换成指针 涉及到将内存地址直接赋值给一个指针变量,以便通过该指针访问或操作该内存地址的内容。这种转换通常通过类型转换操作符(如(int*)
、(void*)
等)实现,其中*
表示指针类型。这种转换允许程序员直接操作内存,尽管这通常需要特殊的权限和谨慎操作,以避免数据损坏或系统崩溃。
在C语言中,这种转换可以通过以下方式进行:
-
初始化指针:通过使用
&
操作符获取变量的地址,并将其赋值给指针变量。例如,int a = 10; int* p = &a;
这里,p
被初始化为指向变量a
的内存地址。 -
强制类型转换:如果需要将一个已知地址强制转换为特定类型的指针,可以使用类型转换操作符。例如,
(int*)0x12345678
将地址0x12345678
强制转换为指向整型的指针。 -
通过指针访问内存:一旦指针被赋予一个地址,就可以通过解引用该指针(使用
*
操作符)来读取或修改该地址处的数据。例如,int value = *p;
将从指针p
指向的内存地址读取一个整型值,并将其存储在变量value
中。 -
注意事项:这种操作需要谨慎进行,因为直接操作内存可能导致数据损坏或系统不稳定。确保只访问和修改已知且安全的内存地址。
-
函数指针的特殊情况:在某些情况下,如需要将一个绝对地址转换为函数指针以实现跳转到该地址执行代码,可以使用类似
(void (*)())address
的语法进行转换和调用。例如,((void (*)())0x100000)();
将地址0x100000
强制转换为函数指针并调用之。
总的来说,将内存器的地址强制转换成指针是一种强大的技术,允许程序员直接控制和操作内存。然而,这种操作需要谨慎进行,以避免潜在的风险12。
#include <stdlib.h>
int main()
{
int a = 100;
int *p = NULL;
p = &a;
printf("p=%d\n", *p); // *P = 100
printf("a=%p\n", &a); // a的地址=0x7ffdf6f6ff84
printf("p=%p\n", p); // 指针p的地址=0x7ffdf6f6ff84
printf("\n");
*p = 11; // 指针操作“*”间接访问操作符
printf("p=%d\n", *p); // *P = 11
printf("a=%d\n", a); // a = 11
printf("a=%d\n", *&a); // *&a = 11 *&a <==> *(int*)0x7ffdf6f6ff84 (变量a是int类型)
*&a = 15;
printf("a=%d\n", a); // a = 15
return(0);
}