C/C 指针详解之提高篇
目录
一. 堆空间与指针的相爱相杀
1.1 堆上一维空间
1.1.1 返回值返回(一级指针)
1.1.2 参数返回(二级指针)
1.2 堆上二维空间
1.2.1 指针作返值输出
1.2.2 空间申请与释放
1.2.3 多级指针作参数输出
1.2.4 具体实例之序列加密
二. const修饰指针
1.1 const 修饰变量
1.2 const 修饰指针
1.3 const 修饰指针指向
1.4 应用( 修饰函数参数)
1.5 const int *pi 与int *const pi的区别
1.5.1 从const int i 说起
1.5.2 const int *pi 与 int *const pi的缠绵
1.5.3 补充三种情况
三. 函数与函数指针
3.1 函数多参返回
3.1.1 引列
3.1.2 正解
3.2 函数指针
3.2.1 函数的本质
3.2.2 函数指针变量定义与赋值
3.2.3 函数指针类型定义
3.2.4 应用场景
3.3 回调函数
3.3.1 问题引出
3.3.2 回调(函数做参数)
一. 堆空间与指针的相爱相杀
1.1 堆上一维空间
1.1.1 返回值返回(一级指针)
char * allocSpace(int n) { char *p = (char*)malloc(n); return p; }
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
1.1.2 参数返回(二级指针)
#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;
int allocSpace(char **p,int n)
{
*p = (char*)malloc(n);
return *p== NULL?-1:1;
}
int _tmain(int argc, _TCHAR* argv[])
{
char *p;
if(allocSpace(&p,100)<0)
{
return -1;
}
strcpy(p,'china');
printf('%s\n',p);
free(p);
system('pause');
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
输出结果如下:
1.2 堆上二维空间
二维数组,是一种二维空间,但是不代表,二维空间就是二维数组。二维空间,并不一定就是二维数组,但具有数组的访问形式。但己经远远不是数组的定义了。
1.2.1 指针作返值输出
#include <stdio.h> #include <stdlib.h> #include <string> using namespace std; void * alloc2dSpace(int base,int row,int line) { void *p = malloc(base*row*line); return p; } int _tmain(int argc, _TCHAR* argv[]) { int (*p)[5] = alloc2dSpace(sizeof(int),3,5); for(int i=0; i<3; i++) { for(int j=0; j<5; j++) { p[i][j] = i+j; } } for(int i=0; i<3; i++) { for(int j=0; j<5; j++) { printf('%d ',*(*(p+i)+j)); } putchar(10); } free(p); system('pause'); return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
1.2.2 空间申请与释放
#include <stdio.h>
#include <stdlib.h>
void ** alloc2dSpace(int base, int row,int line)
{
void **p = malloc(row*sizeof(void*));
for(int i=0; i<row;i++)
{
p[i] = malloc(base * line);
}
return p;
}
int main(void)
{
int **p = alloc2dSpace(sizeof(int),3,4);
for(int i=0; i<3; i++)
{
for(int j=0; j<4; j++)
{
p[i][j] = i+j;
}
}
for(int i=0; i<3; i++)
{
for(int j=0; j<4; j++)
{
printf('%d ',p[i][j]);
}
putchar('\n');
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
1.2.3 多级指针作参数输出
void alloc2dSpace(void ***p,int base,int row,int line) { *p = malloc(row*sizeof(void*)); for(int i=0; i<row;i++) { (*p)[i] = malloc(base * line); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
1.2.4 具体实例之序列加密
现有字符序列 char buf[] = “hello everyone”;
按行读的话,肯定可以读出数据,如果按列来读的话,则会出再乱码的现像。正是这种现像可作为一种加密手段,称为序列加密。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if 0
buf[] = 'china is great';
//chi
//na
//is
//gre
//at
//cniga
//hasrt
//i e
#endif
char * encode(char *buf, int line)
{
int len = strlen(buf);
int nlen;
if(len%line!=0)
{
nlen = len + (line - len%line);
}
else
{
nlen = len;
}
char * tmp = (char *)malloc(nlen+1);
char * secret = (char*)malloc(nlen+1);
char *psecret = secret;
strcpy(tmp,buf);
int n = strlen(tmp);
for(; n<nlen; n++)
{
tmp[n] = ' ';
}
tmp[nlen] = '\0';
int row = nlen/line;
char (*ptmp)[line] =tmp;
int i = 0;
int j = 0;
for(; i<line; i++)
{
for(; j<row; j++)
{
*psecret++ = ptmp[j][i];
}
}
*psecret = '\0';
free(tmp);
return secret;
}
char * decode(char* buf, int line)
{
int len = strlen(buf);
int nline = len/line;
int row = line;
char * desecret = (char*)malloc(len+1);
char *pd = desecret;
char (*p)[nline] = buf;
int i = 0;
int j = 0;
for(;i<nline; i++)
{
for(; j<row; j++)
{
*pd++ = p[j][i];
}
}
*pd= '\0';
while(*(--pd) == 32)
{
*pd= '\0';
}
return desecret;
}
int main(void)
{
char buf[] = 'china is great';
printf('%s\n','*****明文*****');
printf('%s\n',buf);
char * secret = encode(buf,3);
printf('%s\n','*****密文*****');
printf('%s\n',secret);
char * desecret = decode(secret,3);
printf('%s\n',desecret);
free(secret);
free(desecret);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
输出结果如下:
二. const修饰指针
1.1 const 修饰变量
const 修饰变量,此时称为常变量。常变量,有常量的属性,比用宏定义的常量有了类型的属性。
#include <stdio.h> #define N 400a int main(void) { // const int a = 400a; const int a = 200; //定义的时候必须初始化 printf('a = %d\n',a); // a = 200; printf('a = %d\n',a); int *p = &a; // *p = 300; printf('a = %d\n',a); return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
输出结果:
1.2 const 修饰指针
const 修饰指针,表示指针的指向是恒定的,不可更改。
#include <stdio.h>
int main(void)
{
int a = 2;
int * const p = &a;
int b = 3;
//p = &b; //指针常量无法修改,编译不过
printf('%d\n',*p);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
输出结果为:2。
1.3 const 修饰指针指向
#include <stdio.h> int main(void) { int a = 2; const int * p = &a; printf('p = %p\n',p); int b ; p = &b; printf('p = %p\n',p); //*p = 200; //编译不过 const int *const q = &a; //q = &b; //编译不过 // *q = 200; //编译不过 return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
1.4 应用( 修饰函数参数)
常用于修饰入参指针,表示其指向的内容不可以修改。这样,可以增加程序的键壮性。如果用户不小心发生了修改行为,则会用编译警告来提示,而不是用运行的错误来提示。
char * strcpy ( char * destination, const char * source );
char * strcat ( char * destination, const char * source );
1.5 const int *pi 与int *const pi的区别
1.5.1 从const int i 说起
你知道我们声明一个变量时象这样 int i ;这个 i 是可能在它处重新变赋值的。如下:
int i = 0;
i = 20; /*这里重新赋值了*/
/*不过有一天我的程序可能需要这样一个变量(暂且称它变量),在声明时就赋一个初始值。之后我的程序在其它任何处都不会再去重新对它赋值。那我又应该怎么办呢?用 const */
const int ic =20;
ic = 40; /*这样是不可以的,编译时是无法通过,因为我们不能对 const修饰的 ic 重新赋值的。*/
/*这样我们的程序就会更早更容易发现问题了。*/
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
有了 const 修饰的 ic 我们不称它为变量,而称符号常量,代表着 20 这个数。这就是 const 的作用。ic 是不能在它处重新赋新值了。
认识了 const 作用之后,另外,我们还要知道格式的写法。有两种:
const int ic = 20; 与i nt const ic = 20;
它们是完全相同的。这一点我们是要清楚。
#include <stdio.h> int main(void) { int a = 2; printf('a = %d\n',a); a = 3; printf('a = %d\n',a); const int i1 = 4; int const i2 = 5; printf('const int i1 = %d\n',i1); printf('int const i2 = %d\n',i2); //i1 = 6; //编译不过 //i2 = 7; //编译不过 return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
输出结果:
1.5.2 const int *pi 与 int *const pi的缠绵
先看一段代码:
#include <stdio.h>
int main(void)
{
int a = 20;
int b = 30;
const int *pi = &a;
printf('const int *pi = %d\n',*pi);
pi = &b;
printf('const int *pi = %d\n',*pi);
// *pi = 40; //注意这里,编译不过,*pi不能再这样重新赋值了,*pi是一个常量
int *const pi2 = &a;
printf('int *const pi2 = %d\n',*pi2);
//pi2 = &b; //注意这里,pi2不能再这样重新赋值了,即不能再指向另外一个新地址
*pi2 = 80;
printf('int *const pi2 = %d\n',*pi2);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
输出结果为:
我们来具体分析下上述代码中const把指针到底怎么了。我们来看这句代码:const int *pi = &a;这句代码中const 修饰的是*pi,将*pi定义为常量,所以给*pi重新赋值是非法的,而pi是普通的变量,可对其进行再赋值,如: pi = &b;我们再来看这句代码:int *const pi2 = &a;这句代码中const修饰的是 pi2,将pi2定义为常量,所以给pi2重新赋值是非法的,而*pi2则可以重新赋值。
请大声读出下面两句话并牢牢记住:
- 如果 const 修饰在*pi 前,则不能改的是*pi(即不能类似这样:*pi=50;赋值)而不是指 pi。
- 如果 const 是直接写在 pi 前,则 pi 不能改(即不能类似这样:pi=&i;赋值)。
相信到目前为止你还没有被 int *const pi和const int *pi搞混。那么请自行分析 int const *pi 和 int *const pi ,它们各自声明的 pi 分别能修改什么,不能修改什么?如有问题请在下方评论区留言我们继续探讨。
1.5.3 补充三种情况
这里,我再补充以下三种情况。其实只要上面的语义搞清楚了,这三种情况也就已经被包含了。不过作为三种具体的形式,我还是简单做以提示:
情况一:int *pi 指针指向 const int i 常量的情况
#include <stdio.h> int main(void) { const int i1 = 40; int *pi; //pi = &i1; /* 这样可以吗?不行,编译错误 return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
为啥pi = &il编译不过,请自行分析。
情况二:const int *pi 指针指向 const int i1 的情况
#include <stdio.h>
int main(void)
{
const int i1=40;
const int * pi;
pi=&i1;/* 两个类型相同,可以这样赋值。很显然,i1 的值无论是通过 pi 还是 i1 都不能修改的。 */
printf('i1 = %d\n',i1);
printf('pi = %d\n',pi);
printf('*pi = %d\n',*pi);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
上述代码可成功编译并运行,请自行分析。
情况三:用 const int *const pi 声明的指针
#include <stdio.h> int main(void) { int i = 20; const int * const pi=&i; return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
上述代码可成功编译并运行,请自行分析。
三. 函数与函数指针
3.1 函数多参返回
3.1.1 引列
请写一个函数,同时返回两个正整型数据的和与差。但是我们知道函数只有一个返回值的,该如何实现呢?
int foo(int * sum ,int *diff, int a,int b);
- 1
- 1
3.1.2 正解
当我们既需要,通过函数返回值来判断函数调用是否成功,又需要把数据传递出来,此时,我们就要用到多参返回,多参返回,都是通过传递调用空间中的空间地址来实现的。比如前面我们讲到的,通过参数,返回堆上的一维空间,二维空间和初始化指针就是如此。
3.2 函数指针
3.2.1 函数的本质
函数的本质是一段可执行性代码段。函数名,则是指向这段代码段的首地址。
#include <stdio.h> void print() { printf('china\n'); } int main() { print(); printf('%p\n',&print); printf('%p\n',print); int a; int *p = &a; //函数也是一个指针,应当存在一个什么样的指针变量中呢? return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
3.2.2 函数指针变量定义与赋值
#include <stdio.h>
void print()
{
printf('china\n');
}
void dis()
{
printf('china\n');
}
int main()
{
void (*pf)() = print; //void (*pf)() = &print;
pf(); //(*pf)();
pf = dis;
pf();
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
3.2.3 函数指针类型定义
#include <stdio.h> void print() { printf('china\n'); } void dis() { printf('china\n'); } typedef void (*PFUNC)() ; int main() { PFUNC pf= print; pf(); pf = dis; pf(); return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
3.2.4 应用场景
函数指针的一个用法出现在 菜单驱动系统中。例如程序可以提示用户输入一个整数值来选择菜单中的一个选项。用户的选择可以做函数指针数组的下标,而数组中的指针可以用来调用函数。
#include <stdio.h>
void function0(int);
void function1(int);
void function2(int);
int main()
{
void (*f[3])(int) = {function0,function1,function2};
//将这 3 个函数指针保存在数组 f 中
int choice;
printf('Enter a number between 0 and 2, 3 to end: ');
scanf('%d',&choice);
while ((choice >= 0) && (choice <3))
{
(*f[choice])(choice);
//f[choice]选择在数组中位置为 choice 的指针。
//指针被解除引用,以调用函数,并且 choice 作为实参传递给这个函数。
printf('Enter a number between 0 and 2,3 to end: ');
scanf('%d',&choice);
}
printf('Program execution completed.');
return 0;
}
void function0(int a)
{
printf('You entered %d so function0 was called\n',a);
}
void function1(int b)
{
printf('You entered %d so function1 was called\n',b);
}
void function2(int c)
{
printf('You entered %d so function2 was called\n',c);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
3.3 回调函数
3.3.1 问题引出
当我们要实现排序的时候,升序和降序,都是写死在程序中的,如果要改只能改动原代码,那么如果程序是以库的形式给出的呢?那又如何呢?
#include <stdio.h> void selectSort(int *p, int n) { for(int i=0; i<n-1 ;i ++) { for(int j=i+1; j<n; j++) { if(p[i] < p[j]) { p[i] = p[i]^p[j]; p[j] = p[i]^p[j]; p[i] = p[i]^p[j]; } } } } int main(void) { int arr[10] = {6,5,4,3,2,1,7,8,9,0}; selectSort(arr,10); for(int i=0; i<10; i++) { printf('%d\n',arr[i]); } return 0; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
3.3.2 回调(函数做参数)
#include <stdio.h>
int callBackCompare(int a,int b)
{
return a<b?1:0;
}
void selectSort(int *p, int n,int(*pf)(int,int))
{
for(int i=0; i<n-1 ;i ++)
{
for(int j=i+1; j<n; j++)
{
if(pf(p[i],p[j]))
{
p[i] = p[i]^p[j];
p[j] = p[i]^p[j];
p[i] = p[i]^p[j];
}
}
}
}
int main(void)
{
int arr[10] = {6,5,4,3,2,1,7,8,9,0};
selectSort(arr,10,callBackCompare);
for(int i=0; i<10; i++)
{
printf('%d\n',arr[i]);
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
本质:回调函数,本质也是一种函数调用,先将函数以指针的方式传入,然后,调用。这种写法的好处是,对外提供函数类型,而不是函数定义。这样我们只需要依据函数类型和函数功能提供函数就可以了。给程序的书写带来了很大的自由。
【上一篇:】:C/C++指针详解之基础篇