概述
一、指针概述
指针是用来存放地址的变量;
指针的大小是固定的4个或8个,取决于你的操作系统(32位/64位);
指针的类型决定了指针的步长和访问权限;
指针的运算规则;
二、字符串指针
字符串的名称为首元素的地址
定义方法:
char str[]="abcdef";
char* pstr=str;
有种常量字符串的定义,容易出错:
char* pstu="abcdf";//"abcdf"作为常量字符串,pstu指针存首元素地址
此时,pstu作为常量字符串的指针,不能对其解引用操作去改变常量的值,故一般加上const:
const char* pstr="abcdef";
易错点2:
char arr1[]="abc";
char arr2[]="abc";
char* parr1="abc";
char* parr2="abc";
arr1 != arr2 :这两个作为不同数组的首元素地址,故不同
parr1 == parr2:这两个指针指向常量,由于是常量,因此空间只开辟了一个,所以指向了相同的空间
三、指针数组
定义:用于储存指针的数组,本质是数组。
数组内的数据类型 *指针名称[ 数组大小 ]={指针}
应用场景:同时打印多个大小相同的数组
int main()//同时打印多个数组
{
int arr1[]={1,2,3,4,5,6,7,8};
int arr2[]={0,2,6,4,2,5,6,2};
int arr3[]={6,9,4,6,6,5,6,6};
int* parr[]={arr1,arr2,arr3};
int sz=sizeof(parr)/sizeof(parr[0]);//得到元素个数
int i,j;
for(i=0;i<sz;i++)
{
for(j=0;j<8;j++)
{
printf("%d ",(*(parr+i))[j]);
}
printf("n");
}
system("pause");
return 0;
}
四、数组指针
定义: 数组内元素的类型(*p)[数组的元素个数]
例如一个又五个元素的整形数组的指针 —— int (*p)【5】
理解:理解这个数组指针要明白,它本质是指针,用来存放的是一整个数组的地址,在定义的时候,因为【】的优先级比*的高,所以要先用括号,强调p是一个指针(本质),再想到他是什么类型的指针,这里因为是数组,所以自然能想到一个数组重要的两个特征就是,一个是存放的数据类型,一个是放了多少个,再根据定义多看两眼,试着打几下代码不难掌握
五、关于数组指针与指针数组的辨析
理解:这个辨析无非就是看本质,要先知道一个知识点就是【】的优先级比*的高,因此
例如:int *p【3】;这种定义会被计算机识别成数组
知道这个的基础上,就很好分析了,在本章节内,对于一个变量是什么的辨析,无非就是先找本质,再分析
先从变量名称入手,看到p,然后看p具体和谁结合,通过分析去先一步看明白到底是数组还是指针
六、数组与指针的辨析
理解辨析
在前期学习的时候,我们知道,数组的名字就是首元素的地址,而数组的地址由&arr得到。
在学完指针过后,对于数组和指针之间的关系,有了更深刻的理解。
例如:
int arr[]={0,1,2,3,4,5};
arr除了单独出现在sizeof()里面时,作为整个数组的大小被计算之外,其他情况都是指向首元素的地址。
&arr: 代表着我通过这个首元素地址,得到了整个数组的地址,所以&arr是整个数组的地址
arr:首元素地址
*arr:我通过首元素地址找到了第一个元素的值
那么实际上,数组名本身在很多时候都很像一个指针,他在一个连续的数据里面,指向着第一个数
在我们传参的时候,我们也是可以通过一个指针去接受一个数组名
arr【1】能等同于 *(arr+1)
如果上面的能够比较好的理解,那么二维数组也是如此分析
这里定义一个二维数组
int arr[5][3]={{1,2,3},{4,5,6},{7,8,9},{10,9,8},{6,5,4}};
我们都知道,第一个【】代表行,第二个【】代表列,我们通常将二维数组想象成一个表去使用
但为了理解接下来的分析,我们得将这个二维数组理解成,我定义了一个数组A,里面是专门用来
存放一个个相同大小的数组a、b、c、d、e的,用上述定义的数组来说,就是我定义了个数组叫arr
用来存放数组a{1,2,3},数组b{4,5,6}...这个时候,根据刚刚一维数组的分析思路,我们可以分析
&arr 取的是整个数组arr的地址
arr 代表首元素(数组a)的地址,这里的首元素不在是一个整形,而是数组a
*arr 代表着我将首元素地址(数组a)解引用,得到的其实是数组a里首元素的地址
*(*arr) 代表着我将数组a中首元素地址进行解引用,得到的就是数字1
数组传参
理解上面说的,我们不难理解,在一维数组传参的过程中,我将arr(首元素地址)传过去时,我
形参部分要拿一个指针或者arr【】去接受,在二维数组传参的时候,形参部分除了arr【】【5】外
还可以 int (*p)【3】(还是上面的例子)
这本质就是数组指针,首先(*p)表明了本质是指针,其次int 【3】告诉我们它里面存放
的是一个整形数组的地址,这个数组里元素个数是3个,我们刚刚说的二维数组穿数组名的时候
其实就是把第首元素地址(数组a的地址)传了过去,实际这里接收的也是数组a的地址,这里的p
代表的就是整个数组a的地址
p 数组a的地址
*p 数组a首元素的地址
**p 数组a首元素
七、函数指针与函数指针数组
在指针这一章节中,会有各种各样的定义,指针、数组、函数,这三个词组在一起,让人看着觉得
复杂,但我们分析的时候,只要抓住本质,逐步分析,就能解决“如何定义”和“如何分析”两个问题
除此之外,更深一层是如何用这些指针去实现一些特有的操作,简而言之就是
要学会“如何看懂”、“如何定义”、“如何运用”
函数指针
//假如有个叫add的函数
int add(int a,int b);
//定义一个函数指针
int(*padd)(int,int)
定义:
返回类型 (*指针名称)(函数参数类型)
函数指针数组
//例如现在有两个同类型函数
int add(int a,int b);
int jian(int a,int b);
//自定义一个函数指针数组存放上面两个个函数的地址
int (*p[])(int,int)={&add,&jian};
定义:数组内函数的返回类型 (*p【数组大小】)(数组内函数的参数类型)
ps:在定义函数指针数组的时候有些许复杂,这里有个小技巧,你先定义一个函数指针
int(*p)(int,int) 》 int(*p【】)(int,int)
定义好后你想,本质是个数组,那么我应该人p和【】先结合,因此在p后面加个【】
函数指针与函数指针数组的辨析与应用
辨析:
找本质,本质部分去除后看类型,这个不难,就是多找几个练就熟了
运用:
函数指针无非就是将函数的地址取出来,通过地址去调用函数
函数指针数组用于存放这些函数指针
这两个结合,我们通常应用就是将同类功能的函数分开实现,统一放到一个数组里,方便调用
例如:计算器的实现
思路:我们讲加减乘除四个函数分别实现,再统一用个函数指针数组储存调用
代码:略
八、回调函数
定义:利用b的函数指针,在函数a里面访问函数b,这个函数b就被叫做回调函数
回调函数的内容,我们用几个例子去感受一下
例一:qsort—快速排序函数的使用
qsort函数介绍
定义:void qsort(const void*,int,int,void (*p) ( const void * , const void *) );
功能:实现数组的排序,数组的类型可以是任意类型的,例如:浮点型数组、结构体数组...
返回类型:void
参数部分:我们讲从原理的角度解释参数部分,也方便记忆
第一个参数:数组的首元素地址 arr
第二个参数:数组的元素个数 sizeof(arr)/sizeof(arr[0]);
第三个参数:数组中单个元素的大小 sizeof(arr[0]);
第四个参数:由你自定义的一种比大小函数,规则由你自己制定和实现,
void my_cmp(const void* e1,const void* e2);
如何理解这些参数呢?我们用我们熟知的冒泡排序去解释这些参数。
我们都知道,冒泡排序就是将数组里的两个元素不断的进行比较,然后再进行位置上的调整和交换
但是过去的冒泡排序中,我们只能实现整形数组的排序,那么我如果学用冒泡排序的思想去排序一
各浮点型的数组或者结构体数组时,我们该怎么办呢,在实际的应用场景中,更多时候我们需要排
序一个结构体数组,例如:学生名单按成绩高低进行排序。因此我们能不能定义一个函数让它能实
现各种各样的函数呢?我们又该怎么样去定义这个函数呢?
首先,我们肯定要数组的首元素地址和数组的元素个数,但迎面而来的第一个问题就是:
1.我如何要让函数能够接收所有类型的首元素地址?
这里介绍一个void型的指针,它可以用于接收任何类型的指针
但我们都知道指针类型决定了它能够访问的空间大小和加减运算时的步长
因此void类型的指针不能通过地址改变值,也不能进行加减运算,为了代码的稳定性
我们通常在定义void*的指针时,在前面加上const
有了void型指针,我们接受各种各样类型的首元素地址的问题就解决了,我们也能理解,为什么第一个参数的类型是const void*,但是问题又来了,我们无法通过void*去操作数组。
2.我们该如何通过void*去操作数组呢?
我们可以通过强制类型转换去将得到的地址转换成我们传进来的数组,如何就可以进行操作了,但是我们似乎不能通过向函数传参告诉我数组的类型,因此第3个问题。
3.作为开发函数的人如何“知道”数组的类型呢?
学到这里的时候,我不由的惊叹程序员的智慧,他先指针void*类型的转换成char*的指针
我们都知道,char*的指针每次访问只访问一个字节的内容,如果这个时候,我知道你每个数据的大小,那么他就能清楚你这个数组里面元素的大小单位是多少,从而实现对数组的操作。
因此,第三个参数就是数组内单个元素的大小
我们继续回到冒泡排序上考虑,我新设计的万能冒泡已经解决了,如何传不同类型参数的问题以及如何操作不同类型数组的问题,还有什么问题呢?
我们知道,一个结构体变量里面就可能存着各种各样的数据,我们具体要根据其中的哪一个去进行排序呢?例如一个学生表,我可以按学号排,也可以按成绩排。因此我们还差一个能够帮助我们还得想办法解决如果比大小的问题
4.我们该如何让函数去按我们的规则排序?
我们想怎么排序,程序员在设计这个函数的时候是不可能知道的,因此我们要通过一个我们按我们想法制定好规则的比较函数my_cmp去实现排序,因此第四个参数是我们自定义的一个排序函数,这个函数就被称为回调函数。
在我们定义这个函数的时候,函数返回类型是int,参数是(const void* e1,const* void e2)
e1和e2是我们要排的两个函数,这个函数返回值>1时 e1>e2,这个函数返回值<1时 e1<e2
至于参数为什么是void*,这个和问题1原因一样。
——————————————————————————————————————————
通过以上分析,我们应该能更深刻的理解这个函数的参数设计,已经能更好的记住如何使用
这次,我们可以试着真的动手将我们熟知的冒泡排序改造成万能冒泡排序了
实例二:自定义万能冒泡排序
void jiaohuan(char* e1,char* e2,int a)
{
int i;
assert(e1);
assert(e2);
for(i=0;i<a;i++)
{
char tmp=*e1;
*e1=*e2;
*e2=tmp;
e1++;
e2++;
}
}
void boo_sort(void *parr,int sz,int a,int (*cmp)(const void*,const void*))
{
int i;
assert(parr);
for(i=0;i<sz-1;i++)
{
int j;
for(j=0;j<sz-1-i;j++)
{
if(cmp((char*)parr+j*a,(char*)parr+(j+1)*a)<0)//从大到小,逆序
jiaohuan((char*)parr+j*a,(char*)parr+(j+1)*a,a);//实现交换
}
}
}
typedef struct stu
{
char name[10];
int age;
}Stu;
int cmp_stu(const void* e1,const void* e2)
{
assert(e1&&e2);
return ((Stu*)e1)->age -((Stu*)e2)->age;
}
void print_stu(Stu* p,int sz)
{
int i;
assert(p);
for(i=0;i<sz;i++)
printf("%s %dn",(p+i)->name,(p+i)->age);
printf("n");
}
int main()
{
Stu arr[]={{"张三",17},{"李四",19},{"李白",18}};
int sz=sizeof(arr)/sizeof(arr[0]);
print_stu(arr,sz);
boo_sort(arr,sz,sizeof(arr[0]),cmp_stu);
print_stu(arr,sz);
system("pause");
return 0;
}
此处是我自己练习实现的自定义冒泡,其中为了复习之前学的结构体类型的定义和输出,因此用了结构体数组进行排序,按照年龄进行排序,可以作为参考。
总结实现过程中的难点:
1.传参类型的确定,需要你深刻理解上面提到的设计思路
2.分装函数去实现比大小、以及交换的功能
九、各种类型的指针、数组在sizeof和strlen函数里的辨析
重点:
1.对各种指针、数组的分析熟练
2.sizeof中有种特殊情况,sizeof(数组名):计算的是整个数组的大小
3.sizeof是计算()内数据大小的函数,因此在分析中容易出错的一个点是:牢记地址要么4字节要么8字节!!!
4.strlen是计算字符串大小的函数,它()只接收地址,计算原理是遇到‘ ’才停止计数,易错点是
当()内放字符时,程序会报错,当无法遇到‘ ’时,返回的是随机值。
接下来附上练习题,供大家参考:
//一维数组
int a[] = {1,2,3,4};
printf("%dn",sizeof(a));
printf("%dn",sizeof(a+0));
printf("%dn",sizeof(*a));
printf("%dn",sizeof(a+1));
printf("%dn",sizeof(a[1]));
printf("%dn",sizeof(&a));
printf("%dn",sizeof(*&a));
printf("%dn",sizeof(&a+1));
printf("%dn",sizeof(&a[0]));
printf("%dn",sizeof(&a[0]+1));
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%dn", sizeof(arr));
printf("%dn", sizeof(arr+0));
printf("%dn", sizeof(*arr));
printf("%dn", sizeof(arr[1]));
printf("%dn", sizeof(&arr));
printf("%dn", sizeof(&arr+1));
printf("%dn", sizeof(&arr[0]+1));
printf("%dn", strlen(arr));
printf("%dn", strlen(arr+0));
printf("%dn", strlen(*arr));
printf("%dn", strlen(arr[1]));
printf("%dn", strlen(&arr));
printf("%dn", strlen(&arr+1));
printf("%dn", strlen(&arr[0]+1));
char arr[] = "abcdef";
printf("%dn", sizeof(arr));
printf("%dn", sizeof(arr+0));
printf("%dn", sizeof(*arr));
printf("%dn", sizeof(arr[1]));
printf("%dn", sizeof(&arr));
printf("%dn", sizeof(&arr+1));
printf("%dn", sizeof(&arr[0]+1));
printf("%dn", strlen(arr));
printf("%dn", strlen(arr+0));
printf("%dn", strlen(*arr));
printf("%dn", strlen(arr[1]));
printf("%dn", strlen(&arr));
printf("%dn", strlen(&arr+1));
printf("%dn", strlen(&arr[0]+1));
char *p = "abcdef";
printf("%dn", sizeof(p));
printf("%dn", sizeof(p+1));
printf("%dn", sizeof(*p));
printf("%dn", sizeof(p[0]));
printf("%dn", sizeof(&p));
printf("%dn", sizeof(&p+1));
printf("%dn", sizeof(&p[0]+1));
printf("%dn", strlen(p));
printf("%dn", strlen(p+1));
printf("%dn", strlen(*p));
printf("%dn", strlen(p[0]));
printf("%dn", strlen(&p));
printf("%dn", strlen(&p+1));
printf("%dn", strlen(&p[0]+1));
//二维数组
int a[3][4] = {0};
printf("%dn",sizeof(a));
printf("%dn",sizeof(a[0][0]));
printf("%dn",sizeof(a[0]));
printf("%dn",sizeof(a[0]+1));
printf("%dn",sizeof(*(a[0]+1)));
printf("%dn",sizeof(a+1));
printf("%dn",sizeof(*(a+1)));
printf("%dn",sizeof(&a[0]+1));
printf("%dn",sizeof(*(&a[0]+1)));
printf("%dn",sizeof(*a));
printf("%dn",sizeof(a[3]));
十、指针笔试题
方便以后复习时看,有空写下每题的具体分析
1.
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//程序的结果是什么?
2.
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%pn", p + 0x1);
printf("%pn", (unsigned long)p + 0x1);
printf("%pn", (unsigned int*)p + 0x1);
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,%x", 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];
printf( "%d", p[0]);
return 0;
}
5.
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%dn", &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));
return 0;
}
7.
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%sn", *pa);
return 0;
}
8.
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%sn", **++cpp);
printf("%sn", *--*++cpp+3);
printf("%sn", *cpp[-2]+3);
printf("%sn", cpp[-1][-1]+1);
return 0;
}
以上,如有不足,欢迎指出!
最后
以上就是甜美鸡为你收集整理的指针——基础知识点归纳总结一、指针概述二、字符串指针三、指针数组四、数组指针五、关于数组指针与指针数组的辨析六、数组与指针的辨析七、函数指针与函数指针数组八、回调函数九、各种类型的指针、数组在sizeof和strlen函数里的辨析十、指针笔试题的全部内容,希望文章能够帮你解决指针——基础知识点归纳总结一、指针概述二、字符串指针三、指针数组四、数组指针五、关于数组指针与指针数组的辨析六、数组与指针的辨析七、函数指针与函数指针数组八、回调函数九、各种类型的指针、数组在sizeof和strlen函数里的辨析十、指针笔试题所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复