指针(c语言)
一.指针的定义
1.内存被划分为一个一个内存单元,对内存单元进行编号,这些编号称为内存单元的地址,
其中指针就是存放地址的容器
2.平常说的指针,其实就是指针变量
注意:
1个内存单元的大小是1个字节,如果是一个int类型的变量,就会有4个内存单元
二.指针变量
我们可以通过&(取地址)操作符来取出变量的内存其实就是地址,把地址放在一个变量中,这个变量就是指针变量
#include <stdio.h>int main(){int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,因为它是int类型,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
return 0;}
1.指针变量的大小
1.在32位机器上,地址是由二进制(0或1)组成的,1个二进制就是1个bit(比特),32位二进制,那也就是32个bit(比特),比特转换为字节是8进1,所以就是4个字节
2.在64位机器上,也就是有64个二进制,也就是64bit(比特位) ,也就是8个字节
总之:在32位机器上,指针变量的大小是4个字节
在64位机器上,指针变量的大小是8个字节
三.指针和指针的类型
指针有整数类型,浮点数类型,字符类型
那他们的大小怎么样,如下
总之:
在64位(x64)操作系统,指针类型大小统一为8个字节
在32位(x86)操作系统下,指针类型大小统一为4个字节
1.指针类型的意义
指针类型决定了+1或者-1时往后访问几个字节
若int、float、double。short类型变量转成char类型指针,+1往后访问1个字节,-1往前访问1个字节
若char类型变量转成int、float、double。short类型指针, +1往后访问4个字节,-1往前访问4个字节
2.指针+-整数
int main()
{int n = 10;char* pc = (char*)&n;int* pi = &n;//pc是指针变量存放n的指针,所以他们俩的地址相同printf("%p\n", &n);printf("%p\n", pc);//把整数类型转换为字符类型,由4个字节变成1个字节//所以pc+1是往后访问1个字节printf("%p\n", pc + 1);printf("%p\n", pi);//这是整数类型的指针,+1往后访问4个字节printf("%p\n", pi + 1);return 0;
}
运行结果
这里显示的是十六进制👆
那么为什么000000DA66EF9F4到000000DA66EF9F5增加了1个字节呢?
因为内存被划分为了一个个内存单元,每个内存单元的大小是1个字节,内存单元里头放的是地址
那么000000DA66EF9F4到000000DA66EF9F5,也就是走到了下一个内存单元,所以就增加了1个字节。
3.指针-指针
C 语言规定
这个减法操作的结果是两个指针所指向的地址之间相隔的元素个数。
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%p\n", &arr[0]);printf("%p\n", &arr[9]);int n = &arr[9] - &arr[0];printf("%d\n", n);return 0;
}
程序运行👇
例:实现strlen函数的功能
char my_strlen(int* pa)//用指针来接受数组名的首元素的地址
{char* start = pa;while (*start !='\0')//字符串是以'\0'结尾,排除'\0'的情况.{//刚好访问到了\0的地址,但因*start !='\0'条件跳出循环start++;}return start - pa;
}int main()
{char arr[] = "abcdef";/*int ret = my_strlen(arr);*/printf("%d", my_strlen(arr));//数组名代表了首元素的地址,所以不用取地址return 0;
}
代码运行
4.指针的关系运算
//宏定义
#define N_VALUES 5
//创建数组
int values[N_VALUES];
//创建指针变量
int* vp;int main()
{for (vp = &values[N_VALUES]; vp > &values[0];){//先对其解引用,后再-1.*--vp = 0;}int i = 0;for (i = 0; i < 5; i++){printf("%d ", values[i]);}return 0;
}
代码运行
四.指针和数组
1.数组名代表首元素的地址
注意:
1.sizeof(arr):这里头数组名表示的是整个数组,计算的是整个数组的大小。
2.对数组名取地址,表示的是整个数组的地址。
1.应用指针对数组进行遍历
数组元素是连续存放的,并且由低地址想高地址进行存放的,那么就可以对指针变量+1或者-1,找到数组中元素所对应地址,对其解引用,进而访问数组中的元素。
代码如下:
实现数组的遍历
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* pa = arr;int i = 0;for (i = 0; i < 10; i++){//先对指针变量解引用,进而访问数组中的元素,后再++printf("%d ", *(pa++));}return 0;
}
五.野指针
概念:野指针就是指针指向的位置是不可知(随机的,正确的,没有明确限制)
1.野指针成因
指针未初始化
#include <stdio.h>int main(){
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;return 0;}
指针越界访问
include <stdio.h>int main(){int arr[10] = {0};int *p = arr;int i = 0;for(i=0; i<=11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;}return 0;}
2.如何避免出现野指针
1.指针初始化
2.小心指针越界
3.指针指向空间释放,及时置NULL
4.避免使用局部变量的地址
5.指针使用之前检查其有效性
六.二级指针
一级指针存放的是变量的地址,那么二级指针存放的是一级指针的地址
#include<stdio.h>
int main()
{int a =10;
//一级指针存放的是a的地址int* pa =&a;
//二级指针存放的是pa这个指针变量的地址int** ppa=&pa;return 0;
}
图解如下👇
七.字符指针
字符指针存放字符的指针,准确的来说,是把字符首元素的地址存放在指针变量
int main()
{//字符串的首元素的地址被存放再pstr的指针变量了const char* pstr = "Hello";//直接打印字符串printf("%s\n", pstr);return 0;
}
代码运行
例子
#include <stdio.h>
int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}
运行结果
str1[]和str2[]是俩个数组,他们俩个数组的地址不相同,所以ptr1 !=ptr2
str3和str4存放的是同一个字符串的地址,所以str3和str4地址一样,即str3 ==str4
八.指针数组
指针数组就是存放指针的数组
int* arr[]={&a,&b,&c};
arr是一个数组,数组中由三个元素,且是元素的地址
int main()
{int a = 10;int b = 20;int c = 30;//指针数组//存放地址的数组int* arr[] = { &a,&b,&c };int i = 0;while (i < 3){//对其解引用,进而通过地址访问数组中的元素printf("%d ", (*arr[i]));i++;}return 0;
}
1.二级指针的指针数组
int **arr3[5];
九.数组指针
数组指针指的是存放数组地址的指针
int (*p)[10];
解释::p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。
[ ]的优先级是高于 *,所以需要对其加()
利用数组指针的知识实现对一维遍历
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);int(*p)[10] = &arr;int i = 0;//遍历,打印元素for (i = 0; i < sz; i++){//这俩个printf等价//*p代表数组指针printf("%d ", *((*p) + i));//printf("%d",(*p)[i])}return 0;
}
代码运行
利用数组指针对二维数组实现遍历
二维数组的起始是一维数组的数组
二维数组中每一行都当成一个首元素
所以二维数组的第一行是首元素的地址
十.一维数组的传参
以下5种方式均可用于一维数组的传参
#include <stdio.h>void test(int arr[]){}void test(int arr[10]){}void test(int *arr)/{}void test2(int *arr[20]){}void test2(int **arr){}int main(){int arr[10] = {0};
十一.二维数组的传参
//二维数组的传参
void test(int arr[3][5])
{}
//二维数组行可以省略,列不可以省略
void test(int arr[][5])
{}
//利用数组指针,存放二维数组的首地址
void test(int(*arr)[5])
{}int main()
{int arr[3][5] = { 0 };return 0;
}
十二.函数指针
函数指针就是存放函数的指针
这俩个代码即使没有对test取地址,得到的函数的地址都是一样,这说明了函数名已经表示了函数的地址,所以对不对其取地址都一样。类似于数组
int(*pf)(int, int) = Add;
首先pf首选与*结合说明他是一个指针变量,然后指向一个函数,指向的函数有俩个参数,返回类型是int,说明它是一个函数指针
函数调用
void Add(int x, int y)
{int result = x + y;printf("%d ", result);
}int main()
{//存放函数的指针,就是指针变量int(*pf)(int, int) = Add;//这三种调用方式都可以//通过解引用函数指针进行调用(*pf)(2,3);//函数指针可以直接调用,编译器会自动处理pf(1, 1);Add(0, 0);return 0;
}
代码运行
十三.函数指针数组
函数指针数组就是把函数的地址存放在一个数组中
int ( *parr1[10] ) ( );
parr1先与[ ]结合说明他是一个数组,存放的是int (*)()类型的函数指针
例题
十四:指向函数指针数组的指针
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}int main()
{//函数指针int(*pf)(int, int) = Add;//函数指针数组int(*pfArr[4])(int, int) = { Add,Sub };int(*(*pffArr)[4])(int, int) = &pfArr; //pffArr是一个指向函数指针数组的指针变量//首先pffArr与*结合说明他是一个指针,该指针指向一个方括号说明他是指向的数组//并且数组中的每一个元素是函数指针return 0;
}
十五.回调函数
回调函数的定义:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应。
例题:计算器的+ - * /的实现
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//制作菜单
void menu()
{printf("***********************\n");printf("***********************\n");printf("*******1.add 2.sub********\n");printf("*******3.mul 4.div****\n");printf("*********0.exit***********\n");printf("***********************\n");printf("***********************\n");
}
//使用函数指针来实现函数回调
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}void Calc(int(*pa)(int, int))
{int x = 0;int y = 0;int ret = 0;printf("请输入俩个操作数");scanf("%d %d", &x, &y);//通过函数指针来调用函数,比如说Add函数,pa就是等价于Add函数//不需要对其解引用,编译器会自动处理ret = pa(x, y);printf("%d\n", ret);
}
int main()
{int input = 0;do{menu();printf("请选择\n");scanf("%d", &input);switch (input){case 1://函数名就是函数的地址,把函数的地址传递给形参Calc(Add);break;case 2:Calc(Sub);break;case 3:Calc(Mul);break;case 4:Calc(Div);break;default:break;}} while (input);return 0;
}
结语:作为初学者,可能对指针理解不太全面,本篇文章不足之处在所难免,如有错误还望大家纠正下,望大家多多包涵,谢谢大家!