指针
一.指针的概念
一切都是内存地址
C语言用变量来存储数据,用函数来定义一段可以重复使用的代码,它们最终都要放到内存中才能供 CPU 使用。
数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是数据还是代码。当程序被加载到内存后,
操作系统会给不同的内存块指定不同的权限,拥有读取和执行权限的内存块就是代码,而拥有读取和写入权限(也可能只有读取权限)的内存块就是数据。
CPU 只能通过地址来取得内存中的代码和数据,程序在执行过程中会告知 CPU 要执行的代码以及要读写的数据的地址。如果程序不小心出错,
或者开发者有意为之,在 CPU 要写入数据时给它一个代码区域的地址,就会发生内存访问错误。
这种内存访问错误会被硬件和操作系统拦截,强制程序崩溃,程序员没有挽救的机会。
CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,
它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
总而言之,指针就是一种数据类型,里面装的是地址。
二.指针的定义:
- 声明一个指针
数据类型 * 指针名
例如 int *p;
定义了一个名为p的指针,指向int数据.
在C语言中 ‘ * ’ 号,有三个含义:
1.数学里的加减乘除,乘。
2.指针在定义时,作为标记,标记该变量为指针变量。
3.指针在非定义时,加上指针变量名,则是解引用。也就是引用我们地址里保存的常量(数据)。 *p
&在C语言中的三个作用
1.取地址
2.与运算
3.逻辑与
//4.引用(C++)
地址打印格式为 : %p
p = 'a'; 这个是指向某个变量或常量的地址;
*p = ‘A’; 是把某个变量或常量的地址保存在指针p中
- 解引用的使用
- 指针与数组的关系
在某种程度上,我们的指针是可以跟数组互换的.例如:
#include
#include
int main()
{
//定义指针
int c = 4;
int *p = &c;
char stu[15]="hello world";
char* s = stu;//定义一个char *指针s指向stu数组
printf("stu[0] is %pn",&stu[0]);//数组的首元素的地址
printf("&stu is %pn",&stu);//整个数组的地址
printf("%sn",s);//s指向数组,访问数组从首地址开始的元素,
printf("%sn",stu);//stu在表达式当中代表数组首地址
printf("%cn",stu[0]);//stu数组中的首元素
printf("%cn",s[0]);//s指针的首地址的元素
printf("%cn",s[9]);
int b;//b存储的常量
printf("int *p:%pn",p);
printf("int *p: %dn",*p);
printf("int b: %pn",&b);
return 0;
}
- 指针的大小
通过sizeof我们去计算过 基本数据类型 int char double
指针用sizeof再次计算 int * char * double *
在32位机上面指针类型都是4字节
在64位机上所有的指针的都是8字节;
- 指针的加减运算
int main(void)
{
int buf[10] = {1,2,3,4,5,6,7,8};
int *p = buf;
printf("p存放的地址为: %pn",p);
p = p + 1;
printf("p存放的地址为: %pn",p);
char buff[10] = "hello";
char *q = &buff[4];
printf("q存放的地址为: %pn",q);
q = q - 1;
printf("q存放的地址为: %pn",q);
return 0;
}
总结: 指针加法就是加一个数据类型的大小,指针减法就是减一个数据类型的大小;
int main(void)
{
int buf[10] = {10,20,30,40,50,60,70,80};
int *p = buf; //定义指针p指向数组buf
//注意运算符优先级;
printf("p 数据为: %dn",*(p+1));
printf("p 数据为: %dn",*p+1);
return 0;
}
#注意点
1.星号的优先级比加号的优先级高;
2.*(p + 1) 由于括号的原因,先执行的p+1也就是数据类型+1;然后再对p+1进行解引用
3.*p + 1, 由于*号的优先级比+号要高,所以先执行*p,再执行+1的运算.
- 使用指针访问字符串
#includeint main() { char buf[15] = "hello wrold"; char *p = buf; for( ;*p != ' '; p++) { printf("%c n",*p); } return 0; } 也可以写成(取决于后面你还需要不需要用到这个指针) #include int main() { char buf[15] = "hello wrold"; for(char *p = buf; *p != ' '; p++) { printf("%c n",*p); } return 0; } #注意: 在使用for循环时,在判断语句里对指针一定要加上解引用.这样才能保证指针的地址能与值对上 在使用字符串时,一定要注意数组的空间大小,是否有足够的空间储存‘ ’; 如果没有空间储存‘ ’,则会导致越界访问,出现段错误;
- 通过指针申请堆空间(动态内存)
通过之前的学习,我们知道了我们是不能直接往指针里面写数据的,如果想要写数据该怎么办呢?
//头文件 #include//函数原型 //申请堆空间 void *malloc(size_t size); //释放堆空间 void free(void *ptr); //以上的3个函数malloc,calloc,realloc. //都是用于申请堆空间的.只是申请的方式不一样. //函数解析 void *malloc(size_t size); 返回值: void * 是一个通用型指针,假如你需要申请int类型的空间.那么它会返回 int *型的指针地址, 也就是你所申请空间的首地址. //参数(size_t size) : 是申请空间的大小. //写法: (分配类型 *)malloc(分配元素个数 *sizeof(分配类型)) void free(void *ptr); 参数void *ptr: 所需要释放的堆空间指针;
示例代码: #include#include #include int main() { char * p = (char *)malloc(sizeof(char) * 10); stpcpy(p,"11111111"); printf("%sn",p); free(p); return 0; }
- 地址的偏移
假设有数组short buf[5] = {1,2,3,4,5};
数组ch的地址为 0x111 请计算出下面的值分别为多少?
buf+1 &buf+1 &buf[4] buf[4] buf+3 *(&ch[1]) &buf[1]+3
正确答案:
0x113 0x11b 0x119 5 0x117 2 0x119
short类型 : 一个数据有占有2个字节的内存空间.
- 指针与数组的结合
//假设有数组
char buf[12] = "helloworld";
char *p1 = NULL,
*p2 = NULL;
p1 = buf;
p2 = &buf[4];
//假设ch地址为0x113 ,
那么p2 - p1 = ?
p2 + p1 = ?
指针的相加减结论:
(1)指针相减在数组当中才有意义,想减的结果为地址之间元素个数之差.
(2)指针相加无意义,得不到想要得数据.只有在数组中++偏移的时候才会有意义.
(3)在数组中每次偏移的++或--.都是偏移一个数据类型大小的地址.例如(sizeof(char)*偏移的数量)
二级指针
- 概念
我们经常在书籍上看到空间的类型,那么空间的类型实际上就是变量的数据类型.
二级指针:与一级指针相同用于存储地址的指针.
注意:二级指针与数组无关,更与二维数组无关;
二级指针的操作遵循一点:
一个指针指向一个变量,*(解引用)这个指针,就是那个变量的本身.
- 如何使用二级指针去访问一个数据
- 二级指针的地址解析



