一、一级指针
一级指针通常用作函数的参数,在调用函数时,形参和实参占用不同的内存空间,但空间里存放的指针指向的是同一块内存。即一级指针做函数参数, 在函数内做形参做重新指向并不会影响实参的指向。
用一个小例子说明一下:
#include<stdio.h>
#include<stdlib.h>
void point(int* p, int *q)
{
p = q;
printf("%d\n", *p);
}
int main()
{
int a = 10;
int b = 20;
int* p = &a;
int* q = &b;
point(p, q);
printf("%d\n", *p);
system("pause");
return 0;
}
在这个小例子中,输出的值是20和10。
结论:这里不能通过改变形参的指向来改变实参的指向,因为形参和实参只是两个指向同一空间的不同指针,形参改了但是实参不会被改变。
对上面的例子做一个小小的调整:
#include<stdio.h>
#include<stdlib.h>
void point(int* p, int *q)
{
*p = *q;
printf("%d\n", *p);
}
int main()
{
int a = 10;
int b = 20;
int* p = &a;
int* q = &b;
point(p, q);
printf("%d\n", *p);
system("pause");
return 0;
}
输出的结果是20和20。
结论:因为形参和实参指向的是同一空间,在形参中改变指向空间的值,实参指向空间的值也发生改变。
分享一个指针的使用小例子:
int main()
{
char* p = "hello world!"; // 第一种
printf("%s\n", p);
*p = 'x';
printf("%s\n", p);
char q[] = "hello world!"; // 第二种
printf("%s\n", q);
q[0] = 'x';
printf("%s\n", q);
system("pause");
return 0;
}
分析这段代码,发现有什么问题?
运行之后发现,这段代码第五行会发生异常,说明不能修改该指针指向的内容。这是因为开始定义的是一个指针p,指针p里存放的是地址,指向常量区存放的字符串,而常量是不允许被修改的。
但是为什么第二种就没有异常,允许修改呢?
因为以数组的方式存放的是拷贝后的字符串,常量区的字符串已经被拷贝到栈区,此时可以进行修改。
二、二级指针
举一个小例子:
void point(int **q,int *a)
{
*q = a;
}
int main()
{
int a = 10;
int *p = NULL;
point(&p, &a);
printf("%d\n", *p);
system("pause");
return 0;
}
输出10。
这里用二级指针改变形参指向,从而改变实参指向。
三、指针数组、数组指针、sizeof和strlen()的区别
1.指针数组:存放指针的数组。
int* arr[10];
2.数组指针:指向数组的指针。
int(*arr)[10];
指针和数组混合使用的一些小例子:
例一:
int main()
{
int a[] = { 1,2,3,4 };// a表示首元素地址,&a表示数组地址
printf("%d\n", sizeof(a));// 16 特殊处理 表示整个数组大小
printf("%d\n", sizeof(a + 0));// 4 表示首元素地址
printf("%d\n", sizeof(*a));// 4 表示首元素地址解引用,第一个元素
printf("%d\n", sizeof(a + 1));// 4 表示第二个元素地址
printf("%d\n", sizeof(a[1]));// 4 表示第二个元素
printf("%d\n", sizeof(&a));// 4 特殊处理 表示首元素大小
printf("%d\n", sizeof(*&a));// 16 相当于a,表示整个数组大小
printf("%d\n", sizeof(&a + 1));// 4 表示整个数组的下一个空间地址
printf("%d\n", sizeof(&a[0]));// 4 表示首元素地址
printf("%d\n", sizeof(&a[0] + 1));// 4 表示第二个元素的地址
system("pause");
return 0;
}
在此应当注意:当用sizeof求字节的时候,数组名(a)和取地址数组名(&a)往往会特殊化处理,原本sizeof(a)表示首元素地址,sizeof(&a)表示数组地址;特殊化处理后,sizeof(a)表示整个数组的大小,sizeof(&a)表示首元素大小
例二:
int main()
{
char arr[] = { 'a','b','c','d','e','f' };// arr表示首元素地址,&arr表示数组地址
printf("%d\n", sizeof(arr));// 6 特殊处理 表示整个数组的大小
printf("%d\n", sizeof(arr + 0));// 4 表示首元素地址
printf("%d\n", sizeof(*arr));// 1 表示首元素地址解引用,第一个元素
printf("%d\n", sizeof(arr[1]));// 1 表示第二个元素
printf("%d\n", sizeof(&arr));// 4 表示首元素地址
printf("%d\n", sizeof(&arr + 1));// 4 表示整个数组的下一个空间地址
printf("%d\n", sizeof(&arr[0] + 1));// 4 表示第二个元素的地址
printf("%d\n", strlen(arr));// 随机值
printf("%d\n", strlen(arr + 0));// 随机值
printf("%d\n", strlen(*arr));// 编不过
printf("%d\n", strlen(arr[1]));// 编不过
printf("%d\n", strlen(&arr));// 编不过
printf("%d\n", strlen(&arr + 1));// 编不过
printf("%d\n", strlen(&arr[0] + 1));// 随机值-1
system("pause");
return 0;
}
3.sizeof 和 strlen()的区别:
1、sizeof
sizeof()是运算符,而不是一个函数。
其值在编译时即计算好了。
参数可以是数组、指针、类型、对象、函数等。
功能是:获得保证能容纳实现所建立的最大对象的字节大小,包括’\0’。
由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。
实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。
具体而言,当参数分别如下时,sizeof返回的值表示的含义如下:
数组——编译时分配的数组空间大小;
指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4);
类型——该类型所占的空间大小;
对象——对象的实际占用空间大小;
函数——函数的返回类型所占的空间大小。函数的返回类型不能是void。
小例子:
i=10;
sizeof(i++);
这两句执行完成后,i的值会不会变?为什么呢?
答:不会变!
因为sizeof的求值对象如果是表达式,会根据表达式类型直接给出结果,但表达式并不会执行。所以 sizeof(i++) 相当于变成了 sizeof(int)。
2、strlen()
strlen()是函数。
其值要在运行时才能计算。
参数必须是字符型指针(char*), 且必须是以’\0’结尾的。当数组名作为参数传入时,实际上数组就退化成指针了。
它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符’\0’。返回的长度大小,不包括’\0’。
例三:多级指针举例:
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);
system("pause");
return 0;
}
图示,分析这道题:
四、函数指针、函数指针数组、转移表
1.函数指针:
先定义一个简单的指针:
int main()
{
int a = 10;
int *p = &a;
printf("%d\n", *p);
system("pause");
return 0;
}
很明显,上面的代码定义了一个p指针,它指向变量a,*p解引用得到a的值。
与这个指针类似,函数指针指向一个函数,而不是一个变量。
定义一个函数指针:
void test(char *str)
{
printf("%s\n", str);
}
int main()
{
//函数指针的定义方式
void(*p)(char *str) = test;
void(*p1)(char *str) = &test;
//调用函数test,这是两种定义方式
(*p)("hello world!");// 第一种
(*p1)("hello nature!");
p("hello world!");// 第二种
p1("hello nature!");
system("pause");
return 0;
}
下面是函数指针的两种定义方式:
void(*p)(char *str) = test;
void(*p1)(char *str) = &test;
解析:
- 定义p和p1为一个指向函数的指针变量,它们指向的函数类型为void型,且有一个指针参数,并且用test函数名初始化这个函数指针。
- 在上面两种调用方式中,所有的解引用操作符可以省略。同样,在两种调用方式中,&也可以省略。
- 那为什么下面这种方式不可以?
void *p2(char *str)
因为,这种是函数p2的声明,其返回值是void* 。
- test函数都是在调用时才能分配空间,才有地址,
void(*p)(char *str) = test;
void(*p1)(char *str) = &test;
那这样两行代码在没有调用test()时,就已经传了test()的地址?
因为此时给指针赋的是一个虚地址,等真正执行函数时,通过虚地址找到真实地址。
2.函数指针数组:
函数指针数组:是存放函数地址的数组。
用一个小例子说明一下:
void test(char *str)
{
printf("%s\n", str);
}
int main()
{
void(*arr[3])(char* str);
// 初始化数组元素,指向函数test
arr[0] = test;// 第一种
arr[1] = test;
arr[2] = test;
arr[0] = &test;// 第二种
arr[1] = &test;
arr[2] = &test;
// 调用函数test
(*arr[0])("aaa");// 第一种
(*arr[1])("bbb");
(*arr[2])("ccc");
arr[0]("aaa");// 第二种
arr[1]("bbb");
arr[2]("ccc");
system("pause");
return 0;
}
3.实现转移表
常规实现:
// 实现转移表(计算器)
void menu()
{
printf("********************************************\n");
printf("** 1.add 2.sub **\n");
printf("** 3.mul 4.div **\n");
printf("********************************************\n");
}
int my_add(int a, int b)
{
return a + b;
}
int my_sub(int a, int b)
{
return a - b;
}
int my_mul(int a, int b)
{
return a * b;
}
int my_div(int a, int b)
{
return a / b;
}
int main()
{
int select = 1;
int x = 0;
int y = 0;
int tmp = 0;
int flag = 0;
while (select)
{
menu();
printf("请选择功能选项:");
scanf("%d", &select);
switch (select)
{
case 1:
printf("请输入操作数:");
scanf("%d %d", &x, &y);
tmp = my_add(x, y);
flag = 1;
break;
case 2:
printf("请输入操作数:");
scanf("%d %d", &x, &y);
tmp = my_sub(x, y);
flag = 1;
break;
case 3:
printf("请输入操作数:");
scanf("%d %d", &x, &y);
tmp = my_mul(x, y);
flag = 1;
break;
case 4:
printf("请输入操作数:");
scanf("%d %d", &x, &y);
tmp = my_div(x, y);
flag = 1;
break;
default:
printf("输入错误!程序退出!\n");
flag = 0;
break;
}
if (flag)
{
printf("result = %d\n", tmp);
}
}
system("pause");
return 0;
}
函数指针数组实现:
// 函数指针数组 实现转移表(计算器)
void menu()
{
printf("********************************************\n");
printf("** 1.add 2.sub **\n");
printf("** 3.mul 4.div **\n");
printf("********************************************\n");
}
int my_add(int a, int b)
{
return a + b;
}
int my_sub(int a, int b)
{
return a - b;
}
int my_mul(int a, int b)
{
return a * b;
}
int my_div(int a, int b)
{
return a / b;
}
int main()
{
int x = 0;
int y = 0;
int select = 1;
int flag = 0;
int tmp = 0;
int(*p[5])(int x, int y) = { 0,my_add,my_sub,my_mul,my_div };// 转移表
while (select)
{
menu();
printf("请选择功能选项:");
scanf("%d", &select);
if ((select <= 4 && select >= 1))
{
printf("请输入操作数:");
scanf("%d %d", &x, &y);
tmp = (*p[select])(x, y);
flag = 1;
}
else
{
printf("输入错误!程序退出!\n");
flag = 0;
}
if (flag)
{
printf("result = %d\n", tmp);
}
else break;
}
system("pause");
return 0;
}