(1)指针:是内存最小单元编号,也就是地址
地址是怎样产生的: 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电 平(低电压)就是(1或者0);产生2的32次方的数字
每一行编号作为内存单元编号这些编号以16进制进行表示
计算机的内存由内存单元组成(byte)一个字节
平时口语中指针:通常为指针变量,是用来存放内存地址的变量,用*解引用操作
第一个*表示是个指针,*前面表示所指向类型
如图(1):int*p *表是p是个指针,为int类型,此时p是指针用来存放地址,当后面跟*再组合就是对地址解引用(见图(2)(3))
(1)
(2)
(3)
此时图(1)看到一个指针为4个字节;
前面说32位机过产生了2的32次方的可能编码=4294967296byte=4gb
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地 址。
总结:1.指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
2. 指针的大小在32位平台是4个字节,在64位平台是8个字节
(2)指针和指针类型
1.char* 类型的指针是为了存放 char 类型变量的地址。
2.short* 类型的指针是为了存放 short 类型变量的地址。
3. int* 类型的指针是为了存放 int 类型变量的地址。
指针类型的作用:1.决定了指针进行解引用操作时,一次访问几个字节
2.指针类型决定指针的步长(指针+1跳过几个字节)
用char*的指针解引用访问1个字节,int*指针解引用访问4个字节,float*指针解引用访问4个字节,short*指针解引用访问2个
我们可以看到int*类型+1跳过4个字节
指针类型的意义:指针的不类型,提供了不同的视角去观看和访问内存
(3)野指针:指针指向的方向是不可知的
成因:. 1.指针未初始化(局部变量指针未初始化,默认为随机值)
2.指针越界访问
3.指针指向空间释放 (如函数的调用生成与销毁)
如何避免野指针:
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放,及时置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
(4)指针运算
1.指针+-整数
此时*vp++跳过一个fioat*类型
指针的类型决定了指针向前或者向后走一步有多大(距离)。
2.指针-指针(前提指向同一空间)得到两个指针的元素个数的绝对值
*应用可实现算数组个数
3.指针的关系运算
指针的关系运算,指向相同类型数据的关系运算,不同类型的指针之间,或者指针与非0整数之间的比较是无意义。
但是0是专门用来表是空指针(null)
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
**************************************************************************************************************
补充指针与数组的关系:
1.指针和数组是不同的对象:指针是一种变量用来存放地址,大小是4/8字节;
数组是一组相同元素的集合,是可以放多个元素,大小是取决于元素个数与元素类型
2.数组的数组名是数组首元素的地址,地址可以放在指针变量中
通过指针来访问数组
(4)二级指针
指针变量也是变量,是变量就有地址 ;所以用二级指针来接受一级指针的地址
可以通过二级指针来访问一级指针,再通过一级指针访问内容
补充!!!数组与指针的关系:
1.指针和数组是不同的对象
指针是一种变量,存放地址的,大小4/8字节
数组是一组相同类型元素的集合,是可以放多个元素的,大小取决于元素个数和元素的类型
2.数组的数组名是数组首元素的地址,地址可以放在指针变量中,可以通过地址来访问数组
(5)指针数组--存放指针的数组
如图我们生成了三个数组a,b,c此时用了int*arr[3]来接收a,b,c三个数组的地址第一个*表示是个指针,*前面表示所指向类型此时是适用的,这不是简单的指针,此时把第一个*去掉为int arr[3],*所指向为int 类型,int*类型组成一个数组如图
int* arr[3]为指针数组
此时简单模拟了二维数组,arr为int*类型
(6)字符指针
char*pc第一个*表示是个指针,*前面表示所指向类型为char
pc此时为一个char*类型的指针
(7)数组指针--存放数组地址的指针--指向数组的指针
把数组取地址,把地址放到数组指针里面
pa先和*结合,说明pa是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证pa先和*结合。
*********补充&数组名与数组名
数组名--数组首元素地址
&数组名--是数组的地址
数组首元素的地址和数组的地址从值的角度来说是一样的,但意义不同
&arr 的类型是: int(*)[10] ,是一种数组指针类型 数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40
数组名arr,表示首元素的地址
但是二维数组的首元素是二维数组的第一行首元素的地址
所以这里传递的arr,其实相当于第一行的地址
二维数组,数组名+1为二维数组的第二行的地址,与上面同理,此处的地址与二维数组的第二行
的首元素地址相同,不同在于一行地址+1就是跳过一行,如果只是一个元素地址+1只是跳过一
个整型
(8)数组传参
一维数组传参
void test(int arr[])
void test(int arr[10])
void test(int *arr)
int arr[10] = { 0 };
test(arr);
**一维数组传参,可以用一维数组来接受。数组名为首元素的地址,可以拿一级指针来接收**
二维数组传参
void test(int arr[3][5])
void test(int arr[][5])
void test(int (*arr)[5])
int arr[3][5]={0};
test (arr);
**二维数组传参,可以拿二维数组来接受,数组名为第一行的地址,可以拿数组指针来接受**
一级指针传参 就用指针变量接受
void test(int *p)
int a=10;
int *p=&a;
test(p);
int arr[10];
test(arr);
test(&a);
** 地址用一级指针来接收,p为int的指针用一级指针来接收 **
二级指针传参
int n=10;
int *p=&n;
int **pp=&p;
test(pp);
test(&p);
int *arr[10];
test (arr);
** 一级指针用二级指针来接收,pp为int*的指针用二级指针来接收 **
(9)函数指针--指向函数的指针
函数指针int (*pf)(int, int)中的(int, int)表示函数的参数类型,即该指针指向的函数接受两个int类型的参数。这个函数指针可以用来保存指向这样一个函数的地址,该函数接受两个整数作为参数,并返回一个整数值。在使用函数指针时,可以将该指针指向不同的函数,只要这些函数具有相同的参数类型和返回类型,就可以通过函数指针调用这些函数。
int (*pf)(int, int)中的第一个int表示指向的函数返回类型为int。这个函数指针可以指向一个返回类型为int的函数。如果指针类型中的返回类型为void,则指针只能指向返回类型为void的函数;如果返回类型为其他类型,指针只能指向返回该类型的函数。函数指针的类型定义必须与其指向的函数类型匹配,否则会出现编译错误。
补充:!!!&函数名与函数名都是函数的地址
函数指针是指向函数的指针变量。它们可以作为参数传递给其他函数或作为返回值返回给调用者,从而实现更灵活和动态的程序设计。
以下是函数指针的一些主要用途:
1.回调函数:函数指针可以用于回调函数,也就是在函数执行期间回调一个函数。回调函数通常被用来实现事件处理或异步编程等功能。
2.动态函数调用:函数指针可以根据程序运行时的需要,动态地选择调用不同的函数。这种技术通常被用于实现插件系统或动态链接库。
3.函数指针数组:可以使用函数指针数组来实现类似于分派表的功能,将函数指针存储在一个数组中,根据需要选择调用适当的函数。
4.泛型编程:函数指针可以用于实现泛型算法,例如排序算法。通过使用函数指针,可以使算法具有更大的灵活性和通用性。
总之,函数指针是一种非常强大的编程技术,可以增加程序的灵活性和动态性,使程序更加高效和易于维护。
(10)函数指针数组--存放函数指针的数组
上面我们学习了函数指针形如 int (*ptr)(int,int )声明函数指针数组
使用已确定的函数指针类型声明一个函数指针数组。数组的大小可以根据实际需要进行调整。例如,声明一个大小为 3 的指向返回整数类型、接受两个整数参数的函数的指针数组可以表示为:
int (*arr[3])(int, int)。
[]的优先级要高于*号的,所以此处为一个数组,前面与*结合为指针数组,这个数组包含三个元素,每个元素都是指向上述函数类型的指针
可以实现计算器
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
switch (input)
{ case 1:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = add(x, y);
printf( "ret = %d\n", ret);
break;
case 2:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = sub(x, y);
printf( "ret = %d\n", ret);
break;
case 3:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = mul(x, y);
printf( "ret = %d\n", ret);
break;
case 4:
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = div(x, y);
printf( "ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf( "选择错误\n" );
break;
}
} while (input);
return 0;
}
平时没用函数指针数组,这里我们看到函数类型都为接受两个int类型的参数,返回值为int,我们可以尝试用函数数组指针来简化操作
当我们声明 int (*p[5])(int x, int y) 时,我们在创建一个名为 p 的指针数组,该数组包含 5 个元素。每个元素都是一个指向参数为两个 int 类型,返回值为 int 类型的函数的指针。因此,这个函数指针数组的返回类型是指向函数的指针类型。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
while (input)
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf( "输入有误\n" );
printf( "ret = %d\n", ret);
}
return 0;
}
上述代码中,我们先定义了四个函数 add、subtract、multiply 和 divide,然后定义了一个包含五个元素的函数指针数组 p,除去第一个每个元素都是指向参数为两个 int 类型,返回值为 int 类型的函数的指针。接着,我们通过 p[x] 的方式来调用函数指针数组中的不同元素,将它们的返回值赋给 result 变量,并使用 printf 函数来输出运算结果
(10)指向函数指针数组的指针
int(*pf[5])(int,int);
&pf
int(*(*ppf)[5])(int,int)=&pf;
*ppf先结合形成指针,我们把*ppf拿掉,剩下int(*[5])(int,int);此时来看,这是一个指针,指向函数指针数组
(11)回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应。
前面的函数指针,函数指针数组就已经使用
qsort就用了回调函数来实现快排
qsort<stdlib.h>
void qsort(void*base,起始地址 size-t num,数组元素个数 size-t width ,元素占几个字节
int (*cmp)(const void*elem1,const void *elem2));
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//void qsort(void*base,起始地址 size-t num,数组元素个数 size-t width ,元素占几个字节
//int (*cmp)(const void* elem1, const void* elem2)比较地址);
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
模拟qsort
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
char tmp = *((char*)p1 + i);
*((char*)p1 + i) = *((char*)p2 + i);
*((char*)p2 + i) = tmp;
}
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{
int i = 0;
int j = 0;
for (i = 0; i < count - 1; i++)
{
for (j = 0; j < count - i - 1; j++)
{
if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
{
_swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
}
}
}
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
(12)sizeof与strlen
sizeof
1.sizeof(数组名),数组名表示整个数组,计算整个数组的大小,单位字节
2.&数组名,数组名表示整个数组,取出的是整个数组的地址,除了这2种情况,所有的数组名
都是数组首元素地址
3.只关注占用内存的大小,单位字节,不关心放什么
strlen
1.找\0,只求字符串长度,统计\0之前出现的字符个数,一定要找到\0才算结算,所以存在越界访问
实战易出错:
//char arr[] = { 'a','b','c','d','e','f' };
//strlen(*arr); 此处报错,实际上是strlen('a')->strlen(97); 非法访问
//strlen(arr[1]);同理
//strlen(&arr),&arr虽然是数组的地址,但是也是从数组起始的位置开始
//
//int a[3][4] = { 0 };
//printf("%d\n", sizeof(a[0] + 1)); a[0]作为第一行的数组名;没有单独放在sizeof内部,没有取地址,表示的是数组首元素的地址
//那就是a[0][0]的地址,a[0] + 1就是第一行第二个元素地址大小4 / 8
//
//sizeof(&a[0] + 1); &a[0]是第一行的地址,& a[0] + 1是第二行的地址,大小4 / 8
数组名--数组首元素地址
&数组名--是数组的地址
数组首元素的地址和数组的地址从值的角度来说是一样的,但意义不同
//
//sizeof(a[3]);如果存在a[3]就是第四行的数组名,数组单独放sizeof内部,计算第四行大小
这里不能说是出现了数组越界
相当于,你去银行面前看了一眼,但只是看了一眼没有去抢所以没有事情发生
这个情况同理
sizeof是不参与运算的(底层运行逻辑)
指针初稿:后面会不断完善。
点点关注,后期会跟新结构体总结