指针
内存会划分为小的内存单元,每个内存单元都会有一个编号,这个编号被称为地址。这个地址也叫指针。
内存单元 = 地址 = 指针
指针或者地址,要存储,就可以放到:指针变量中。
指针的大小是固定的4/8个字节(32/64位平台)
指针是有类型,指针的类型决定了指针的+整数的步长,指针解引用的时候的权限。
字符指针
int main()
{
char ch ='w';
char * pc =&ch;
*pc = 'a';
print("%c\n",ch);//a
return 0;
}
int main()
{
const char * p ="abcdef";
//p是指针变量,在x86环境下,是四个字节。
// 将“abcdef”常量表达式的起始字符的地址放在p里面
print("%c",*p);//a
print("%s\n",p);//打印字符串给与首字符地址就可以了
//"abcdef"是常量字符串不能被修改
//*p = w;会崩溃
}
int main()
{
const char * p1 = "abcdef";
const char * p2 = "abcdef";
char arr1[] = "abcdef";
char arr2[] = "abcdef";
if( p1 == p2)
{
printf("p1 == p2\n")
}
else
{
printf("p1 !=p2\n")
}
if(arr1 == arr2)
{
printf("arr1 == arr2");
}
else
{
printf("arr1 != arr2");
}
return 0;
}
指针数组
指针数组:存放指针的数组
整形数组:存放整形的数组
字符数组:存放字符的数组
int main()
{
//int * arr[10];//存放整形指针的数组
//char * ch[10];//存放字符指针的数组
int a = 10;
int b = 20;
int c = 30;
int * p1 = &a;
int * p2 = &b;
int * p3 = &c;
int * arr[3] = {&a,&b,&c}; //arr就是一个指针数组
for(i = 0;i<3;i++)
{
printf("%d",* (arr[i]));
}
return 0;
}
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 * parr[3] ={arr1,arr2,arr3};
for(int i = 0;i<3;i++)
{
for(int j = 0; j<5; j++)
{
printf("%d ",parr[i][j]);
//printf("%d ",parr[i]+j);
}
printf("\n");
}
//parr 是指针数组
}
数组指针
是指针,是指向数组的指针
int main()
{
int a =10;
int * p ==&a;//整形指针 - 指向整形的指针,存放整形地址的指针
char ch ='w';
char * pc = &ch;//字符指针 - 指向字符的指针,存放字符地址的指针
//数组指针 - 指向数组的指针
int * p1[10]; //p1是数组,指针数组
int (*p2)[10];//p2现在是指针;指向一个10个元素的数组,每个元素是int
}
int mian()
{
int a = 10;
int * p = &a;
int arr[10] = {0};
//数组名是首元素地址;
//&arr 取出的是数组的地址
printf("%p\n",arr);//int* +1 = +4个字节
printf("%p\n",&arr[0]);//int* +1 = +4 个字节
printf("%p\n",&arr);//+1 直接跨越整个数组
// int (*p)[10] == &arr;
//p是一个指针,指向了数组
//所以p叫一个数组指针
}
数组名:
通常情况下,数组名都是数组首元素的地址。
有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
数组指针的应用
void print1(int arr[],int sz)
{
for (int i = 0;i<sz;i++)
{
printf("%d ",*(arr+i));
}
}
void print2(int(*p)[10],int sz)
{
for (int i = 0;i<sz;i++)
{
//*p相当于数组名,数组名又是首元素的地址,所以*p就是&arr[0]
printf("%d",*(*p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);
print1(arr,sz);
}
void print2(int arr[3][5],int c,int r)
{
for(int i = 0;i < c;i++)
{
for(int j =0;j<r;j++)
{
printf("%d",arr[i][j]);
}
}
}
int print3(int (*p)[5],int c ,int r)
{
int i = 0;
for (i = 0;i<c;i++)
{
for(int j =0;j<r;j++)
{
//p+i是指向第i行的
//*(p+i)相当于拿到了第i行,也相当于第i行的数组名
//数组名表示首元素的地址,*(p+i)相当于第i行的第一个元素的地址
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}};
//二维数组的首元素是{1,2,3,4,5},五个元素的数组
//写一个函数,打印arr数组
print2(arr,3,5);
return 0;
}
int arr[5] //arr是一个整形的数组,每个元素是int类型的,有5个元素
int* parr1[10] //指针数组,数组10个元素,每个元素的类型是int*
int(*parr2)[10] //数组指针 指向数组的指针,指向的数组有10个元素,每个元素的类型是int
int (*parr3[10])[5]; //parr3 是一个数组,数组有10个元素,每个元素是int(*)[5] 存放数组指针的数组
数组参数、指针参数
一维数组传参
void test(int arr[10])
{
pass;
}
void test(int arr[]) //一维数组形参部分可以省略
{}
void test(int arr[100])//不建议,但是没错
{}
void test(int * p)
{}
void test2(int* arr2[])
{}
void test2(int** p)
{}
int main(){
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2)
}
二维数组传参
形参写成数组的形式可以的
二维元素的数组名是第一行的数组地址
二级指针是用来存放一级指针的地址
数组指针是用来存放数组的地址的
void test(int arr[][5])//行可以省略,列不可以省略
{}
void test(int (*p)[5])
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
一级指针传参
函数的参数为一级指针,可以为:一维数组名,一级指针,一维数组的地址
#include<stdio.h>
void test(int * p,int sz)
{
for(int i = 0; i <sz; i++ )
{
printf("%d",*(p+i))
}
}
int mian()
{
int arr[10] ={1,2,3,4,5,6,7,8,9,10};
int * p =&arr;
int n =sizeof(arr)/sizeof(arr[0]);
test(p,sz)
return 0;
}
二级指针传参
void test(char** ppc)
{
}
int main()
{
char ch = 'w';
char * pc = &ch;
char ** pc = &pc;
char* arr[4];
test(arr);
test(&pc);
test(ppc)
}
//int main()
//{
// char a = 'w';
// char * pa =&a;
// char** ppa =&pa;//ppa就是一个二级指针
//
// test(ppa);
//}
void test1(int(*p)[5])
{}
void test2(int(*p)[3][5])
{
*p;// 第一行的地址
}
int main()
{
int arr[3][5];
test1(arr); //传递的第一行的地址
test2(&arr); //传递的是整个二维数组的地址
}
函数指针
函数指针:指向函数的指针
int Add(int x ,int y)
{
return x+y;
}
int test(char* str)
{
;
}
int main()
{
int arr[10];
int (*p)[10] =&arr; // p是一个数组指针变量
printf("%p\n",&Add);//函数的地址
printf("%p\n",Add);//函数的地址 写法不同,&Add与Add含义一模一样
int (*pf)(int,int) = Add;//pf就为函数指针变量
int ret = (*pf)(2,3);
printf("%d",ret); //5
int rets = pf(2,3); //*的含义并不重要
printf("%d",rets); //5
int (*pt)(char*) = test;
return 0;
}
(*(void(*)())0)();
// void(*)() 是函数类型
// (void(*)())强制类型转换
// (*(void(*)())0)()
//1.首先吧0强制转换成为一个函数指针类型,这意味着0地址处放一个返回类型是void,无参的一个函数
//2.调用0地址处的这个函数
void (* signal(int,void(*)(int)))(int);
//signal是一个函数的声明
//signal函数的参数,第一个是int类型,第二个是为void(*)(int)的函数指针类型
//函数返回类型为函数指针 void(*)(int)
typeof void(* pf_t)(int);
//pf_t 即为 signal(int,pf_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()
{
//指针数组
//字符指针数组
char* arr[5];
//整形指针数组
int* arr2[4];
//函数指针数组
int (*pf1)(int,int) = Add;
int (*pf2)(int,int) = sub;
int (*pf3)(int,int) = Mul;
int (*pf4)(int,int) = Div;
int (*pf[4])(int,int)={Add,Sub,Mul,Div};//函数指针数组
for(int i =0;i<4;i++)
{
int ret = pf[i](8,2);
printf("%d",ret)
}
return 0;
}
指向函数指针数组的指针
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 mian()
{
// int arr[10] = {1,2,3,4,5,6,7,8,9,10}
// int (*p)[10] = &arr; //p是数组指针
// int* arr2[5] 指针数组
// int*(*p2)[5] 指向指针数组的指针
int (*pf)(int,int) = &Add; //函数指针
int (*pfarr[4])(int,int) = {Add,Sub,Mul,Div}; //函数指针数组
int (*(*p3)[4])(int,int) = &pfarr; //指针:指向函数指针数组的指针
//p3是一个指针变量,p3指向函数指针数组的指针
//*p3 --> pfarr
//(*p3)[i] -->pfarr[i]
//32位 4个字节 64位 8个字节
return 0;
}
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};
int(*(*p3)[4])(int,int) =&parr;//p3是一个函数指针数组的指针
for (int i = 0;i<4;i++)
{
//*p3
//*(p3+0)
//p3[0]
//p3[0][i]
//p3是整个数组的地址,&parr是取出的整个数组的地址
int ret = (*p3)[i] (3,4);
printf("%d\n",ret);
}
//7,-1,12,0
}
回调函数
回调函数是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特地的事件或条件发生时由另外的一方调用的,用于该事件或条件进行响应。
void test()
{
printf("hehe");
}
void print_hehe(void(*p)())
{
if(1)
p();
}
int main()
{
print_hehe(test);//hehe
return 0;
}
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(*p)(int,int)) // 函数指针的参数必须一样
{
int x = 0;
int y = 0;
scanf("%d %d",&x,&y);
ret = pf(x,y);
printf("ret = ",ret);
}
void menu()
{
printf("0.exit 1.sub 2.Sub 3.Mul 4.Div");
}
//计算器
int mian()
{
int input = 0;
do
{
menu();
printf("请选择");
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;
}
}while(input);
}
qsort函数的实现
qsort是一个库函数,基于快速排序算法实现的一个排序算法
#include<stdio.h>
void bubble_sort(int arr[],int sz)
{
for(int i =0 ; i<sz;i++)
{
for(int j = 0;j<sz-1-i;j++)
{
if arr[j]>arr[j+1]
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
void print_arr(int arr[],int sz)
{
for(int i = 0;i<sz;i++)
{
printf("%d",arr[i]);
}
}
int main()
{
int arr[] = {9,8,7,6,5,4,3,2,1,0};
int sz = sizeof(arr) / sizeof(arr[0]);
//排序为升序
bubble_sort(arr,sz);
print_arr(arr,sz);
}
int main()
{
int a =10;
void* pf =&a; //void类型的指针可以接受任何类型的指针
return 0;
}
void qsort(void * base,size_t num,size_t width,int(*cmp)(const void*e1,const void * e2))
{
//void * base 待排序数据的起始位置
//size_t:无符号整形
// num:数组的个数
// width:一个元素的字节大小
//cmp :比较函数,要求qsort的使用者自定义一个比较函数
//排序的整形数据:用>,<
//排序的结构体数据:不方便直接使用>,<比较了
//使用者根据实际情况,提供一个函数,实现2个数据的比较
//e1,e2:待比较的两个元素的地址
//返回值:int 0,>0,<0
void test1()
{
int arr[] = {9,8,7,6,5,4,3,2,1,0};
int sz = sizeof(arr) / sizeof(arr[0]);
//排序为升序
bubble_sort(arr,sz);
print_arr(arr,sz);
}
int cmp_int(const void *e1,const void * e2)
{
//void是无确切类型的指针,不可以直接解引用
if (*(int*)e1 > *(int*e2))
return 1;
if (*(int*)e1 = *(int*e2))
return 0;
if (*(int*)e1 < *(int*e2))
return -1;
//return (*(*int)e1-*(*int)e2)
}
void test2()
{
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)
}
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 Swap(char* buf1,char* buf2,int width)
{
for(int i = 0 ;i<width;i++)
{
char temp = *buf1;
*buf1 = *buf2;
*buf2 =temp;
*buf1++;
*buf2++;
}
}
void bubble_sort(void* base,int num,int width,(*cmp)(const void *e1,const void * e2))
{
for(int i = 0 ,i<num-1;i++)
{
for(int j = 0;j < sum-1-i;j++)
{
if(cmp((char*)base+width*j,(char*)base+width*(j+1))>0)
{
//交换
swap((char*)base+width*j,(char*)base+width*(j+1),width);
}
}
}
}
//使用qsort排序结构体
void test3()
{
struct Stu arr[3] = {{"zhangsan",20,55.5},{"lisi",30,88.0},{"wangwu",10,90.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);
bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
}
}
面试题
sizeof:操作符,计算的是对象所占内存的大小-单位是字节,size_t。不在乎内存中存放的是什么只在乎内存的大小
strlen:库函数,求字符串长度,从给定的地址向后访问字符,统计\0之前出现的字符个数
1.数组名通常来说是数组首元素的地址
但是有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
int main()
{
int a[] = {1,2,3,4}
printf("%d",sizeof(a)); // 16
printf("%d\n",sizeof(a+0)); //4/8 a+0是数组第一个元素的地址,是地址大小就是4或8个字节
printf("%d",sizeof(*a)); // 4 a:表示首元素地址 *a:表示第一个元素
printf("%d",sizeof(a+1)); // 4/8 a+1 数组第二个元素的地址
printf("%d",sizeof(a[1])); // 4 计算的是第二个元素的大小
printf("%d",sizeof(&a)); // &a取出的是数组的地址,数组的地址也是地址,4或者8个字节
printf("%d",sizeof(*&a)); // 16 &a取出的是整个数组的地址,*&a解引用得到的是整个数组
printf("%d",sizeof(&a+1)); // 4/8 &a取出的是整个数组,+1跳过整个数组,产生的4后边位置的地址
printf("%d",sizeof(&a[0])); //4/8 取出的是数组第一个元素的地址
printf("%d",sizeof(&a[0]+1)); // 4/8取出的是数组第二个元素的地址
}
// 字符数组
// strlen() 的输入是指针
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n",sizeof(arr)); //不要与strlen()搞混。 6
printf("%d\n",sizeof(arr+0)); //4/8 取出首元素的地址
printf("%d\n",sizeof(*arr)); //1 首元素
printf("%d\n",sizeof(arr[1])); //1 首元素
printf("%d\n",sizeof(&arr)); //4/8 取出的是整个数组的地址
printf("%d\n",sizeof(&arr+1)); //4/8 &arr+1是从数组地址开始向后跳过了整个数组产生的一个地址
printf("%d\n",sizeof(&arr[0]+1)); //4//8 数组第二个元素的地址
printf("%d\n",strlen(arr)); // 随机值,遇到‘\0’才停止
printf("%d\n",strlen(arr+0)); // 随机值,arr+0还是数组首元素的地址
printf("%d\n",strlen(*arr));// err arr是首元素地址,*arr就是数组的首元素,就为a,'a'-97,野指针
printf("%d\n",strlen(arr[1])); //err 'b' -98
printf("%d\n",strlen(&arr)); //随机值
printf("%d\n",strlen(&arr+1)); //随机值
printf("%d\n",strlen(&arr[0]+1)); //随机值
return 0;
}
int main()
{
char arr[] = "abcdef"
printf("%d\n",sizeof(arr)); // 7 计算的是整个数组的大小包括'\0'
printf("%d\n",sizeof(arr+0)); // 4/8 arr+0 是数组首元素
printf("%d\n",sizeof(*arr)); // 1 arr数组的首元素
printf("%d\n",sizeof(arr[1])); // 1 arr的第二个元素
printf("%d\n",sizeof(&arr)); // 4/8 &arr数组的地址,地址大小就是4/8
printf("%d\n",sizeof(&arr+1)); // 4/8 &arr+1 是\0后面的地址
printf("%d\n",sizeof(&arr[0]+1)); // 4/8 -&arr[0] +1 是数组第二个元素的地址
printf("%d\n",strlen(arr)); // 6 遇到\0停止
printf("%d\n",strlen(arr+0)); // 6
printf("%d\n",strlen(*arr));// err arr是首元素地址,*arr就是数组的首元素,就为a,'a'-97,野指针
printf("%d\n",strlen(arr[1])); //err 'b' -98
printf("%d\n",strlen(&arr)); //6
printf("%d\n",strlen(&arr+1)); //随机值
printf("%d\n",strlen(&arr[0]+1)); //5
return 0;
}
int main()
{
char* p = "abcdef"; //abcdef是常量字符串,p存储的是a的地址
printf("%d\n",sizeof(p));//4/8
printf("%d\n",sizeof(p+1));// 4/8 p+1存放b的地址
printf("%d\n",sizeof(*p)); // 1 - *p就是'a'
printf("%d\n",sizeof(p[0])); // 1 p[0] *(p+0) ->*p
printf("%d\n",sizeof(&p));// 4/8 &p是指针变量p在内存中的地址
printf("%d\n",sizeof(&p+1)); // 4/8 &p+1 跳过p之后的地址
printf("%d\n",sizeof(&p[0]+1)); //4/8 取出的是b的地址
printf("%d\n",strlen(p)); // 6 p就是a的地址
printf("%d\n",strlen(p+1)); // 5 p+1 从b开始
printf("%d\n",strlen(*p)); // err 野指针
printf("%d\n",strlen(p[0])); //err 野指针
printf("%d\n",strlen(&p)); // 随机值
printf("%d\n",strlen(&p+1)); //随机值
printf("%d\n",strlen(&p[0]+1)); //5 &p[0]就是a的地址
}
int main()
{
int a[3][4] = {0};
printf("%d\n",sizeof(a)); //a是整个数组 12*4=48字节
printf("%d\n",sizeof(a[0][0])); // 4字节 第一行第一个元素的大小
printf("%d\n",sizeof(a[0])); // a[0]是第一行的数组名,计算的是第一行的元素 16字节
printf("%d\n",sizeof(a[0]+1)); // a[0]作为第一行的数组名。并没有单独放在sizeof内部或者取地址
//a[0]就是数组首元素的地址,就是第一行第一个元素的地址,a[0]+1就是第一行第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1))); // 4个字节
printf("%d\n",sizeof(a+1));//a表示首元素的地址,a是二维数组,首元素的地址就是第一行的地址,
//a+1就是第二行的地址 4/8个字节
printf("%d\n",sizeof(*(a+1)); //16 *(a+1)->a[1]对第二行解引用
printf("%d\n",sizeof(&a[0]+1)); // 4/8 -a[0]取出的是第一行的数组名,&a[0]取出的是第一行的地址
printf("%d\n",sizeof(*(&a[0]+1))); //16 - 第二行的地址解引用就是第二行
printf("%d\n",sizeof(*a)); // 16字节 a就是首元素的地址,就是第一行的地址 *a就是第一行
printf("%d\n",sizeof(a[3])); // 16个字节 并没有访问
//arr[i] -> * (arr+i)
}
struct Test
{
int Num;
char* pc Name;
short sDate;
char cha[2];
short sBa[4];
}* p;
//假设p 的值为0x100000,如下的表达式的值分别是多少?
//已知 ,结构体Test类型的变量大小是20字节
int main()
{
p = (struct Test*)0x100000;
printf("%p\n",p + 0x1); //相当于+20,16进制, 00100014
printf("%p\n",(unsigned long)p + 0x1); // 强制转换为整形类型,不为指针,+1就加一个字节 00100001
printf("%p\n",(unsigned int*)p + 0x1); //整形指针,+1,跳过四个字节 00100004
}
int main()
{
int a[4] = {1,2,3,4};
int* ptr1 = (int*)(&a+1);
int* ptr2 = (int*)((int)a+1);
printf("%x,%x",ptr1[-1],*ptr2);//1 02 00 00 00->20000000
return 0;
}
#include<stdio.h>
int main()
{
int a[3][2] = {(0,1),(2,3),(4,5)};//逗号表达式,1,3,5
int * p;
p =a[0];
printf("%d",p[0]);
return 0;
}
int main()
{
int a[5][3];
int(*p)[4];
p = a;
printf("%p %d\n",&p[4][2]-&p[4][2],&p[4][2]-&a[4][2]);//指针减指针是元素的个数 -4
//FFFFFFFC -4
return 0;
}
int main()
{
int aa = {1,2,3,4,5,6,7,8,9,10 };
int * ptr1 = (int*)(&aa+1);
int * ptr2 = (int*)(*(aa+1));
printf("%d %d",*(ptr1-1),*(ptr2-1)); //10 5
return 0;
}
#include <stdio.h>
int main()
{
char* a[] = {"work",'at','alibaba'};
// work\0 at\0 alibaba\0
//a数组有三个元素 a[0]是work\0的w的地址,a[1]是at\0的a的地址,a[2]是alibaba\0的a的地址
//pa指向a[0]
char** pa = a;
pa++;
printf("%s\n",*pa);//at
return 0;
}
int main()
{
return 0;
}
int main()
{
char* c[] = {"ENTER","MEW","POINT","FIRST"};
char** cp[] = { c + 3,c + 2,c + 1,c};
char*** cpp = cp;
// c : E N P F
// cp :F P N E
// cpp:F
printf("%s\n",**++cpp); //POINT
printf("%s\n",*-- * ++cpp + 3); //ER
printf("%s\n",*cpp[-2]+3); //ST
printf("%s\n",cpp[-1][-1]+1); //EW
return 0;
}