指针
一.指针的概念
一切都是内存地址
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 <stdio.h>
#include <string.h>
int main()
{
//定义指针
int c = 4;
int *p = &c;
char stu[15]="hello world";
char* s = stu;//定义一个char *指针s指向stu数组
/*1.指针指向数组实际上是指向数组的首地址
2.数组名用在表达式当中的含义是数组首地址
3.在表达式当中数组名使用取地址符号,代表数组的地址 */
printf("stu[0] is %p\n",&stu[0]);//数组的首元素的地址
printf("&stu is %p\n",&stu);//整个数组的地址
printf("%s\n",s);//s指向数组,访问数组从首地址开始的元素,
printf("%s\n",stu);//stu在表达式当中代表数组首地址
printf("%c\n",stu[0]);//stu数组中的首元素
printf("%c\n",s[0]);//s指针的首地址的元素
printf("%c\n",s[9]);
int b;//b存储的常量
printf("int *p:%p\n",p);
printf("int *p: %d\n",*p);
printf("int b: %p\n",&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存放的地址为: %p\n",p);
p = p + 1;
printf("p存放的地址为: %p\n",p);
char buff[10] = "hello";
char *q = &buff[4];
printf("q存放的地址为: %p\n",q);
q = q - 1;
printf("q存放的地址为: %p\n",q);
return 0;
}
总结: 指针加法就是加一个数据类型的大小,指针减法就是减一个数据类型的大小;
int main(void)
{
int buf[10] = {10,20,30,40,50,60,70,80};
int *p = buf; //定义指针p指向数组buf
//注意运算符优先级;
printf("p 数据为: %d\n",*(p+1));
printf("p 数据为: %d\n",*p+1);
return 0;
}
#注意点
1.星号的优先级比加号的优先级高;
2.*(p + 1) 由于括号的原因,先执行的p+1也就是数据类型+1;然后再对p+1进行解引用
3.*p + 1, 由于*号的优先级比+号要高,所以先执行*p,再执行+1的运算.
- 使用指针访问字符串
#include<stdio.h>
int main()
{
char buf[15] = "hello wrold";
char *p = buf;
for( ;*p != '\0'; p++)
{
printf("%c \n",*p);
}
return 0;
}
也可以写成(取决于后面你还需要不需要用到这个指针)
#include<stdio.h>
int main()
{
char buf[15] = "hello wrold";
for(char *p = buf; *p != '\0'; p++)
{
printf("%c \n",*p);
}
return 0;
}
#注意:
在使用for循环时,在判断语句里对指针一定要加上解引用.这样才能保证指针的地址能与值对上
在使用字符串时,一定要注意数组的空间大小,是否有足够的空间储存‘\0’;
如果没有空间储存‘\0’,则会导致越界访问,出现段错误;
- 通过指针申请堆空间(动态内存)
通过之前的学习,我们知道了我们是不能直接往指针里面写数据的,如果想要写数据该怎么办呢?
//头文件
#include <stdlib.h>
//函数原型
//申请堆空间
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<stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char * p = (char *)malloc(sizeof(char) * 10);
stpcpy(p,"11111111");
printf("%s\n",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)*偏移的数量)
二级指针
- 概念
我们经常在书籍上看到空间的类型,那么空间的类型实际上就是变量的数据类型.
二级指针:与一级指针相同用于存储地址的指针.
注意:二级指针与数组无关,更与二维数组无关;
二级指针的操作遵循一点:
一个指针指向一个变量,*(解引用)这个指针,就是那个变量的本身.
- 如何使用二级指针去访问一个数据
- 二级指针的地址解析