指针是内存中一个最小单元的编号 ,也就是地址
平时口语中说的指针其实本质上是指针变量,是用来存放内存地址的变量
一个字节为一个内存单元,我们可以通过编号找到内存 ,编号在生活中叫地址,对应我们c语言其实就是指针
#include
int main()
{
int a = 10;
int *p = &a;
return 0;
}
在32位机器上,地址就是32个0或1组成二进制序列,那地址就得用用4个字节的空间进行储存 ,也就是说32位机器,指针变量大小就是4个字节
在64位机器上,地址就是64个0或1组成二进制序列,那地址就得用用8个字节的空间进行储存 ,也就是说64位机器,指针变量大小就是8个字节
int main()
{
int a = 0x11223344;
char* pc = (char*)&a;
int i = 0;
for (i = 0; i < 4; i++)
{
*pc = 1;
pc++;
}
return 0;
}
指针的不同类型决定了,指针在解引用的时候一次性访问几个字节,决定了访问权限的大小
如果是char *的指针,解引用访问一个字节
如果是int *的指针,解引用访问四个字节
int main()
{
int a = 0x11223344;
int* pa = &a;
char* pc = &a;
printf("%p\n", pa);
printf("%p\n", pa+1);
printf("%p\n", pc);
printf("%p\n", pc+1);
return 0;
}
指针类型决定了指针+1跳过几个字节(步长)
如果是字符指针 ,+1跳过一个字节
如果是整形指针 ,+1跳过四个字节
#include
int main()
{
int n = 0x11223344;
char* pc = (char*)&n;
int* pi = &n;
*pc = 0;
*pi = 0;
return 0;
}
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
#include
int main()
{
int *p;
*p = 20;
return 0;
}
局部变量指针未初始化,默认为随机值
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%d\n", *p );
p++;
}
return 0;
}
当指针指向的范围超过数组arr的范围 , p 就是野指针
int* test()
{
int a = 10;
return &a;
}
int main()
{
int *p = test();
printf("hehe\n");
printf("%d\n", *p);
return 0;
}
int main()
{
int a = 10;
int* pa = &a;
return 0;
}
如果实在不知道指针指向哪里 也需要初始化
#include
int main()
{
int* p = NULL;
if (p != NULL)
{
}
return 0;
}
assert中可以放一个表达式,如果表达式为假就报错,如果为真就什么也不发生
举个例子
void * my_strcpy(char* dest, const char* src)
{
assert(dest && src);//断言指针的有效性
//assert(dest!= NULL)
//assert(src!= NULL)
while (*dest++ = *src++)
{
;
}
}
int main()
{
char arr1[] = "hello";
char arr2[20] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
前提 :两个指针要指向同一块空间
指针-指针的绝对值得到的是两个指针之间的个数
int main()
{
char ch[5] = {0};
int arr[10] = { 0 };
printf("%d\n", &arr[0] - &arr[9]);
return 0;
}
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
也就是说可以允许向后找到越界的地址和数组某个元素进行比较,但是不允许向前越界找到的地址和数组某个元素进行比较。这里的越界是指跳出了自己的范围,并没有非法访问越界之后的指针。这里只是比较大小。
#define N_VALUES 5
float values[N_VALUES];
float *vp;
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
指针和数组是不同的对象
指针是一种变量,用来存放地址,大小为4/8个字节。
数组是一组相同类型元素的集合,是可以存放多个元素的,大小取决于元素个数和元素类型。
数组的数组名是数组首元素的地址,地址是可以放在指针变量中,可以通过指针访问数组。
int main()
{
int a = 10;
int* pa = &a;
int* * ppa = &pa;
return 0;
}
pa是一级指针变量 ,ppa 是二级指针变量。 a 开辟了一块内存,用来存放数据 ,数据为10。 a的地址是0x0012ff40。 pa里面存放a的地址
存放指针的数组
使用一维数组模拟二维数组
int main()
{
int a[] = { 1,2,3,4 };
int b[] = { 2,3,4,5, };
int c[] = { 3,4,5,6 };
int* arr[] = { a , b ,c };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("%d", arr[i][j] );
}
printf("\n");
}
return 0;
}
关于arr [i] [ j] 的解释: arr[ i] 当i 为 0 , 1 ,2 时 arr [0] 访问的是a数组首元素地址 arr[1] 访问的是b数组首元素的地址 arr[ 2] 访问的是c数组首元素的地址
*(arr [0] + j ) 当j是 0 , 1,2,3 时 ,就可以等价于 arr[0] [j ]
int main()
{
char ch = 'w';
char * pc = &ch;
*pc = 'w';
return 0;
}
还有一种表现形式
int main()
{
char* ps = "hello bit";
return 0;
}
本质上是把“hello bit“这个字符串的首字符的地址存储到ps中
下面看一下一道来自《剑指offer》的题
#include
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不相同 ,str3和str4相同 那为什么会这样子呢?
因为str1 和str2 指向不同的地址(这里不是比较两个字符串的内容)
hello bit 是常量字符串 ,在内存中只会存一份
str3 和str4 都存放了h的地址,所以str3和str4相同
指针数组是一个存放指针的数组
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
指针数组的应用场景
int main()
{
int a[5] = { 1,2,3,4,5 };
int b[] = { 2,3,4,5,6 };
int c[] = { 3,4,5,6,7 };
int* arr[3] = {a,b,c};
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
//printf("%d ", *(arr[i] + j));
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
模拟了一个二维数组 ,但是其实并不是二维数组 ,因为二维数组每一行在内存中是连续存放的
能够指向数组的指针
存放的是数组的地址
int main()
{
int arr[10] = { 1,2,3,4,5 };
int(*parr)[10] = &arr;
return 0;
}
&arr 取出的是数组的地址
parr十一个数组指针,其中存放的是数组的地址
void print1(int arr[3][5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7 } };
print1(arr, 3, 5);
return 0;
}
void print2(int(*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7 } };
print2(arr, 3, 5);//arr数组名,表示数组首元素的地址
return 0;
}
arr数组名 ,表示数组首元素的地址
二维数组的数组名表示首元素地址 , 二维数组的首元素是第一行
数组名是数组首元素的地址
但是有2个例外:
int main()
{
int arr[10] = { 1,2,3,4,5 };
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
数组名代表首元素地址
&arr 取出的是数组的地址,数组也有起始位置,虽然值相同 ,但是类型不一样
int main()
{
int arr[10] = {0};
int* p1 = arr;
int (*p2)[10] = &arr;
printf("%p\n", p1);
printf("%p\n", p1+1);
printf("%p\n", p2);
printf("%p\n", p2+1);
return 0;
}
p1是整形指针 ,整形指针+1 跳过4个字节
p2是数组指针 ,数组指针+1跳过一个数组
既然知道了数组指针和指针数组 ,那我们看一下下面的代码:
int arr[5]; // 1
int *parr1[10]; //2
int (*parr2)[10]; // 3
int (*parr3[10])[5]; // 4
1 整型数组
2 整形指针的数组
3 数组指针 , 该指针指向一个数组 ,数组10个元素 ,每个元素的类型是int
4 parr3 是一个存放数组指针的数组 , 该数组能够存放10个数组指针 ,每个指针能够指向一个数组 ,数组有5个元素 ,每个元素的类型是int
#include
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 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
void test(int arr[3][5]) //正确
{
}
void test(int arr[][])//err
{
}
void test(int arr[][5])//正确
{
}
void test(int* arr)//err
{
}
void test(int* arr[5])//err
{
}
void test(int(*arr)[5])//正确
{
}
void test(int** arr)//err
{
}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
void print(int* ptr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(ptr + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//p是一级指针
print(p, sz);
return 0;
}
指针传参,形参用一级指针变量接收
void test(char* p)
{
}
int main()
{
char ch = 'w';
test(&ch);//char*
return 0;
}
void test(char* p)
{
}
int main()
{
char ch = 'w';
char* p1 = &ch;
test(p1);//char*
return 0;
}
void test(int** p2)
{
**p2 = 20;
}
int main()
{
int a = 10;
int* pa = &a;//pa是一级指针
int** ppa = &pa;//ppa是二级指针
//把二级指针进行传参呢?
test(ppa);
printf("%d\n", a);
return 0;
}
二级指针传参 ,形参用二级指针接收
void test(int** p2)
{
**p2 = 20;
}
int main()
{
int a = 10;
int* pa = &a;//pa是一级指针
//把二级指针进行传参呢?
test(&pa); //传一级指针变量的地址
printf("%d\n", a);
return 0;
}
传一级指针变量的地址 ,形参用二级指针接收
void test( int **p2)
{
}
int main()
{
int* arr[10] = { 0 };
test(arr);//传存放一级指针的数组
return 0;
}
整形指针数组 ,每个元素都是int * , 数组名相当于首元素地址 ,首元素是int * , int *的地址就是 int **
指向函数的指针
存放函数地址的指针
&函数名 ,取到的就是函数的地址
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf) (int, int) = &Add;
return 0;
}
pf就是函数指针变量
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &Add;
int (*pf)(int, int) = Add;//Add === pf
int ret = (*pf)(3, 5);//1
int ret = pf(3, 5);//2
int ret = Add(3, 5);//3
int ret = * pf(3, 5);//err
printf("%d\n", ret);
return 0;
}
1 2 3 得到的结果都是一样的
看两段来自《c陷阱和缺陷》的代码
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
存放函数指针的数组
函数指针数组的应用 :转移表
#include
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");
}
int main()
{
int input = 0;
do
{
menu();
int x = 0;
int y = 0;
int ret = 0;
printf("请选择:>");
scanf("%d", &input);
int (*pfArr[5])(int, int) = { NULL ,Add,Sub ,Mul ,Div };
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
int ret = (*pfArr[input] )(x, y);
printf("%d", ret);
}
else if (input == 0)
{
printf("退出程序\n");
break;
}
else
{
printf("选择错误 ,请重新选择\n");
}
} while (input);
return 0;
}
指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针
int main()
{
int (* p1)(int, int);
int (* p2[4])(int, int);
int ( * (*p3)[4])(int ,int )=&p2;
return 0;
}
p1是函数指针
p2是函数指针的数组
&p2 取出的是函数指针数组的地址
p3 就是一个指向【函数指针的数组】的指针
void qsort(void* base,
size_t num,
size_t size,
int (*cmp)(const void* e1, const void* e2)
);
base中存放的是待排序数据中第一个对象的地址
size_t num 排序数据元素的个数
size_t_size 排序数据中一个元素的大小 ,单位是字节
int (cmp)(const void e1, const void* e2) 是用来比较待排序数据中2个元素的函数
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int cmp_int( const void* e1 ,const void* e2)
{
return * (int *)e1 - * (int *)e2 ;
}
int main()
{
int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
return 0;
}
int cmp_int( const void* e1 ,const void* e2)
{
return * (int *)e1 - * (int *)e2 ;
}
void Swap( char *buf1 ,char * buf2 ,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf2++;
buf1++;
}
}
void bubble_sort(void * base,int sz , int width,int (*cmp)(const void *e1, const void *e2) )
{
//躺数
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
//比较
if (cmp( (char*)base+j*width , (char *)base+(j+1)*width ) > 0)
//交换
{
Swap( (char*)base + j * width, (char*)base + (j + 1) * width ,width );
}
}
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
return 0;
}
如果你觉得这篇文章对你有帮助,不妨动动手指给点赞收藏加转发,给鄃鳕一个大大的关注
你们的每一次支持都将转化为我前进的动力!!!