目录
1.明确
指针就是地址,口头语中的指针 为指针变量
32位虚拟地址空间
cpu - 32位地址 - 地址传输线 - 内存
2.指针与指针类型
指针变量的大小都是一样的。 32个地址序列或者64个地址序列
int main()
{
int a = 10;
int* pa = &a;
char ch = 'w';
char* pc = &ch;
printf("%d,%d\n", sizeof(pa), sizeof(pc));
//指针变量的大小都是一样的。 32个地址序列或者64个地址序列
return 0;
}
指针类型有很重要的作用:
指针类型决定了,指针在被解引用的时候,访问的权限。
整形指针解引用访问4个字节
字符指针解引用访问1个字节
……
int main()
{
int a = 0x11223344;//十六进制数值, 11占一个字节,22占一个字节,33占一个字节,44占一个字节
// int* pa = &a;
// *pa = 0;//int* 全部变成了零 44332211 00000000
char* pa = &a;
*pa = 0;//char* 44332211 只有44变成了00 00332211
return 0;
}
指针类型决定了,指针向前或者向后走一步 走的距离。
整形指针 看作一个整形进行增加。 一次字节+4 int* +n ---> n*sizeof(int)
字符指针 看作一个字符进行增加。 一次字节+1
指针类型——指针看待类型的视角
野指针
1.指针没有初始化
2.越界
指针加减
指针 - 指针 得到的 两个指针之间的元素个数 前提是:两个指针指向同一个块空间
int main()
{
int arr[10] = {0};
printf("%d\n", &arr[9] - &arr[0]);
return 0;
}
求字符串长度:
1.计数器的方法
2.递归
3.指针 - 指针
指针和数组
数组名表示的是 数组首元素的地址。
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
可见,两者地址相同。
所以p+1 计算的是数组arr下标为1的地址。
二级指针
指针变量也是变量,是变量就有地址,则用二级指针
int a = 10; //假设地址为0x0018ff44
int* pa = &a; 0x0018ff44
int** ppa = &pa; 0x0018ff3c
int b = 20;
*ppa = &b;//等价于 pa = &b;**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
3.指针进阶(一)
字符指针
在char* 类型指针中,除了可以实现 char* pc = 'w';外,我们还可以如此使用
char* pstr = "hello bit.";
printf("%s\n", pstr);
此时,是将字符串的首地址 存放在了pstr中。
有一道面试题如下:
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
char* str3 = "hello bit.";
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 是两个数组,两个均可修改,故此分配了两个内存。
str3 str4 是一个常量字符串,不可修改,故此只分配了一个内存。
指针数组 与 数组指针
指针数组 与 数组指针的区别
指针数组: 存放指针的数组
数组指针: 存放 数组的地址 的指针
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int* parr1[3] = { arr1,arr2, arr3 };//指针数组 : 存放指针的数组
int (*parr2)[10];//数组指针 。
}
数组指针中,由于[]的优先级高于 * ,所以需要用() 将 *与parr2结合。
parr2先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针
以下为数值指针与 指针数组的图解
&数组名VS数组名
通常情况下,数组名都是代表该数组的首元素地址。
有两个例外:&arr 取出整个地址, sizeof(数组名) 计算整个数组的大小
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
运行结果如下:
因为&arr是整个数组的地址,所以对&arr+1 代表跳过整个数组的大小
数组指针的使用
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
int arr[5];
是一个整形数组,每个元素的类型是 int,有5个元素
int* parr1[10];
是一个指针数组,每个元素的类型是 int*
int(*parr2)[10];
是一个指向数组的指针,指向的数组有10个元素,每个元素的类型是 int
int(* parr3[10])[5];
是一个数组,有10个元素,每个元素的类型是:int(*)[5]
存放数组指针的数组。
arr1 int int int int int
arr2 int int int int int
arr3 int int int int int
parr3 &arr1 &arr2 &arr3 &arr4 int(*)[5] int(*)[5] ……
&arr5 &arr6
4.指针进阶(二)
数组参数、指针参数
1.一维数组的传参
形参写成数组的形式:
void test(int arr[])
{}
形参部分的数组大小可以不写。
形参写成指针的形式:
void test(int* p)
{}
因为传入的数组的首地址,所以可以指针形式
2.二维数组的传参
int main()
{
int arr[3][5] = {0};
teat(arr);
}
形参写成数组的形式:
void test(int arr[][5])//行可以省略,列必须写
形参写成指针的形式:
void test(int(*p)[5])
arr
p -> 0 int int int int int
p+1 -> 1 int int int int int
p+2 -> 2 int int int int int
(*p)[5] P指向的是每一行5个元素的首地址
3.一级指针传参
void test(int* ptr)
{
ptr = 100;
}
int main()
{
int a = 10;
int arr[3]= {1,2,3};
int* p = &a;
test(p);
test(arr);
}
4.二级指针传参
void test_1(char** ppc)
{
//...
printf("test_1\n");
}
void test_2(char (*p)[5])
{
//...
printf("test_2\n");
}
void test_3(char(*p)[3][5])
{
//...
*p; //此处*p 代表的是第一行的地址
printf("test_3\n");
}
int main()
{
char ch = 'w';
char* pc = &ch; //①
test_1(&pc); //①传一级指针变量地址
char** ppc = &pc;//②
test_1(ppc); //②传二级指针
char* arr[4];
test_1(arr); //③指针数组,数组可看作一级指针
char arr1[3][5];
test_2(arr1); //此处只传了第一行的地址
test_3(&arr1); //此处取二维数组全部地址,传的是二维数组,所以函数需要[3][5] - 不常见
}
5.函数指针
int Add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10];
int(*p)[10] = &arr; //p是一个数组指针变量
&Add;//即可拿到函数的地址
printf("%p\n", &Add);
printf("%p\n", Add); //99 ,100 这两条代码意义是一样的
//存放函数Add的地址
int (* pf)(int, int) = Add;//pf就是函数指针
//函数指针的使用
int ret = (* pf)(2, 3); //通过指针调用函数
// ret = Add(2,3);
// int ret = pf(2,3); //即在 函数指针中, *可以忽略
printf("\n%d\n", ret);
return 0;
}
两个锻炼用代码:
代码1
( * ( void (*)()) 0 ) ();进行逐步分析:
void(*)() 是函数指针类型 void (*p) ()
( void(*)() ) 强制转换类型
( void(*)() ) 0 对0进行强制类型转换
( ( void(*) () ) ) 0 ()
首先将0强制转换为一个函数指针类型,意味着 0 处的地址放着一个返回类型是void 的无参数函数,
调用0 地址处这个函数
在函数指针类型中, * 号无意义,可以省去
代码2
void (*signal(int, void(*)(int)))(int);
(*signal(int, void(*)(int)))
signal( int, void(*)(int) )
signal是一个函数的声明
signal函数的参数,第一个是int,第二个函数指针类型void(*)(int)
signal函数的返回类型也是 void(*)(int)
6.函数指针数组
函数指针数组的用途:转移表
int ( *parr1[10] )( );
其中, parr 先与 [ ] 结合,是一个数组,数组的内容是 int( * ) ( )类型的函数指针。
#include <stdio.h>
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 menu()
{
printf("*************************\n");
printf("****1.Add 2.Sub*******\n");
printf("****3.Mul 4.Div*******\n");
printf("****0.exit *******\n");
printf("*************************\n");
}
int main() //使用
{
int input,ret,x,y;
//int (* pf1)(int, int) = Add;
//int (* pf2)(int, int) = Sub;
//int (* pf3)(int, int) = Mul;
//int (* pf4)(int, int) = Div;
//构建多个,过于麻烦, 构建一个函数指针数组
//函数指针数组
int (*pf[5])(int, int) = {0, Add, Sub, Mul, Div};
do
{
menu();
printf("请选择计算器模式\n");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
break;
}
else if (1 <= input && input <= 4)
{
printf("请输入两个有效数字:");
scanf("%d %d", &x, &y);
ret = (*pf[input])(x,y);
printf("ret = %d\n", ret);
}
else if (input > 4)
{
printf("输入错误,请重新输入");
}
} while (input != 0);
}
7.指向函数指针数组的指针
指向函数指针数组的指针是一个 指针,它指向一个 数组 ,数组的元素都是 函数指针
int Add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7 };
int (*p)[10] = &arr;
p数组指针
int (* pf)(int, int) = &Add;
函数指针
int (* pfarr[4])(int, int);
函数指针数组
int (*(*p3)[4])(int, int) = &pfarr;
指向函数指针数组的 指针
return 0;
}
8.回调函数
回调函数是 将一个函数的指针(地址) 作为参数,传给另外一个函数, 当这个指针被调用的时候,它就是回调函数。
回调函数一般用于发生 特定的事件时,由别的函数调用。
void test()
{
printf("test\n");
}
void print_test(void(*p)() )
{
if (1)
p();
}
int main()
{
print_test(test);
return 0;
}
9.qsort函数的使用
引入#include< stdlib.h>
void qsort(void* base, //base存放的是 待排序数据的起始位置
size_t num, //待排序的元素个数
size_t width, //一个元素的字节大小
int(*cmp)(const void* e1, const void* e2));//函数指针
比较函数 e1、e2:待比较的两个元素的地址
int返回 e1<e2 返回 <0
int返回 e1=e2 返回 0
int返回 e1>e2 返回 >0
第四个参数,要求qsort函数的使用者,自定义一个比较函数。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//使用qsort排序数组
void cmp_int(const void* e1, const void* e2)
{
//if (*(int*)e1 > *(int*)e2)
// return 1;
//else if (*(int*)e1 == *(int*)e2)
// return 0;
//else if (*(int*)e1 < *(int*)e2)
// return -1;
return (*(int*)e1 - *(int*)e2);
//此处指针 - 指针,所得元素个数,自动判断返回值与0的关系
}
void printf_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
printf_arr(arr, sz);
}
//使用sqort 对结构体的排序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
char name[20];
int age;
double score;
};
//排序年龄
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//排序名字
int cmp_stu_by_name(const void* e1, const void* e2)
{
return strcmp (((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void main()
{
struct Stu arr[3] = { {"zhangsan",20,55.5},{"lisi",21, 60.0},{"wangwu",12,35.0} };
int sz = sizeof(arr) / sizeof(arr[0]);
//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
10.qsort函数的模拟实现
//使用回调函数,模拟实现qsort(采用冒泡的方式)。
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_qsort(void* base, int num, int width,int (*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < num - 1; i++)
{
int j = 0;
for (j = 0; j < num - 1 - i; j++)
{
//if (arr[j] > arr[j + 1])//比较
if(cmp((char*)base + j * width,(char*)base + (j+1) * width)>0)
{
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}