目录
1.5 const int *pi 与int *const pi的区别
1.5.2 const int *pi 与 int *const pi的缠绵
一. 堆空间与指针的相爱相杀
1.1 堆上一维空间
1.1.1 返回值返回(一级指针)
char * allocSpace(int n)
{
char *p = (char*)malloc(n);
return p;
}
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 堆上二维空间
二维数组,是一种二维空间,但是不代表,二维空间就是二维数组。二维空间,并不一定就是二维数组,但具有数组的访问形式。但己经远远不是数组的定义了。
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.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 多级指针作参数输出
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.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;
}
输出结果如下:
二. 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 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;
}
输出结果为: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.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 重新赋值的。*/
/*这样我们的程序就会更早更容易发现问题了。*/
有了 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.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;
}
输出结果为:
我们来具体分析下上述代码中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;
}
为啥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;
}
上述代码可成功编译并运行,请自行分析。
情况三:用 const int *const pi 声明的指针
#include <stdio.h>
int main(void)
{
int i = 20;
const int * const pi=&i;
return 0;
}
上述代码可成功编译并运行,请自行分析。
三. 函数与函数指针
3.1 函数多参返回
3.1.1 引列
请写一个函数,同时返回两个正整型数据的和与差。但是我们知道函数只有一个返回值的,该如何实现呢?
int foo(int * sum ,int *diff, int a,int b);
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;
}
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;
}
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;
}
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);
}
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;
}
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;
}
本质:回调函数,本质也是一种函数调用,先将函数以指针的方式传入,然后,调用。这种写法的好处是,对外提供函数类型,而不是函数定义。这样我们只需要依据函数类型和函数功能提供函数就可以了。给程序的书写带来了很大的自由。
【上一篇:】:C/C++指针详解之基础篇