自学内容网 自学内容网

指针(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");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("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;
}


结语:作为初学者,可能对指针理解不太全面,本篇文章不足之处在所难免,如有错误还望大家纠正下,望大家多多包涵,谢谢大家!


原文地址:https://blog.csdn.net/2402_86350741/article/details/143434708

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!