前言
本文主要讲解了c语言中指针的进阶内容,在初阶的基础上进行延伸.
内存会划分为一个个的内存单元
每个内存单元都有一个独立的编号,-编号也称为地址
地址在C语言中也被称为指针
指针(地址)需要存储起来-存储到变量中,这个变量也就被称为指针变量
指针(地址)的大小固定说4/8个字节(32位平台/64位平台)
地址是物理的电线上产生
32为机器-32根地址线 -1/0
32个0/1组成的二进制序列,把这个二进制序列作为地址,32bit位才能存储这个地址
也就是需要4个字节才能存储,所以指针变量的大小是4个字节
同理64位机器上,地址的大小是64个0/1组成的二进制序列,需要64bit位存储,也就是8个字节。所以指针变量的大小是8个字节。
一、字符指针
在指针的类型中我们知道有一种指针类型为字符指针char*
//在指针的类型中我们知道有一种指针类型为字符指针char*
int main()
{
char ch = 'w';
char* pc = &ch; //pc就是字符指针
*pc = 'a';
char arr[] = "abcdef"; //[a b c d e f \0]
const char*p = "abcdef"; //常量字符串
printf("%s\n",p); //abcdef
printf("%c\n",*p); //a
return 0;
}
经典面试题小试牛刀
int main()
{
char str1[] = "hello xiaofan.";
char str2[] = "hello xiaofan.";
const char* str3 = "hello xiaofan.";
const char* str4 = "hello xiaofan.";
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;
}
二、数组指针
数组指针是数组还是指针 ----是指针哦
数组指针类比:
整型指针-指向整型变量的指针,存放整型变量的地址的指针变量
字符指针-指向字符变量的指针,存放字符变量的地址的指针变量
数组指针-指向数组的指针,存放的是数组的地址的指针变量
//数组指针
//数组名的理解
//数组名是数组首元素的地址
//2个例外:1.sizeof(数组名),这里的数组名不是数组首元素的地址,数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
//2.&数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址
//除此之外,所有的地方的数组名都是数组首元素的地址
// int main()
// {
// int arr[10] = {0};
// printf("%p\n",arr); // int*
// printf("%p\n",arr+1);
// printf("%p\n",&arr[0]); //int*
// printf("%p\n",&arr[0]+1);
// printf("%p\n",&arr); //int(*)[10]
// printf("%p\n",&arr+1);
// return 0;
// }
//对于数组名来说
//&数组名得到的是数组的地址
// int main()
// {
// int arr[10] = {1,2,3,4,5,6,7,8,9,10};
// //数组的地址,存储到数组指针变量
// //int[10] *p = &arr; //err
// int(*p)[10] = &arr;
// }
// 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 ",*(p+i));
// }
// }
// int main()
// {
// int arr[10] = {1,2,3,4,5,6,7,8,9,10};
// int (*p)[10] = &arr; //*&arr -> arr
// int i = 0;
// for (i = 0;i<10;i++)
// {
// printf("%d ",*((*p)+i));
// printf("%d ",(*p)[i]);
// }
// }
//数组指针怎么使用呢?一般在二维数组上才方便
//二维数组传参,形参是二维数组的形式
// void Printf(int arr[3][5],int r,int 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("\n");
// }
// }
// int main()
// {
// int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
// Printf(arr,3,5);
// return 0;
// }
//二维数组传参,形参是指针的形式
// void Printf(int (*p)[5],int r,int c)
// {
// int i = 0;
// for(i = 0; i < r; i++)
// {
// int j = 0;
// for (j = 0; j < c; j++)
// {
// printf("%d ",*(*(p+i)+j));
// }
// }
// }
// //二维数组的每一行可以理解为二维数组的一个元素
// //每一行又是一个一维数组
// //二维数组其实是一维数组的数组
// //二维数组的数组名,也是数组名,数组名就是数组首元素地址
// //arr - 首元素地址 - 第一行的地址 -一维数组的地址 -数组的地址
// int main()
// {
// int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
// //arr数组名是首元素地址
// Printf(arr,3,5);
// return 0;
// }
//一维数组传参,形参的部分可以是数组,也可以是指针
// void test1(int arr[5],int sz)
// {
// }
// void test2(int* p,int sz)
// {
// }
// int main()
// {
// int arr[5] = {0};
// test1(arr,5);
// test2(arr,5);
// return 0;
// }
//二维数组传参,形参的部分可以数组,也可以是指针
void test3(char arr[3][5],int r,int c)
{
}
void test4(char(*p)[5],int r,int c)
{
}
int main()
{
char arr[3][5] = {0};
test3(arr,4,5);
test4(arr,3,5);
return 0;
}
三、指针数组
指针数组
整型数组-存放整型的数组
字符数组-存放字符的数组
指针数组-存放指针的数组
// int main()
// {
// int* arr[10];//存放整型指针的数组
// char* arr2[10]; //一级字符指针的数组
// char **arr3[10];//二级字符指针的数组
// return 0;
// }
//使用指针数组模拟实现二维数组
int main()
{
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};
int arr3[] = {3,4,5,6,7};
//指针数组
int* arr4[3] = {arr1,arr2,arr3};
printf("%d\n",arr4[1][1]);
int i = 0;
for ( i = 0; i < 3; i++)
{
/* code */
int j = 0;
for(j=0;j<5;j++)
{
printf("%d ",arr4[i][j]);
//printf("%d ",*(arr4+i));
}
printf("\n");
}
return 0;
}
四、数组传参和指针传参
在写代码的时候难免要把数组或者指针传给函数,那函数的参数该如何设计呢?
一维数组传参
二维数组传参
一级指针传参
二级指针传参
//二维数组传参,形参是二维数组的形式
// void Printf(int arr[3][5],int r,int 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("\n");
// }
// }
// int main()
// {
// int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
// Printf(arr,3,5);
// return 0;
// }
//二维数组传参,形参是指针的形式
// void Printf(int (*p)[5],int r,int c)
// {
// int i = 0;
// for(i = 0; i < r; i++)
// {
// int j = 0;
// for (j = 0; j < c; j++)
// {
// printf("%d ",*(*(p+i)+j));
// }
// }
// }
// //二维数组的每一行可以理解为二维数组的一个元素
// //每一行又是一个一维数组
// //二维数组其实是一维数组的数组
// //二维数组的数组名,也是数组名,数组名就是数组首元素地址
// //arr - 首元素地址 - 第一行的地址 -一维数组的地址 -数组的地址
// int main()
// {
// int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
// //arr数组名是首元素地址
// Printf(arr,3,5);
// return 0;
// }
//一维数组传参,形参的部分可以是数组,也可以是指针
// void test1(int arr[5],int sz)
// {
// }
// void test2(int* p,int sz)
// {
// }
// int main()
// {
// int arr[5] = {0};
// test1(arr,5);
// test2(arr,5);
// return 0;
// }
//二维数组传参,形参的部分可以数组,也可以是指针
// void test3(char arr[3][5],int r,int c)
// {
// }
// void test4(char(*p)[5],int r,int c)
// {
// }
// int main()
// {
// char arr[3][5] = {0};
// test3(arr,4,5);
// test4(arr,3,5);
// return 0;
// }
//一维数组传参
// void test(int arr[]) //true
// {}
// void test(int arr[10]) //true
// {}
// void test(int* arr) //true
// {}
// void test2(int* arr[20]) //true
// {}
// void test2(int** arr)
// {}
// int main()
// {
// int arr[10] = {0}; //存放整型
// int *arr2[20] = {0}; //存放地址
// test(arr);
// test2(arr2);
// }
//一级指针传参
// void print(int* p,int sz)
// {
// int i = 0;
// for(i = 0;i<sz;i++)
// {
// printf("%d\n",*(p+i));
// }
// }
// int main()
// {
// int arr[10] = {1,2,3,4,5,6,7,8,9};
// int *p = arr;
// int sz = sizeof(arr)/sizeof(arr[0]);
// //一级指针p,传给函数
// print(p,sz);
// return 0;
// }
//二级指针传参
void test(int** ptr)
{
printf("num = %d\n",**ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp); //10
test(&p); //10
return 0;
}
五、函数指针
函数指针-指向函数的指针
数组指针-指向函数的指针
//函数指针
// int Add(int x,int y)
// {
// return x+y;
// }
// int main()
// {
// int arr[10] = {0};
// int (*pa)[10] = &arr;
// // //&arr;
// // printf("%p\n",&Add);
// // printf("%p\n",Add);
// //函数名是函数的地址
// //&函数名也是函数的地址
// int (*pf)(int,int) =&Add; //pf是函数指针变量 int(*)(int int)是函数指针类型
// }
// void test(char* pc,int arr[10])
// {}
// int main()
// {
// void (*pf)(char*,int[10]) = &test;
// return 0;
// }
int Add(int x,int y)
{
return x+y;
}
int main()
{
//int (*pf)(int,int) = &Add;
int (*pf)(int,int) = Add;
int r = Add(3,5);
printf("%d\n",r);
int m = (*pf)(4,5);
printf("%d\n",m);
return 0;
}
//void (*p)() -p是函数指针
//void (*)()是函数指针类型
// int main()
// {
// //调用0地址处的函数
// //1.将0强制类型转换为void(*)() 类型的函数指针
// //2.调用0地址处的这个函数
// ( *( void (*)())0)();
// }
// typedef unsigned int uint;
// typedef int* ptr_t;
// //typedef int(*)[10] parr_t; //err
// typedef int(*parr_t)[10] ; //true
// typedef int (*pf_t)(int,int);
// int main()
// {
// uint u1;
// ptr_t p1;
// int* p2;
// //parr_t[10];
// //pf_t(2,3);
// return 0;
// }
int main()
{
//signal是一个函数声明
//signal函数有2个参数,第一个参数是int,第二个参数类型是void(*)(int) 函数指针类型
//该函数指针指向的函数有一个int类型的参数,返回类型是void
//signal 函数的返回类型也是 void(*)(int) ,该函数指针指向的函数有一个int类型的参数,返回类型是void
typedef void(*pf_t)(int);
pf_t signal(int ,pf_t);
void (* signal(int,void(*)(int)))(int);
return 0;
}
六、函数指针数组
整型指针放在一个数组中
int* arr[5]; //整型指针数组
char* arr2[5];//字符指针数组
函数指针数组:数组的每个元素是函数指针类型
//函数指针数组
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 (*pf1)(int , int) = Add;
// int (*pf2)(int , int) = Sub;
// int (*pf3)(int , int) = Mul;
// int (*pf4)(int , int) = Div;
// //函数指针数组
// int (*pfArr[4])(int , int) = {Add,Sub,Mul,Div};
// return 0;
// }
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 = 0;
// int x = 0;
// int y = 0;
// int ret = 0;
// do
// {
// menu();
// 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("选择错误重新选择");
// break;
// }
// } while(input);
// return 0;
// }
//函数指针的方式
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
//函数指针数组的使用 - 转移表
int (*pfArr[5])(int,int) = {NULL,Add,Sub,Mul,Div};
do
{
menu();
printf("请选择:>");
scanf("%d",&input);
if(input >=1 && input <=4)
{
printf("请输入两个操作数:");
scanf("%d %d",&x,&y);
ret = pfArr[input](x,y);
printf("ret = %d\n",ret);
}
else if(input == 0)
{
printf("退出计算器\n");
}
else
{
printf("选择错误,重新选择\n");
}
} while(input);
return 0;
}
七、指向函数指针数组的指针
int(*pf)(int,int);//函数指针
int(*pfArr[4])(int,int);//函数指针数组
int(*(*p)[4](int,int) = &pfArr);//函数指针数组的地址
p就是指向函数指针数组的指针
void test(const char* str)
{
printf("%\n",str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
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 menu()
// {
// printf("**********************************\n");
// printf("******* 1.add 2.sub ************\n");
// printf("******* 3.mul 4.div ************\n");
// printf("******* 0.exit ************\n");
// printf("**********************************\n");
// }
// void Calc(int (*pf)(int x,int y))
// {
// int x = 0;
// int y = 0;
// int ret = 0;
// printf("请输入两个操作数:\n");
// scanf("%d %d",&x,&y);
// ret = pf(x,y);
// printf("ret = %d\n",ret);
// }
// int main()
// {
// int input = 0;
// int x = 0;
// int y = 0;
// int ret = 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;
// case 0:
// printf("退出计算器\n");
// break;
// default:
// printf("选择错误重新选择");
// break;
// }
// } while(input);
// return 0;
// }
//冒泡排序
//有一组整数,需要排序为升序
//1.两两相邻的元素比较
//2.如果不满足顺序就交换
// int main()
// {
// int arr[10] = {9,8,7,6,5,4,3,2,1,0};
// int sz = sizeof(arr)/sizeof(arr[0]);
// int i = 0;
// //int a = qsort(arr[10]);
// //bubble_sort(arr,sz);
// //趟数
// for(i = 0;i < sz-1; i++)
// {
// //一趟比较
// //两两元素比较
// int j = 0;
// for (j = 0;j <sz-1-i ;j++)
// {
// if(arr[j] > arr[j+1])
// {
// int tmp = arr[j];
// arr[j] = arr[j+1];
// arr[j+1] = tmp;
// }
// }
// }
// for (i = 0; i<sz;i++)
// {
// printf("%d ",arr[i]);
// }
// return 0;
// }
//qsort 函数的特点
//1.快速排序的方法quick
//2.适用于任何类型数据的排序
// void qsort(void *__base, //指向了需要排序的数组的第一个元素
// size_t __nel, //排序的元素个数
// size_t __width,//一个元素的大小,单位是字节
// int (* _Nonnull __compar)(const void *, const void *));//函数指针类型-这个函数指针指向的函数,能够比较base指向数组中的两个元素
int cmp_int(const void*p1,const void*p2)
{
return (*(int*)p1 - *(int*)p2);
}
void print(int arr[],int sz)
{
int i = 0;
for(i = 0;i<sz;i++)
{
printf("%d ",arr[i]);
}
}
//测试qsort排序整型数据
void test1()
{
int arr[10] = {9,8,7,6,5,4,3,2,1};
int sz = sizeof(arr)/sizeof(arr[0]);
//默认是升序的
qsort(arr,sz,sizeof(int),cmp_int);
print(arr,sz);
}
//测试qsort排序结构体数据
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* p1,const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_stu_by_name(const void* p1,const void* p2)
{
return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
}
void test2()
{
struct Stu arr[] = { {"张三",18}, {"李四,20"},{"王五,16"}};
int sz = sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_age);
}
void test3()
{
struct Stu arr[] = { {"张三",18}, {"李四,20"},{"王五,16"}};
int sz = sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_stu_by_name);
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
//void* 指针
//void*类型的指针可以接受任意类型的地址
//这种类型的指针是不能直接解引用操作的
//也不能直接进行指针运算的
// int main()
// {
// int a = 10;
// float f = 3.14f;
// int* pa = &a;
// char* pc = &a;
// void* pv = &a;
// }
//使用冒泡排序的思想,实现一个功能类似qsort函数 bubble_sort()
//1.使用冒泡排序的思想
//2.适用于任意类型数据的排序
//使用冒泡排序的思想,实现一个功能类似qsort函数 bubble_sort()
//1.使用冒泡排序的思想
//2.适用于任意类型数据的排序
void Swap(char* buf1,char* buf2,int size)//交换arr[j],arr[j+1]这两个元素
{
int i = 0;
int tmp = 0;
for(i = 0;i < size;i++)
{
tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
//char*指针一次只能交换一个字节,但是char有四个字节
buf1++;
buf2++;
}
}
void bubble_sort(void* base,int num,int size,int (*cmp)(const void*,const void*))
{
int i = 0;
//趟数
for (i = 0; i < num - i; i++)
{
int j = 0;
//一趟内部比较的对数
for(j = 0; j < num - 1; j++)
{
//假设需要升序,cmp返回>0,交换
if(cmp((char*)base+j*size,(char*)base+(j+1)*size) > 0) //两个元素比较,需要将arr[j],arr[j+1]的地址要传给cmp
{
//交换
Swap((char*)base + j * size,(char*)base + (j+1) * size,size);
}
}
}
}
int cmp_int(const void*p1,const void*p2)
{
return (*(int*)p1 - *(int*)p2);
}
//测试bubble_sort()排序整型数据
void test1()
{
int arr[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);
}
//测试bubble_sort()排序结构体数据
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_age(const void* p1,const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_stu_by_name(const void* p1,const void* p2)
{
return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
}
void test2()
{
struct Stu arr[] = { {"张三",18}, {"李四,20"},{"王五,16"}};
int sz = sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr,sz,sizeof(arr[0]),cmp_stu_by_age);
}
void test3()
{
struct Stu arr[] = { {"zhangsan",18}, {"lisi,20"},{"wangwu,16"}};
int sz = sizeof(arr)/sizeof(arr[0]);
bubble_sort(arr,sz,sizeof(arr[0]),cmp_stu_by_name);
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
九、指针和数组面试题的解析
// 数组名的理解
// 数组名是数组首元素的地址
// 但是有2个例外:
// 1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
// 2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
// int main()
// {
// //一维数组
// int a[] = {1, 2, 3, 4};
// printf("%d\n", sizeof(a)); // 4*4=16
// printf("%d\n", sizeof(a + 0)); //数组名a是数组首元素的地址,a+0还是首元素地址,地址的大小4/8
// printf("%d\n", sizeof(*a));//数组名a是数组首元素的地址,*a就是首元素,int类型大小4个字节
// printf("%d\n", sizeof(a + 1));//数组名a是数组首元素的地址,a+1是第二个元素的地址,地址的大小是4/8
// printf("%d\n", sizeof(a[1]));//4
// printf("%d\n", sizeof(&a));//&a是数组的地址,数组的地址也是地址,是4/8个字节
// printf("%d\n", sizeof(*&a));// --> sizeof(a) ,*&a,取地址解引用a就是a 16
// printf("%d\n", sizeof(&a + 1)); //8
// printf("%d\n", sizeof(&a[0]));// 首元素地址 4/8字节
// printf("%d\n", sizeof(&a[0] + 1));//&a[0]是首元素的地址,&a[0]+1就是第二个元素的地址,地址就是4/8个字节
// }
int main()
{
// 字符数组
char arr[] = {'a', 'b', 'c', 'd', 'e', 'f'};
// printf("%d\n", sizeof(arr)); //计算的是整个数组的大小,单位是字节,总共6个字节
// printf("%d\n", sizeof(arr + 0)); //arr表示数组首元素地址,arr+0还是数组首元素地址,地址就是4/8字节
// printf("%d\n", sizeof(*arr));//arr是首元素地址,*arr是首元素,大小1个字节
// printf("%d\n", sizeof(arr[1]));//1
// printf("%d\n", sizeof(&arr));//&arr是数组的地址 地址就是4/8
// printf("%d\n", sizeof(&arr + 1)); //地址就是4/8
// printf("%d\n", sizeof(&arr[0] + 1)); //地址是4/8
printf("%d\n", strlen(arr)); //因为字符数组arr中没有\0,所以求字符串长度的时候,会一直往后找,产生的结构就是随机值
printf("%d\n", strlen(arr + 0));// arr+0 是首元素的地址,和第一个一样,也是随机值
//printf("%d\n", strlen(*arr));//arr是数组首元素的地址,*arr就是数组首元素 就是'a'-97
//strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,‘a’的ASCII码值就是97,那就是将97作为地址传参
//strlen就会从97这个地址开始统计字符串长度,这就非法访问内存了
//printf("%d\n", strlen(arr[1])); //err
printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,传递给strlen函数后,依然是从数组的第一个元素的位置,还是往后统计
printf("%d\n", strlen(&arr + 1));//跳过这个数组 ,随机值
printf("%d\n", strlen(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,随机值-1
}
#include <stdio.h>
#include <string.h>
// 数组名的理解
// 数组名是数组首元素的地址
// 但是有2个例外:
// 1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
// 2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
// int main()
// {
// //一维数组
// int a[] = {1, 2, 3, 4};
// printf("%d\n", sizeof(a)); // 4*4=16
// printf("%d\n", sizeof(a + 0)); //数组名a是数组首元素的地址,a+0还是首元素地址,地址的大小4/8
// printf("%d\n", sizeof(*a));//数组名a是数组首元素的地址,*a就是首元素,int类型大小4个字节
// printf("%d\n", sizeof(a + 1));//数组名a是数组首元素的地址,a+1是第二个元素的地址,地址的大小是4/8
// printf("%d\n", sizeof(a[1]));//4
// printf("%d\n", sizeof(&a));//&a是数组的地址,数组的地址也是地址,是4/8个字节
// printf("%d\n", sizeof(*&a));// --> sizeof(a) ,*&a,取地址解引用a就是a 16
// printf("%d\n", sizeof(&a + 1)); //8
// printf("%d\n", sizeof(&a[0]));// 首元素地址 4/8字节
// printf("%d\n", sizeof(&a[0] + 1));//&a[0]是首元素的地址,&a[0]+1就是第二个元素的地址,地址就是4/8个字节
// }
// int main()
// {
// // 字符数组
// char arr[] = {'a', 'b', 'c', 'd', 'e', 'f'};
// // printf("%d\n", sizeof(arr)); //计算的是整个数组的大小,单位是字节,总共6个字节
// // printf("%d\n", sizeof(arr + 0)); //arr表示数组首元素地址,arr+0还是数组首元素地址,地址就是4/8字节
// // printf("%d\n", sizeof(*arr));//arr是首元素地址,*arr是首元素,大小1个字节
// // printf("%d\n", sizeof(arr[1]));//1
// // printf("%d\n", sizeof(&arr));//&arr是数组的地址 地址就是4/8
// // printf("%d\n", sizeof(&arr + 1)); //地址就是4/8
// // printf("%d\n", sizeof(&arr[0] + 1)); //地址是4/8
// printf("%d\n", strlen(arr)); //因为字符数组arr中没有\0,所以求字符串长度的时候,会一直往后找,产生的结构就是随机值
// printf("%d\n", strlen(arr + 0));// arr+0 是首元素的地址,和第一个一样,也是随机值
// //printf("%d\n", strlen(*arr));//arr是数组首元素的地址,*arr就是数组首元素 就是'a'-97
// //strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,‘a’的ASCII码值就是97,那就是将97作为地址传参
// //strlen就会从97这个地址开始统计字符串长度,这就非法访问内存了
// //printf("%d\n", strlen(arr[1])); //err
// printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,传递给strlen函数后,依然是从数组的第一个元素的位置,还是往后统计
// printf("%d\n", strlen(&arr + 1));//跳过这个数组 ,随机值
// printf("%d\n", strlen(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,随机值-1
// return 0;
// }
// int main()
// {
// char arr[] = "abcdef"; //[a b c d e f \0]
// // printf("%d\n", sizeof(arr)); //7
// // printf("%d\n", sizeof(arr + 0)); //arr+0是首元素地址 4/8
// // printf("%d\n", sizeof(*arr)); // *arr首元素 char类型是1
// // printf("%d\n", sizeof(arr[1]));//下标1的元素b 1
// // printf("%d\n", sizeof(&arr)); //地址就是4/8
// // printf("%d\n", sizeof(&arr + 1));//地址 4/8
// // printf("%d\n", sizeof(&arr[0] + 1));//地址4/8
// printf("%d\n", strlen(arr));//6
// printf("%d\n", strlen(arr + 0)); //6
// //printf("%d\n", strlen(*arr)); //err
// //printf("%d\n", strlen(arr[1])); //err
// 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"; //[a b c d e f \0]
// // printf("%d\n", sizeof(p)); //p是一个指针变量,大小就要4/8个字节
// // printf("%d\n", sizeof(p + 1)); //p+1是b的地址,是地址大小就是4/8个字节
// // printf("%d\n", sizeof(*p)); //*p首元素,字符a 1个字节
// // printf("%d\n", sizeof(p[0]));//p[0] = *(p+0) = *p 1
// // printf("%d\n", sizeof(&p)); // 4/8 p = char* &p = char**
// // printf("%d\n", sizeof(&p + 1));// 4/8
// // printf("%d\n", sizeof(&p[0] + 1));// 4/8
// printf("%d\n", strlen(p)); //6
// printf("%d\n", strlen(p + 1));//5
// //printf("%d\n", strlen(*p));// err *p = a
// //printf("%d\n", strlen(p[0]));// p[0] = *(p+0) = *p err
// printf("%d\n", strlen(&p));//随机值
// printf("%d\n", strlen(&p + 1));//随机值
// printf("%d\n", strlen(&p[0] + 1));//&p[0]+1 是b 5
//return 0;
// }
int main()
{
// 二维数组
int a[3][4] = {0};
printf("%d\n", sizeof(a)); //3*4*4
printf("%d\n", sizeof(a[0][0]));//4
printf("%d\n", sizeof(a[0])); //4*4
printf("%d\n", sizeof(a[0] + 1)); // 4/8
//a[0]作为第一行的数组名,没有单独放在sizeof内部,没有&
//a[0]表示数组首元素的地址,也就是a[0][0]的地址
//a[0]+1是第一行第二个元素的地址
//地址就是4/8
printf("%d\n", sizeof(*(a[0] + 1)));//计算的就是第一行第2个元素的大小 4个字节
printf("%d\n", sizeof(a + 1)); // 4/8
//a是数组首元素的地址,是第一行的地址
//a+1就是第二行的地址
printf("%d\n", sizeof(*(a + 1))); //4*4
//*(a+1)--> a[1] -> sizeof(a[1]),计算第二行的大小
//a+1就是第二行的地址,*(a+1)计算第二行的大小
printf("%d\n", sizeof(&a[0] + 1));// 4/8
//&a[0]第一行的地址
//&[0]+1第二行的地址
printf("%d\n", sizeof(*(&a[0] + 1))); //第二行的大小 4*4
printf("%d\n", sizeof(*a)); //4*4
//a是数组首元素地址,就是第一行地址
//*a就是第一行
//*a --> *(a+0) --> a[0]
printf("%d\n", sizeof(a[3])); //4*4
//a[3] --> int[4]
return 0;
}
//总结:数组名的意义
//1sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
//2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
//3.除此之外所有的数组名都表示首元素的地址
// 笔试题1
// int main()
// {
// int a[5] = { 1, 2, 3, 4, 5 };
// int *ptr = (int *)(&a + 1);
// printf( "%d,%d", *(a + 1), *(ptr - 1)); //2 5
// return 0;
// }
// 笔试题2
// 由于还没学习结构体,这里告知结构体的大小是20个字节
// struct Test
// {
// int Num;
// char *pcName;
// short sDate;
// char cha[2];
// short sBa[4];
// } *p = (struct Test*)0x100000;
// // 假设p 的值为0x100000。 如下表表达式的值分别为多少?
// int main()
// {
// printf("%p\n", p + 0x1); //0x100020 +1跳过一个结构体就是20个字节
// printf("%p\n", (unsigned long)p + 0x1);//0x100001
// printf("%p\n", (unsigned int *)p + 0x1); //0x100004
// return 0;
// }
//笔试题3
// int main()
// {
// int a[4] = {1, 2, 3, 4};
// int *ptr1 = (int *)(&a + 1);
// int *ptr2 = (int *)((int)a + 1);
// printf("%x\n,%x\n", ptr1[-1], *ptr2);
// return 0;
// }
//笔试题4
// #include <stdio.h>
// int main()
// {
// int a[3][2] = { (0, 1), (2, 3), (4, 5) };
// int *p;
// p = a[0]; //a[0] --> &a[0][0]
// printf( "%d", p[0]);//p[0] --> *(p+0) -->*p 1
// return 0;
// }
//笔试题5
// int main()
// {
// int a[5][5];
// int(*p)[4];
// //p --> int(*)4 a -->int(*)5
// p = a;
// printf( "%p,%d\n",
// &p[4][2] - &a[4][2],
// &p[4][2] - &a[4][2]);
// return 0;
// }
//笔试题6
// int main()
// {
// int aa[2][5] = { 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;
// }
//笔试题7
// int main()
// {
// char *a[] = {"work","at","alibaba"};//char* work char* at char* alibaba
// char**pa = a;
// pa++; //0 work 1 at 2 alibaba
// printf("%s\n", *pa); //at
// return 0;
// }
//笔试题8
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);
return 0;
}
笔试题3
笔试题5
笔试题6
总结:本章主要对指针在初阶的基础上更深一步的进行了研究,对指针地址和解引用都有详细的讲解,如需初阶可以观看C语言基础-指针_小凡喜编程的博客-CSDN博客