[题集]指针与函数
调试题:找出程序中的错误
#include<stdio.h>
#include<stdlib.h>
int sum(int*, int);
int main()
{
int i;
int arr[] = { 10,20,30,40,50,60,70,80,90,100 };
int elements = sizeof(arr) / sizeof(arr[0]);
int total = sum(arr, elements);
for (i = 0; i < elements; i++)
{
printf("%d\n", arr[i]);
}
printf("数组之和:%d\n", total);
getchar();
return 0;
}
int sum(int* x, int n)
{
int j, t = 0;
for (j = 0; j < n; j++)
{
t += *(x);
}
return t;
}
我们可以看到,在代码开头,它声明了一个int sum(int * ,int);
这是一个返回型为int的函数,带两个参数,一个为指针类型,一个为int变量类型
标准的地址传递,int total = sum(arr, elements);
将arr的首地址传给函数第一个参数,将elements传给函数第二个参数,然后将返回值赋给左值total
这段代码在编译上并无错误,但是一段无作用的代码,问题出在它的sum函数内,我们来看看
原运行结果:
看见没,这段代码可以运行,无报错,无语法上的错误,我们假设这段代码的作用是求数组内的和
n为形参,它的实参是elements,就是数组的下标数,问题就出在函数体内的for循环中
t+=*(x)这段语句的意思是取到x的值加上t,然后返回t,但是x在该函数体内并无任何运算,它一直都是arr[0]的值,也就是10,下标数也是10,循环从0开始,执行10-1次循环,第0次循环后t等于arr[0]等于10,第二次就是t=10+10,二十,依次下去,最后到循环结束的时候t等于90+10=100,则返回值为100
但这只能累加arr[0]的值,无法计算数组之和,那如何实现数组元素累加呢,我们可以把*(x)改为*(x+j)
第二题:调试
#include<stdio.h>
#include<conio.h>
int sum(int*, int);
int main()
{
int i;
int arr[] = { 10,20,30,40,50,60,70,80,90,100 };
int elements = sizeof(arr) / sizeof(arr[0]);
int total = sum(arr, elements);
for (i = 0; i, elements; i++)
{
printf("%d", arr[i]);
}
printf("数组之和=%d", total);
getchar();
return 0;
}
int sum(int *x, int n)
{
int j, t = 0;
for (j = 0; j < n; j++)
{
t += *(x + j);
return t;
}
}
这段代码跟上一段代码一样的,也是求和,并且也无语法错误,主函数内的功能在上一段已经说的很透彻了
唯独不同的还是在sum函数的for循环中,我们来看一看
典型的地址传递,把arr的首地址也就是arr[0]的地址传给形参x,形参x是一指针类型,指针的值并是arr的首地址
*(x+j)将arr的首地址加上j然后取值,我们拟第一次循环开始,这时j等于0那么
t+=*(x+j)则等价于t=arr[0],则t=10
最重要的,在下面这句return t,它直接结束循环体,返回函数值t,这时t等于10!!
我们如何让他执行完循环再返回t呢?只需要把这段语句放到for循环底下就可以了。
第三题:调试题:
#include<stdio.h>
#include<conio.h>
int sum(int[][2], int, int);
int main()
{
int i, j, row, column, total;
int arr2[][2] = { 10,20,30,40,50,60,70,80,90,100 };
//定义一个二维数组,行计算机自分配,拥有两列
int elements = sizeof(arr2) / sizeof(arr2[0][0]);
//利用总长度除以单个元素长度求的数组长度
row = elements / 2;
//利用数组长度除以列数得行长度
column = 2;
total = sum(arr2, row, column);
printf("数组长度=%d\n", elements);
for (i = 0; i < row; i++)
{
for (j = 0; j < column; j++)
{
printf("数组遍历:%d\n", arr2[i][j]);
}
}
printf("二维数组之和=%d\n", arr2[i][j]);
}
int sum(int p2[][2], int n, int m)
{
int i, j, t = 0;
for (i = 0; i < n; i++)
{
for (j = 0; j < m; j++)
{
t += p2[i][j];
}
}
return t;
}
这题有所不同,在编译上并报错了,我们来看下报错信息:
啥意思呢?编译器以为我们定义了两个函数,重名的,为什么会这样呢,我们来复习下指针与函数,参数类型必须一致这一条
我们是吧arr2数组的首地址传入形参p2,但是p2的列地址为未知,这时参数类型并不一致,我们只需要将参数类型一致并能解决问题,arr2是一个拥有两列的二维数组,所以形参p2也需要拥有两列
第四题:调试题
#include<stdio.h>
#include<conio.h>
int sum(int* p[2], int);
int main()
{
int i, j, row, column, total = 0;
int arr2[][2] = { 10,20,30,40,50,60,70,80,90,100 };
int elements = sizeof(arr2) / sizeof(arr2[0][0]);
row = elements / 2;
column = 2;
total = sum(arr2, row);
printf("数组长度:%d\n", elements);
for (i = 0; i < row; i++)
{
for (j = 0; j < 2; j++)
{
printf("%3d", arr2[i][j]);
//左补3空
}
printf("\n");
}
printf("数组之和=%d\n", total);
getchar();
return 0;
}
int sum(int* p2[2], int n)
{
int i, j, t = 0;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
t += *(*(p2 + i)+j);
}
}
return t;
}
我们来看看这段代码,这段代码也是在编译上报错了,我们来看看错误信息:
sum(arr2,row);并是将arr2的首地址传入给了形参*p2[]
但是!arr2是一个二维数组,而形参p2是一个指针数组类型,并不匹配,实参不能将值传入给形参,因为类型不匹配!
那我们解决这个错误,将p2改为拥有两列的二维数组,或则将p2该为指向两列的数组指针:
我们来看下效果:将形参转换为二维数组
将形参p2 改为数组指针,指向拥有两列的数组:
那如果一定要用指针数组做形参输出相同的值,就需要改变整段代码了:
#include<stdio.h>
#include<conio.h>
int sum(int *p2[], int);
int main()
{
int i, j, row, column, total = 0;
int s[]={ 10,20,30,40,50,60,70,80,90,100 };
int* arr2[] = { s,s + 1,s + 2,s + 3,s + 4,s + 5,s + 6,s + 7,s + 8,s + 9 };
int elements = sizeof(s) / sizeof(s[0]);
row = elements;
column = 2;
total = sum(arr2, row);
printf("数组长度:%d\n", elements);
for (i = 0; i < row; i++)
{
printf("%4d", *arr2[i]);
//左补3空
printf("\n");
}
printf("数组之和=%d\n", total);
getchar();
return 0;
}
int sum(int *p2[], int n)
{
int i, j, t = 0;
for (i = 0; i < n; i++)
{
t += **(p2 + i);
}
return t;
}
我一直在想如何不改变*p2[2]来达到相同的效果,但是好像不可能,毕竟p2[2]只能存放两个int*类型的值,那样就要大改,如果有大佬有简单的办法,请评论告诉!!!
好了不到这道题上折磨了,进入下一题:
#include<stdio.h>
#include<conio.h>
int* pf(int[], int);
//定义一个指针函数,两个参数为int数组类型和int类型,返回一个int*类型
#define MAX 5
//定义一个全局常量,MAX,数值为5
int main()
{
int total = 0, k;
int i[MAX] = { 10,20,30,40,50 };
//定义一个数组,长度为5
int* ptr;
//定义一个一级指针
ptr = pf(i, MAX);
//将指针指向pf的返回值
//将i的首地址和MAX作为实参拷贝进形参x[],n内
for (k = 0; k < MAX; k++)
//for循环,从0开始,判定条件小于5,运算递增
{
printf("k[%d]=%d\n", k, *(ptr + k));
//输出ptr[k]的值
total += *(ptr + k);
//total=total+ptr[k]
}
printf("数组之和=%d\n", total);
getchar();
return 0;
}
int* pf(int x[], int n)
{
int m;
int k[MAX];
//定义一个局部变量:int类型数组,数组长度为5
int j[] = { 100,200,300,400,500 };
for (m = 0; m < n; m++)
{
k[m] = x[m] + j[m];
printf("k[%d]=%d\n", m, k[m]);
}
printf("k=%p\n", k);
return k;
}
这题是重头菜,我们来看一下它的输出结果
什么情况,怎么会出现错误呢?我们跟着代码走也没发现什么错误啊
我们来看看问题出来哪:
问题就出现在k上面,k是一个局部变量,分配时它的地址储存来栈内,结束后全部释放
return k是将k的首地址返回给主函数,在主函数内被赋给了ptr,这时ptr的值并为k的首地址,但是k在函数结束后全部释放,那么指针并只能指向这个被拷贝过来的地址,我们来看图
在执行完返回后,函数体并结束了,但是这时ptr已经接收到了k拷贝的地址
这题剖析具体请点击:补充笔记
这题问题出在局部变量上,那么我们只需要将这个局部变量改为全局变量或者静态局部变量即可实现求和功能:
改为静态变量
静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。
下一题:叙述下列语句含义
int (*p)(int);
p为一指针,因为括号优先级最大,先于*结合,它指向一个拥有一个int类型参数,返回值为int类型的函数
int *p(int)
p为一函数,因为括号优先级最大,他有一个int类型的参数,返回为int指针类型,也就是返回的是一个地址,它需要赋给拥有同样数据类型的指针变量
下一题:程序实战
- 定义一个含有10个数据的一维数组,再利用传址的方式将此数组
- 传给input函数,以便输入数据
- 传给total函数,计算此数组元素之和,之后再将总和返回
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<conio.h>
int input(int*, int);
int total(int[], int);
int main()
{
int a[10];
input(a, 10);
int c = total(a, 10);
printf("数组之和:%d\n", c);
getchar();
return 0;
}
int input(int* a, int n)
{
printf("请输入数值:");
for (int k = 0; k < n; k++)
{
scanf("%d", &a[k]);
printf("*(a+%d)=%d\n", k, *(a + k));
}
}
int total(int b[], int o)
{
int t = 0;
for (int k = 0; k < o; k++)
{
printf("b[%d]=%d\n", k, b[k]);
t += b[k];
}
return t;
}
注意点:输入数值,利用for循环将终端上输入的值存入数组,在主函数内调用。
表达式可改为:
第二题
- 定义一个含有8个数据的二维数组2行4列,再利用传址的方式将此数组
- 传给input函数,以便输入数据
- 传给total2函数,计算此数组元素之和,之后再将总和返回
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<conio.h>
#define N 2
#define M 4
int input(int(*p)[M], int, int);
int total(int[][M], int, int);
int main()
{
int arr[N][M];
input(arr, N, M);
int c = total(arr, N, M);
printf("数组之和=%d\n", c);
getchar();
return 0;
}
input(int p[N][M], int n, int m)
{
printf("请输入数值:");
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
scanf("%d", &p[i][j]);
printf("p[%d][%d]=%d\n", i, j, p[i][j]);
}
}
}
total(int a[N][M], int n, int m)
{
int t = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
t = t + a[i][j];
}
}
return t;
}
在我翻阅答案后,答案是另一种写法
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<conio.h>
#define N 2
#define M 4
int input(int(*p)[M], int, int);
int total(int[][M], int, int);
int main()
{
int arr[N][M];
input(arr, N, M);
int c = total(arr, N, M);
printf("数组之和=%d\n", c);
getchar();
return 0;
}
input(int (*p)[M], int n, int m)
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
printf("请输入数值:");
scanf("%d",*(p + i) + j);
printf("p[%d][%d]=%d\n", i, j, *(*(p + i) + j));
}
}
}
total(int(*p)[M], int n, int m)
{
int t = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
t = t + *(*(p + i) + j);
}
}
return t;
//返回t
}
函数的参数类型为数组指针类型, (*p)[M],因为定义了一个全局常量M=4,则这指针p指向的是一个拥有4列的数组
*(*(p + i) + j)补下指针与数组吧,拟第二次循环
- 先与括号内的相结合,第一次循环i为0,则输出p的值,p指向的是实参arr的首地址,则p指向arr[0]的地址。
- 这时在p的行地址上偏移0个int*4字节,再取到p[1]的地址,形参p[1]的地址,就是实参arr[1]的地址
- 再偏移j个int字节,这时j为1,再取到该地址的值,就是p1的值,转到实参就是arr1的值
注意点:传址运算,形参和实参的类型必须匹配,二维数组可用行指针代替,指针指向该数组的首地址,列数必须与原数组一样,指针的值就是该数组的首地址
第三题
下一题:编写两种方法,如冒泡排序与插入排序,之后,以函数指针调用这两个函数
这题涉及到的两种排序方式为最简单的算法,具体看补充笔记:冒泡与插入算法
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<conio.h>
int maopao(int[], int n);
int charu(int[], int n);
int (*p)(int[], int n);
int main()
{
int i;
int j;
int a[10];
for (i = 0; i < 10; i++)
{
printf("请输入数组值a[%d]:", i);
scanf("%d", a+i);
printf("\n");
}
printf("请输入排序方式:1.冒泡排序 2.插入排序\n");
scanf("%d", &j);
if (j == 1)
{
p = maopao;
(*p)(a, 10);
}
if (j == 2)
{
p = charu;
(*p)(a, 10);
}
for (i = 0; i < 10; i++)
{
printf("a[%d]=%d\n", i, a[i]);
}
getchar();
return 0;
}
int maopao(int x[], int n)
{
int i, j, temp;
for (i = 0; i < n - 1; i++)
{
for (j = 0; j < n - 1 - i; j++)
{
if (x[j] > x[j + 1])
{
temp = x[j];
x[j] = x[j + 1];
x[j + 1] = temp;
}
}
}
}
int charu(int x[], int n)
{
int i;
int j;
int temp;
for (i = 1; i < n; i++)
{
temp = x[i];
j = i - 1;
while (j >= 0 && temp < x[j])
{
x[j + 1] = x[j];
j--;
}
x[j + 1] = temp;
}
}
这题的详细剖析全在补充笔记理,唯一要理解算法如何工作的!
下一题:
试以命令行自变量,再命令提示符模式下,将一个数组的数据以冒泡排序加以排序
sort -t表示升序,sort-a表示降序
看见命令行,我们就该想到要把程序名改为sort,输入指令-t表示调用升序冒泡,输入指令-a表示降序冒泡
空格在命令行中就是调用下一个参数,这个参数结束:命令行自变量笔记
我自己写了一段,由于电脑原因没有安装虚拟机,又不想用VC6.0++,就只好按照混乱的头绪写:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int r(int[], int n);
int t(int[], int n);
int main(int argc,int *argv[])
{
int a[20];
int i = 0;
do
{
printf("请输入数组元素,最大20,输入0时结束\n");
scanf("%d", a + i);
printf("a[%d]=%d\n", i, a[i]);
} while (a[i++] != 0);
if (argc == 2)
{
if (argv[1][0] == '-')
{
switch (argv[1][1])
{
case'r':
r(a, --i);
break;
case'a':
t(a, --i);
break;
default:
printf("没有该选项");
break;
}
}
else
{
printf("未找到此指令");
}
}
else
{
printf("输入错误");
}
printf("排序后的序列:");
for (int n = 0; n < i; n++)
{
printf("%d\n", a[n]);
}
getchar();
return 0;
}
int r(int x[], int n)
{
int i, j, temp;
for (i = 0; i < n-1; i++)
{
for (j = 0; j < n - 1 - i; j++)
{
if (x[j] > x[j + 1])
{
temp = x[j];
x[j] = x[j + 1];
x[j + 1] = temp;
}
}
}
}
int t(int x[], int n)
{
int i, j, temp;
for (i = 0; i < n - 1; i++)
{
for (j = 0; j < n - 1 - i; j++)
{
if (x[j] < x[j + 1])
{
temp = x[j];
x[j] = x[j + 1];
x[j + 1] = temp;
}
}
}
}
我们引用答案的代码做笔记:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int r(int[], int n);
int t(int[], int n);
int main(int argc,int *argv[])
{
int data[20];
int size = 0, i;
printf("\n请输入数组元素,最多输入20个,输入0时结束");
do
{
printf("data[%d]=", size);
scanf("%d", data + size);
} while (data[size++] != 0);
//定义一个do while循环,先循环后判断
//它会一直循环下去,直到循环条件不满足
//由于是先循环,判断结束的时候,要data[size++]因为判断结束后还会进行一次循环
if (argc == 2)
//判断输入参数是否满足
{
switch (*(argv[1] + 1))
//argv[0]是留给符号 - 的
//判断指令是否正确,选择指令
{
case'r':
//输入指令r,传参到r函数
r(data, --size);
break;
case'a':
//输入指令a,传参到t函数
t(data, --size);
break;
default:
//若指令错误,则输出下列语句
printf("输入错误");
}
}
//argc参数不正确,则
else
{
printf("不正确格式");
}
for (i = 0; i < 40; i++)
{
printf("-");
}
printf("\n排序后的数据:");
for (i = 0; i < size; i++)
{
printf("%d\n", data[i]);
}
printf("\n");
getchar();
return 0;
}
int r(int x[], int n)
{
int i, j, temp;
for (i = 0; i < n-1; i++)
{
for (j = 0; j < n - 1 - i; j++)
{
if (x[j] > x[j + 1])
{
temp = x[j];
x[j] = x[j + 1];
x[j + 1] = temp;
}
}
}
}
int t(int x[], int n)
{
int i, j, temp;
for (i = 0; i < n - 1; i++)
{
for (j = 0; j < n - 1 - i; j++)
{
if (x[j] < x[j + 1])
{
temp = x[j];
x[j] = x[j + 1];
x[j + 1] = temp;
}
}
}
}
最后要编译成exe,然后通过命令行下访问,输入指令,访问地址存到argv[0]中,main函数内有两个参数,argc是传入参数的总数,也就是argv的下标,argv[0]是第一段指令,它本就是一个指针数组
在答案中对argv1的值没有一个规定,也就是说不满足题目‘-’的要求,对题目中降序‘a’的指令也没有一个规定
程序通过一个do while循环来输入数组中的数,在数组值为0的时候,并结束循环,整段程序中0不计入排序内,在传参时--i,前置自减,就是把最后一个数值为0的元素舍弃,不传入函数中进行运算
由于没有虚拟机,我们模拟一下,假设编译完成的程序sort.exe存在C:根目录下
我们只需要输入
- C:>sort -t 升序排序你输入的数组元素
- C:>sort -a 降序排序你输入的数组元素
C:>存入的是argv[0],-为argv1 ,t为argv1这就是答案不严谨的原因
总结:
到这里指针与函数的所有内容都学完了,但是都是基础,在以后的运用中还会有更多加深的地方,到这儿咱们理解了:
- 指针函数:返回值为指针类型的函数(局部变量做返回值)
- 函数指针:指向用于同参数函数的指针
- 指针参数:传址中需要用到的,传入函数的是一个指针类型,值为地址
欢迎大神来补充,本文内容借鉴了多位大佬的博客,如有侵权,请联系我删除,本文内容参照“指针的艺术”编写,作者为:蔡明志
如本文内容有不足的地方,请发送到我的邮箱,有偿采纳!