指针
int main()
{
const int num = 10;
//const int* p = # //const放在指针变量的*左边时,修饰的是*p,也就是说:不能通过p来改变*p(num)的值
//*p = 20;
int* const p = #//指针变量
*p = 20;
int n = 100;
p = &n;//const放在*右边,修饰的是指针变量p本身,p不能被改变了
printf("%d\n", num);
}
指针是变量,变量中存的地址,指针就是地址
在32位的机器上,地址是32个0或1组成的二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4字节。
如果在64位机器上,有64个地址线,那一个指针变量大小就是8字节,才能存放一个地址
int main()
{
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(short*));
printf("%d\n", sizeof(int*));
printf("%d\n", sizeof(double*));
}
指针类型的意义:
1、指针类型决定了指针解引用操作符能访问几个字节 charp ;p访问了1个字节
2、指针类型决定了指针+1,-1加的是或减的是几个字节,charp;p+1,跳过1个字节,intp;p+1,跳过一个整型4字节
指针进行解引用操作的时候能够访问空间大小
int main()
{
int a = 0x11223344;
//int* p = &a;
//*p = 0;//修改4个字节 00000000
char* p = &a;
*p = 0;//修改1个字节 00332211
}
指针的类型决定了指针向前或向后走一步有多大
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);
}
int main()
{
int arr[10] = { 0 };
//int* p = arr;//数组名-首元素地址 一次改变一个数组元素
char* p = arr;//一次改变一个字节
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 1;
}
}
野指针:指针指向的位置是不可知的
1、指针未初始化
int main()
{
//int a;//局部变量不初始化,默认是随机值
int* p=NULL;//NULL用来初始化指针的,给指针赋值
*p = 20;
}
2、指针越界访问
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 12; i++)
{
*(p++) = i;//当指针指向的范围超出数组arr的范围时,p就是野指针
}
}
3、指针指向的空间释放
int* test()
{
int a = 10;//a为局部变量,出函数释放,内存已经还给系统
return &a;
}
int main()
{
int* p = test();
*p = 20;
}
避免野指针
1、指针初始化
2、小心指针越界
3、指针指向的空间释放,将指针变量置为NULL pa=NULL;
4、指针使用之前检查有效性 if(pa!=NULL)
指针运算
指针±整数 取决于指针类型
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = &arr[0];
//for (i = 0; i < sz; i++)
//{
// printf("%d ", *p);
// p++;
//}
for (i = 0; i < 5; i++)
{
printf("%d ", *p);
p += 2;
}
}
struct Test
{
int num;//4
char* pcname;//4
short sdate;//2
char cha[2];//2
short sba[4];//8
}* p;//结构体指针 指向结构体的地址
//假设p的值为0x100000,已知结构体类型的变量大小是20字节
int main()
{
p = (struct Test*)0x100000;
printf("%p\n", p + 0x1); //p为结构体指针 跳过结构体20个字节 0x100014
printf("%p\n", (unsigned long int)p + 0x1);//将p强制转换为整型 0x100001
printf("%p\n", (unsigned int*)p + 0x1);//将p强制转换成无符号整型指针 0x100004
}
int main()
{
int a[4] = { 1,2,3,4 };// 01 |ptr2 00 00 00 02 00 00 00 03 00 00 00| 04 00 00 00|ptr1
int* ptr1 = (int*)(&a + 1);//&a是数组的地址
int* ptr2 = (int*)((int)a + 1);//a是首元素的地址 强制转换成整型+1相当于向后移动一字节
printf("%x,%x", ptr1[-1], *ptr2);//*(ptr1-1) 指针+-整数 取决了指针类型 向前移动四字节 4 //取四个字节00 00 00 02 2000000
}
指针±指针 指针-指针表示两个指针指向的内存位置空间相隔多少个元素
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9] - &arr[0]);//同一数组的地址是连续的 9
}
指针的关系运算
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
int main()
{
int arr[5];
int* p;
for (p = &arr[5]; p > &arr[0];)
{
*--p = 0;
}
for (p = &arr[4]; p >= &arr[0]; p--)//越界
{
*p = 0;
}
}
指针和数组
可以直接通过指针访问数组
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%p==============%p\n", p + i, &arr[i]);//p+i其实计算的是数组arr下标为i的地址
}
}
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
}
二级指针
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;//ppa就是二级指针
printf("%d\n", **ppa);
}
指针数组—数组
数组指针—指针 能够指向数组的指针
整型数组存放整型,字符数组存放字符,指针数组存放指针
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* arr2[3] = { &a,&b,&c };//指针数组,存放整型指针的数组
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", *arr2[i]);// 10 20 30
}
}
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* parr[] = { arr1,arr2,arr3 };//指针数组
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", *(parr[i] + j));
}
printf("\n");
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int (*p)[10] = &arr;//数组指针 arr是首元素的地址 &arr整个数组的地址
char* arr[5];
char* (*pa)[5] = &arr;//数组指针
}
int arr[5]; //arr是一个5个元素的整型数组
int* parr1[10];//parr是一个数组,数组有10个元素,每个元素的类型是int*,parr1是指针数组
int(*parr2)[10];//parr2是一个指针,该指针指向了一个数组,数组有10个元素,每个元素的类型是int,parr2是一个数组指针
int(*parr3[10])[5];//parr3是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int
一维数组指针应用
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[10] = &arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(*p + i));
printf("%d ", (*p)[i]);//p是数组的地址,*p就是数组 *p=arr 指向第一个元素地址
}
}
一维数组传参
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(arr);
}
一级指针传参
void test(int*p)//当一个函数的参数部分为一级指针时,可以接收什么样的参数
{}
int main()
{
int arr[10] = { 0 };
int* p = arr;
test(p);//一级指针传给函数
test(arr);
int a = 10;
int* p1 = &a;
test(p1);
test(&a);
}
二维数组指针应用
//参数是数组的形式
void print1(int arr[3][5], int x, int y)
{
int i = 0;
int j = 0;
for (i = 0; i < x; i++)
{
for (j = 0; j < y; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
//参数是指针的形式
void print2(int(*p)[5], int x, int y)
{
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; j++)
{
printf("%d ", *(* (p + i) + j) );
//将二维数组看做若干个一维数组
//p是数组指针
//p+i是每个数组的指针
//*(p+i)是数组名,指向每个数组的第一个元素的地址
//*(p+i)+j指向每个数组的每个元素的地址
//*(*(p+i)+j)指向每个元素
printf("%d ", (* (p + i)) [j]);
//p指向第一行的地址
//p+i指向每一行的地址
//*(p+i)每一行数组名,相当于a[0] a[1] a[2]
//(*(p+i))[j]每一行数组的每个元素 以(*(p+i))为起始地址,下标为j的元素
printf("%d ", *(p[i] + j));
printf("%d ", p[i][j]);
}
}
}
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);//arr数组名 数组名就是首元素地址 //指向a[0]数组的地址 数组指针
print2(arr, 3, 5);
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int* p = arr;
for (i = 0; i < 10; i++)
{
printf("%d", p[i]);//p为起始地址,下标为i的元素
printf("%d", *(p + i));//p指向数组首元素的地址,p+i指向数组每个元素的地址,*(p+i)得到数组每个元素
printf("%d", *(arr + i));//arr指向数组首元素的地址,arr+i指向数组每个元素的地址,*(arr+i)得到数组每个元素
printf("%d", a[i]);//a[i]=*(arr+i)=*(p+i)=p[i]
}
}
二维数组传参
void test(int arr[3][5])
{}
void test1(int arr[][5])
{}
//void test2(int arr[3][])//err 二维数组只能省略行,不能省略列
//{}
void test2(int(*arr)[5])
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
test1(arr);//数组指针 指向数组的指针
test2(arr);
}
二级指针传参
void test(int** ptr)
{}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
int* arr[10];//指针数组
test(arr);//存放一级指针数组的数组名
}
字符指针
int main()
{
//char ch = 'w';
//char* pc = &ch;//字符指针
char arr[] = "abcdef";
char* pc = arr;
printf("%s\n", arr);
printf("%s\n", pc);
}
int main()
{
const char* p = "abcedf";//"abcdef"是一个常量字符串 把字符串首字符a的地址放到了p中
*p = 'w';//常量字符串不能被修改
printf("%c\n", *p);//p指向的是a的地址
printf("%s\n", p);
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcedf";//arr1和arr2占有不同空间
const char* p1 = "abcdef";
const char* p2 = "abcdef";//常量字符串不能被修改,p1和p2指向同一地址
//if (arr1 == arr2)
if (p1 == p2)
{
printf("hehe");
}
else
{
printf("haha");
}
}
常量字符串被存储到一个单独的内存区域,指向同一字符串会指向同一内存,但是相同的常量字符串初始不同的数组会开辟出不同的内存块,所以arr1和arr2不同,p1和p2相同。
char* p = "abcdef";//常量字符串 a b c d e f \0
printf("%d\n", sizeof(p));//4/8 把字符串首字符a的地址放到了p中
printf("%d\n", sizeof(p+1));//4/8 得到字符b的地址
printf("%d\n", sizeof(*p));//1 字符串第一个字符 'a'
printf("%d\n", sizeof(p[0]));//1 arr[0]==*(arr+0) p[0]==*(p+0)=='a'
printf("%d\n", sizeof(&p));//4/8 地址
printf("%d\n", sizeof(&p + 1));//4/8 地址
printf("%d\n", sizeof(&p[0] + 1));//4/8 地址
printf("%d\n", strlen(p));//6 a的地址\0停止统计
printf("%d\n", strlen(p+1));//5 b的地址
//printf("%d\n", strlen(*p));//err 'a'97 把97当做一个地址向后访问
//printf("%d\n", strlen(p[0]));// err 'a'97把97当做一个地址向后访问
printf("%d\n", strlen(&p));//随机值 p里面存放的是a的地址 &p取得是p的地址 从p的开始向后访问 若a的地址是0x 0012ff44 最少是3 若a的地址是0x12324465 则最少是4
printf("%d\n", strlen(&p + 1));//随机值
printf("%d\n", strlen(&p[0] + 1));//5
函数指针 void(* )()
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int arr[10] = { 0 };
printf("%p\n", Add);
printf("%p\n", &Add);
int (*pa)(int, int) = Add;//*pa是一个指针 (int,int)是一个函数 int是返回类型,所以是一个函数指针,存放函数地址的指针
printf("%d",(*pa)(2, 3)); //pa是指针变量名 *pa找到这个函数 (2,3)赋参数
printf("%d",pa(2, 3));
}
void print(char* str)
{
printf("%s", str);
}
int main()
{
void(*p)(char*) = print;
(*p)("hello world");
}
*((void(*)())0)()//把0强制转换成:void(*)()函数指针类型 0就是一个函数的地址
//解引用找到这个函数,调用0地址处的该函数
void(*signal(int, void(*)(int)))(int);
//signal是一个函数声明
//signal函数的参数有2个,第一个是int,第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void
//signal函数的返回类型也是一个函数指针,该函数指针指向的函数参数是int,返回类型是void
typedef void(* pfun_t)(int);
pfun_t signal(int, pfun_t);
函数指针数组
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;
}
int main()
{
//需要一个数组可以存放四个函数的地址 函数指针的数组
int(*parr[4])(int, int) = { Add,Sub,Mul,Div };
//parr和[]先结合,说明parr1是数组,数组的内容是int(*)()类型的函数指针
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d\n", parr[i](2, 3));
}
}
char* my_strcpy(char* dest, const char* src);
//1、写一个函数指针pf,指向my_strcpy
char* (*pf)(char*, const char*) = my_strcpy;
//2、写一个函数指针数组pfArr,能够存放4个my_strcpy函数的地址
char* (*pfArr[4])(char*, const char*) = { my_strcpy ,my_strcpy ,my_strcpy ,my_strcpy };
指向函数指针数组的指针
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*pfArr[4])(int, int);//pfArr是一个数组 函数指针数组
int(*(*ppfArr)[4])(int, int) = &pfArr;
//ppfArr是一个数组指针,(指向数组的指针),指针指向的数组有四个元素
//指向的数组的每个元素的类型是一个函数指针
}
回调函数 通过函数指针调用的函数 ,将函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向函数时,我们说这就是回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对事件或条件进行响应。
void类型的指针
int main()
{
int a = 10;
char ch = 'w';
void* p = &a;
p = &ch;
//*p = 0;//err
//p++;//err
//1、void*类型的指针可以接收任意类型的地址
//2、void*类型的指针不能解引用操作
//3、void*类型的指针不能进行+-整数的操作
}
qsort进行排序
struct Stu
{
char name[20];
int age;
};
int cmp_int(const void* e1, const void* e2)
{
//比较两个整型值的
return *(int*)e1 - *(int*)e2;
}
void test1()
{
int arr[10] = { 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);
//第一个参数:代排序数组的首元素地址
//第二个参数:代排序数组的元素个数
//第三个参数:代排序数组的每个元素的大小 单位字节
//第四个参数:是函数指针,比较两个元素函数的地址 -这个函数使用者自己实现,函数指针的两个参数:带比较的两个元素的地址
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int cmp_float(const void* e1, const void* e2)
{
//比较两个浮点型的
if (*(float*)e1 == *(float*)e2)
return 0;
else if (*(float*)e1 > *(float*)e2)
return 1;
else
return -1;
}
void test2()
{
float f[10] = { 9.0,8.0,7.0,6,5,4,3,2,1,0 };
int sz = sizeof(f) / sizeof(f[0]);
qsort(f, sz, sizeof(f[0]), cmp_float);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%lf ", f[i]);
}
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test3()
{
struct Stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s ", s[i]);
}
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
//比较名字就是比较字符串
//字符串比较用strcmp
return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);
}
void test4()
{
struct Stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s ", s[i]);
}
}
void Swap(char* buf1, char* buf2, int width)//两个元素进行交换,一个字节一个字节进行交换,交换的长度是width
{
int i = 0;
for (i = 0; i < width; i++)
{
//第一个元素的第一个字符与第二个元素的第一个字符进行交换
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;//第一个元素第二个字节指针的位置,此后循环一次向后移动一个字节
buf2++;
}
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(void*e1,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)//指针+-整数 看指针的类型 跳过几个字节
{
//看函数返回值如果大于0,则交换
//base指向首元素的地址,将base强制转换为char*,一个字节,+width元素的长度,则表示跳过这个元素的长度到下一元素的地址
//j=0,实现第一个元素跟第二个元素比较,j=1实现第二个元素和第三个元素比较,以此类推
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
void test5()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
}
void test6()
{
struct Stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
int main()
{
test1();
test2();
test3();
test4();
test5();
test6();
}
指针打印数组内容
void print(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
}
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
print(p, sz);
}
面试题
int main()
{
int arr[] = { 1,2,3,4,5 };
short* p = arr;//短整型指针变量 占2个字节
int i = 0;
for (i = 0; i < 4; i++)//一次两个字节访问四次也就是二个整型,将数组前两个整型置为0
{
*(p + i) = 0;//指正+-整数 看要指针类型
}
for (i = 0; i < 5; i++)
{
printf("%d ", arr[i]);//打印整个数组 0 0 3 4 5
}
}
int main()
{
int a = 0x11223344;// 00 33 22 11
char* pc = (char*)&a;
*pc = 0;//只能操作1个字节
printf("%x\n", a);//11 22 33 00
}
int main()
{
int a[5][5];
int(*p)[4];//数组指针 指针指向的每个数组有四个元素
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
}
解析:
&a[4][2]是数组第五个一维数组的第三个元素,取其地址
将a强制赋值给p,p[4][2]=((p+4)+2)
p指向a首元素的地址,也就是第一个数组的地址,p+4将p向后移动4个数组,每个数组4个整型,(p+4)解引用出数组名,数组名指向该数组的第一个元素,(p+4)+2,向后移动两个元素,((p+4)+2)解引用处这个元素,再取地址, 也就是该二维数组第四个一维数组第四个元素,两地址相减=-4-4
10000000000000000000000000000100 -原码
11111111111111111111111111111011 -反码
11111111111111111111111111111100-补码
地址是无符号,将补码看成无符号,补码与原码相同
FFFFFFFC
int main()
{
int arr[2][5] = {1,2,3,4,5,6,7,8,9,10};
int* ptr1 = (int*)(&arr + 1);
int* ptr2 = (int*)(*(arr + 1));
printf("%d,%d\n", *(ptr1 - 1), *(ptr2 - 1));//10 5
}
解析: 12345 678910 &arr,取整个数组的地址,&arr+1跳过整个数组,将数组指针强制转换成整型指针
ptr1-1向后移动一个整型指针,此时指向10的地址,(ptr-1)解引用取出元素10
arr指向数组的首元素地址,也就是第一行数组的地址,arr+1指向第二行数组的地址,(arr+1)解引用处数组名,相当于a[1],将二维数组看成一维数组,指向a[1]首元素6的地址,ptr2-1向后移动一个地址,指向5的地址,解引用出5
int main()
{
char* a[] = { "work","at","alibaba" };//常量字符串将首字母的元素地址放到指针中
char** pa = a;
pa++;
printf("%s\n", *pa);
}
解析:指针数组a,存放每个字符串首元素地址,每个数组元素的类型是char*,pa是指向指针数组首元素地址的指针,类型是char**,pa++,跳过一个char*类型的,指向指针数组第二个元素的地址,也就是a的地址,打印字符串at
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1] + 1);
}