目录
一、指针初阶
1.指针是什么?
1.1指针的定义
指针是内存中一个最小单元的编号,也就是地址(以字节为单位,每一个字节的存储单位具有一个地址)。
1.2指针变量的定义
指针变量是用来存放内存地址的变量;可以通过取地址符&取出某一变量(字符型、整型、数组类型等)的起始地址存放在指针变量中。经常将指针变量称为指针。
注意区分:指针变量的大小即地址的大小取决于计算机是32位还是64位;对于32位的计算机,有32根地址线,在寻址时产生32个0或1;即0000 0000 0000 0000 0000 0000 0000 0000~1111 1111 1111 1111 1111 1111 1111 1111,此时有2的32次方个地址(=4GB空闲编址),该指针变量占32/8=4个字节。对于64位的计算机,有64根地址线,在寻址时产生64个0或1;即0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000~1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 ,此时有2的64次方个地址(=16GB空闲编址),该指针变量占64/8=8个字节。指针变量存放着其他变量的地址,而存放着其他变量地址的指针变量也作为其中一种类型的变量存放在内存中。
2.指针和指针类型
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
int num = 10;
int *pj = num;
指针定义的方式是 type * 指针变量名 ;其中类型是表明该指针变量中存放的是哪种类型变量的地址(如int * pj存放的是int类型的num);如果要将变量存入标有与变量类型不同的指针变量时,要进行强制类型转化:
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
易得,&n的类型是 int *,要将&n存入char *类型的指针变量中就要先将其强制类型转化为char *型
2.1指针类型不同的意义
printf("%p\n", &n);//000000065911F984
printf("%p\n", pc);//000000065911F984
printf("%p\n", pc+1);//000000065911F985
printf("%p\n", pi);//000000065911F984
printf("%p\n", pi+1);//000000065911F984
不同类型的指针在+-1或其它整数时跳过的字节数与指针变量定义的类型有关。在n的地址经过强制类型转化后存入char * pc,而指针变量pc与指针变量pi中存的都是n的起始第一个字节地址;不同之处在于当指针pc+1时跳过了1个字节,指针pi+1时跳过了4个字节;原因是char * pc的类型char长度为1个字节,int * pi的类型int为四个字节。
2.2指针的解引用
对指针变量进行解引用(*)操作的时候,指针的类型决定解引用的权限;比如:char *的指针解引用只能访问第一个字节,而int *类型的指针可以访问四个字节。
int n = 256;
char* pc = (char*)&n;
int* pi = &n;
printf("%d\n", *pi);//打印结果:256
printf("%d", *pc); //打印结果:0
上述打印结果的原因解释;256以二进制的形式表示为:
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 0000 0000
在解引用指针变量pi类型为int *类型时可以访问四个字节(后32bit位):0000 0000 0000 0000 0000 0001 0000 0000(256);在解引用指针变量pc类型为char *类型时只能访问1个字节(后8bit位):0000 0000(0);
3.野指针
野指针就是指针指向位置是不可知的(随机的、不正确的、没有明确限制的)
3.1野指针成因
3.1.1指针未初始化
int *p;//局部变量指针未初始化,默认为随机值,程序运行过程中报错
*p = 20;
return 0;
3.1.2指针越界访问
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
3.1.3指针指向的空间释放
3.2如何规避野指针
int* p = NULL;
int* q = NULL;
int n = 10;
p = &n;
printf("%d", *p);//10
*q = 10;//报错!!!
创建指针变量时初始化,无指向值时使其指向为空,之后该指针变量可以重新指向其他变量,但是由于指针变量置空,在没指向某一变量时不能进行赋值,编译器会报错,因此在对指针变量进行直接赋值操作时,可以先检查它是否为空。
4.指针的运算
4.1指针+-整数
float values[5];
float* vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[5];)
{
*vp++ = 0;
}
4.2指针-指针
int my_strlen(char *s)//计算字符串长度
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
两个指针相减实际上是两个指针值(地址)相减之差再除以该指针所指变量的的大小(字节数)。上图代码就是通过将p自增至指向s字符串的末位,再减去s原指向的首位得到中间元素的个数。
4.3指针的关系运算
for(vp = &values[MAX]; vp > &values[0];)//代码1
{
*--vp = 0;
}
for(vp = &values[MAX-1]; vp >= &values[0];vp--)//代码二
{
*vp = 0;
}//循环在最后一次判断时,vp递减到数组首位元素在内存中的前一位
代码一不可行,代码二可行;标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与
指向第一个元素之前的那个内存位置的指针进行比较。
5.指针和数组
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr);//000000C1FA4FF6C8
printf("%p\n", &arr[0]);//000000C1FA4FF6C8
可见,数组名表示的是数组首元素的地址。但sizeof(arr)!=sizeof(arr[0]);此时,arr表示整个数组,arr[0]表示数组首元素;那么将数组的地址给到指针变量时就可以表示为:
int arr=[1,2,3,4,5];
int* p = arr;
int* q = &arr[0];
指针p、q均解引用后表示 数组的首元素,在表示arr[i]时又可以有多种表达:
arr[i]; *(p+i);* (arr+i)
p+i指得是数组第i个元素的地址。2.1有提到,再对指针变量进行+-操作时,是以其所指向元素大小为单位;+i即跳过i个该元素大小的字节后所指向的字节它的地址。
6.二级指针
指针变量中存放着地址,那么指针变量也可以存放其他指针变量的地址,该指针变量称为二级指针变量(二级指针)。表示为 type** 变量名;type *表示其所指向的变量类型。
int a = 10;
int *pa = &a;
int **ppa = &pa;
printf("%p\n", ppa);//pa的地址000000EA244FF598
printf("%p\n", *ppa);//pa中存放的a的地址000000EA244FF574
printf("%d\n", **ppa);//先对pa的地址解引用得到a的地址 在对a的地址解引用得到a的值
7.指针数组
指针数组表示的是数组中存放的每个元素的类型是指针。即有整型数组、字符数组、指针数组。
int arr[5]; char arr[5]; int* arr3[5];//表示每个元素的类型是 * int
二、指针进阶
1.字符指针
在指针的类型中我们知道有一种指针类型为字符指针char *
1.1一般使用
char ch = 'w';
char *pc = &ch;
*pc = 'w';
1.2字符串使用
const char* pstr = "hello world.";
printf("%s\n", pstr);//hello world.
printf("%c\n", *pstr);//h
const char* pstr = "hello world.";该行代码并不是将hello world.字符串放入了指针变量pstr中,而是将该字符串的首字节(元素)地址放入 了pstr中。那么通过指针变量创建的字符串又和字符数组初始化创建的字符串有什么区别呢?看下面的例子:
char str1[] = "hello world.";
char str2[] = "hello world.";
const char* strp1 = "hello world.";
const char* strp2 = "hello world.";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n"); //打印输出
if (strp1 == strp2)
printf("strp1 and strp2 are same\n"); //打印输出
else
printf("strp1 and strp2 are not same\n");
字符串的比较要用strcmp();但在此代码中我们直接用来比较是为了判断它们所创建的"hello world."在内存中是不是指的同一串;由打印输出的结果来看,在创建数组str1[ ]和str2[ ]时在内存中创建了两个"hello world.";而在创建指针变量strp1时,"hello world."被创建放置在内存中,再由strp1指向其,又在创建strp2时让其直接指向已经被创建的"hello world.";
2.数组指针
2.1数组指针的定义
指针数组时存放指针的数组,数组指针是指向数组的指针;
int *p1[10];//指针数组
int (*p2)[10];//数组指针
[ ] 的优先级高于 *。当没有括号时int* p1[10],p1先和[ ]结合表示p1[ ]数组,int *此时表示数组类型,表示存放指针类型变量;当有括号时int (*p2) [10],p2先和*结合,说明p2是一个指针变量,int [10]此时为指针的类型,表示指向数组。
2.2数组名与&数组名
&arr与arr的含义相同吗?
int arr[10] = { 0 };
printf("%p\n", &arr[0]);//000000046BF9FA18
printf("%p\n", arr);//000000046BF9FA18
printf("%p\n", &arr);//000000046BF9FA18
printf("arr+1 = %p\n", arr+1);//000000046BF9FA1C
printf("&arr+1= %p\n", &arr+1);//000000046BF9FA40
在此段代码中arr,&arr打印输出都是数组首元素的地址 ;但打印arr+1跳过了4个字节,&arr+1跳过了40个字节;因为取地址arr取的是arr数组的地址,若存放进指针变量则变量类型为int(*)[10]的数组指针,再进行取地址+1操作时,跳过的字节大小是整个数组。
2.3数组指针的使用
数组指针可以用来在二维数组传参时做形参。
void print_arr2(int (*arr)[5], int row, int col)//相当于int arr[3][5]
{
int i = 0;
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
3.数组参数、指针参数
3.1一维数组传参
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);
}
3.2二维数组传参
void test(int arr[3][5]){}//正确
void test(int arr[][]){}//错误
void test(int arr[][5]){}//正确
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
void test(int* arr){}//正确
void test(int* arr[5]){}//正确
void test(int(*arr)[5]){}//正确
void test(int** arr){}//正确
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
3.3一级指针传参
void test1(int *p)
{}//test1函数能接收int类型变量的地址
void test2(char* p)
{}//test2函数能接收char类型变量的地址
3.4二级指针传参
void test(char **p){}//能接收存放字符型变量地址的指针变量的地址
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);//指针数组(arr表示数组首元素的地址,数组首元素是存放char类型变量的指针)
return 0;
}
4.函数指针
void test(){printf("hehe\n");}
int main()
{
printf("%p\n", test);//00007FF618AB1406
printf("%p\n", &test);//00007FF618AB1406
}//函数也具有地址
函数具有地址并且它的地址可以用指针保存,该指针类型为函数指针。格式为:
void (*pfunction) (int ,char );
函数返回值类型(*函数指针变量名)(函数参数类型【没有参数时可为空】)
//代码一:
void (*signal(int , void(*)(int)))(int);
//代码二:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
代码一和代码二相同。这是一个函数的声明:signal函数有两个参数,其中一个是整型,另一个参数是返回值为空、有一个整型参数的函数指针类型;而signal函数的返回值是void (*)(int)类型,即signal函数的返回值是,一个无返回值有一个整型参数的函数指针类型。
5.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。
典型回调函数:qsort函数的使用
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)//void表示指针是无指向类型
{
return (*( int *)p1 - *(int *) p2);//需要先将无类型的指针强制转换为要比较的类型
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
qsort函数C语言编译器函数库自带的排序函数。qsort 的函数原型是
void qsort(void*base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*)); 在传参时,需要传入数组的地址,数组的元素个数,数组单个元素的长度,以及排序判断函数。qsort最后一个参数就是函数指针类型,在使用qsort函数进行排序时,就要调用比较函数。