可恶的C语言——函数与指针
函数是一块代码,接收零个或多个参数,做一件事情,并返回零个或一个值。
函数定义
void sum(int begin,int end)//函数头
{
int i;
int sum=0;
for(i=begin;i<=end;i++){
sum+=i;
}
printf("%d到%d的和是%d\n",begin,end,sum);
}
//大括号中为函数体。void为返回类型。sum为函数名。void表示不返回任何东西。sum后面的括号中内容为参数表,参数间用逗号隔开。
调用参数:函数名(参数值);
()起到了表示函数调用的重要作用,即使没有参数也需要()。
int main()
{
sum(1,10);
sum(20,30);
sum(35,45);
return 0;
}
从函数中返回值
return停止函数的执行,并送回一个值。一个函数里可以出现多个return语句。
没有返回值的函数:void函数名(参数表)
不能使用带值的return,可以没有return,调用的时候不能做返回值的赋值。
#include <stdio.h>
void sum(int begin,int end);
int main()
{
sum(1,10);
sum(20,30);
sum(35,45);
return 0;
}
void sum(int begin,int end)
{
int i;
int sum=0;
for(i=begin;i<=end;i++){
sum+=i;
}
printf("%d到%d的和是%d\n",begin,end,sum);
}
对于函数参数表中的参数,叫做”形式参数“。
调用函数时给的值叫做”实际参数“。
本地变量:函数的每次运行,就产生一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的。定义在函数内部的变量就是本地变量。参数也是本地变量。
变量的生存期:什么时候这个变量开始出现了,到什么时候它消亡了。
变量的作用域:在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)
C语言不允许函数嵌套定义。
指针
运算符&:获得变量的地址,其操作数必须是变量。
int i;
printf("%p",&i);
地址的大小是否与int相同取决于编译器。
指针:保存地址的变量。
int i;
int* p=&i;
//下面两种表达方式相同
int* p,q;
int *p,q;
变量的值是内存的地址。普通变量的值是实际的值。指针变量的值是具有实际值的变量的地址。
void f(int *p);
int i=o;
f(&i);
在被调用的时候得到了某个变量的地址,在函数里面可以通过这个指针访问外面的这个i。
*是一个单目运算符,用来访问指针的值所表示的地址上的变量,可做右值也可做左值。
int k=*p; *p=k+1;
指针的使用
- 需要传入较大的数据时用作参数
- 传入数组后对数组做操作
- 函数返回不止一个结果
- 需要用函数来修改不止一个变量
- 动态申请的内存
应用场景一
交换两个变量的值
#include <stdio.h>
void swap(int *pa,int *pb);
int main(void)
{
int a=5;
int b=6;
swap(&a,&b);
printf("a=%d,b=%d\n",a,b);
return 0;
}
void swap(int *pa,int *pb)
{
int t=*pa;
*pa=*pb;
*pb=t;
}
应用场景二a
函数返回多个值,某些值就只能通过指针返回。
传入的参数实际上是需要保存带回的结果的变量。
#include <stdio.h>
void minmax(int a[],int len,int *max,int *min);
int main(void)
{
int a[]={1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};
int min,max;
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("min=%d,max=%d\n",min,max);
return 0;
}
void minmax(int a[],int len,int *min,int *max)
{
int i;
*min = *max=a[0];
for(i=1;i<len;i++){
if(a[i]<*min){
*min=a[i];
}
if(a[i]>*max){
*max=a[i];
}
}
}
应用场景二b
函数返回运算的状态,结果通过指针返回。常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错。但是当任何数值都是有效的可能结果,就得分开返回了。
#include <stdio.h>
//如果除法成功返回1,否则返回2
int divide(int a,int b,int *result);
int mian(void)
{
int a=5;
int b=2;
int c;
if(divide(a,b,&c)){
printf("%d/%d=%d\n",a,b,c);
}
return 0;
}
int divide(int a,int b,int *result)
{
int ret = 1;
if (b==0)ret=0;
else{
*result=a/b;
}
result ret;
}
指针与数组
函数参数表中的数组实际上是指针,可以用数组的运算符[]进行运算。
下面四种函数原型是等价的:
int sum(int *ar,int n);
int sum(int*,int);
int sum(int ar[],int n);
int sum(int[],int);
数组变量是特殊的指针,数组变量本身表达地址。所以int a[10];int*p=a;无需用&取地址。
但是数组的单元表达的是变量,需要用&取地址。a==*a[0];
[]运算符可以对数组做,也可以对指针做。
运算符可以对指针做,也可以对数组做: * a=25;
数组变量是const的指针,所以不能被赋值
int a[]<==>int *const a=…
指针与const(只适用于C99)
指针是const表示一旦得到了某个变量的地址,便不能再指向其他变量。表示不能通过这个指针去修改那个变量,并不能使得那个变量成为const。
const int *p=&i;
*p=26;//ERROR!(*p)是const
i=26;//OK
p=&j;//OK
判断哪个被const
const int p 里的const是修饰int的,也就是说这个int值不可以改变
int const * p 里 const是修饰p的 , 而*p的类型是int 也就是说const修饰的是int , 那也是跟上面的一样int值不可变
(int) const § p的类型是int是个指针 这个指针是一个放地址的变量
const修饰的是p也就是修饰的类型是int*也就是这个指针不可变 就是地址不可变
虽然地址不能改变,但是地址指向的int的值是可以变的,const修饰的只是一个地址。
const int *p
: 值不可变
int const *p
: 值不可变
int *const p
: 地址不可变, 那个地址的值可以变
const int *const p
: 地址和值都不可变
当要传递的参数类型比地址大的时候,可以将一个非const的值转换成const的。
void f(const int* x);
int a=15;
f(&a);//ok
const int b=a;
f(&b);//ok
b=a+1;//Error!
const数组
const int a[]={1,2,3,4,5,6,};
数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int,所以必须通过初始化进行赋值。
保护数组值:因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值,为了保护数组不被函数破坏,可以设置参数为const。
int sum(const int a[],int length);
指针运算
给一个指针加1表示要让指针指向下一个变量
int a[10];
int *p=a;
*(p+1)-->a[1]
如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义。
*p++:取出p所指的那个数据来,完事后顺便把p移到下一个位置去。*的优先级虽然高,但是没有++高。常用于某些数组类的连续空间操作,在某些CPU上可直接被翻译成一条汇编指令。
指针的类型
无论指向什么类型,所以的指针的大小都是一样的,因为都是地址。但是指向不同类型的指针是不能直接互相赋值的。
指针的类型转换
void*表示不知道指向什么类型的指针。
计算时与char*相同(但不相通)。
指针也可转换类型。
int *p=&i;void *q=(void *)p;
这并没有改变p所指的变量的类型,而是以不同的眼光通过p看其所指的变量。
动态内存分配
int *a = (int *)malloc(n *sizeof(int));
#include <stdio.h>
#include<stdlib.h>
int main(void)
{
int number;
int *a;
int i;
printf("输入数量:");
scanf("%d",&number);
//int a[number];
a=malloc(number*sizeof(int));
for(i=0;i<number;i++){
scanf("%d",&a[i]);
}
for(i=number-1;i>=0;i--){
printf("%d",a[i]);
}
free(a);
return 0;
}
malloc
#include <stdlib.h>
void*malloc(size_t size);
向malloc申请的空间的大小是以字节为单位的,返回的结果是void*,需要类型转换为自己需要的类型。
如果申请失败则返回0或NULL。
free():把申请来的空间还给系统。只是还申请来的空间的首地址。
练习题
输出一个中文版九九乘法表
#include <stdio.h>
int main()
{
char a[21]={"0一二三四五六七八九十"};
int i=1,j=1,t=1,u=1;
for(i=1;i<=9;i++)
{
for(j=1;j<=i;j++)
{
if(i*j<=9&&i!=j)
{
printf("%c%c%c%c得%c%c\t",a[2*j-1],a[2*j],a[2*i-1],a[2*i],a[2*i*j-1],a[2*i*j]);
}
if(i*j<=9&&i==j)
{
printf("%c%c%c%c得%c%c\n",a[2*j-1],a[2*j],a[2*i-1],a[2*i],a[2*i*j-1],a[2*i*j]);
}
if(i*j>9&&i!=j)
{
printf("%c%c",a[2*j-1],a[2*j]);
printf("%c%c",a[2*i-1],a[2*i]);
printf("%c%c",a[2*(i*j/10)-1],a[2*(i*j/10)]);
printf("%c%c",a[19],a[20]);
if(i*j%10!=0)
{
printf("%c%c",a[2*(i*j%10)-1],a[2*(i*j%10)]);
}
printf("\t");
}
if(i*j>9&&i==j)
{
printf("%c%c",a[2*j-1],a[2*j]);
printf("%c%c",a[2*i-1],a[2*i]);
printf("%c%c",a[2*(i*j/10)-1],a[2*(i*j/10)]);
printf("%c%c",a[19],a[20]);
printf("%c%c",a[2*(i*j%10)-1],a[2*(i*j%10)]);
printf("\t");
printf("\n");
}
}
}
return 0;
}
小鱼最近被要求参加一个数字游戏,要求它把看到的一串数字 (长度不一定,以 00 结束),记住了然后反着念出来(表示结束的数字 00 就不要念出来了)。这对小鱼的那点记忆力来说实在是太难了,你也不想想小鱼的整个脑袋才多大,其中一部分还是好吃的肉!所以请你帮小鱼编程解决这个问题。
#include <stdio.h>
int main()
{
int a[100],i,t;
while(t!=0){
if(i<=99){
printf("请输入一个整数:");
scanf("%d",&t);
i++;
a[i]=t;
}else{
t=0;
a[100]=0;
}
}
do{
i--;
printf("%d ",a[i]);
}while(i>=2);
return 0;
}
输入L、R两个数(L一定小于或等于R且R小于1000),然后输入一个小于10的整数n,请统计范围[L,R]的整数中n出现的次数(如n=2,2 、12、 20、22、222等,注意当n=0时可能会出现bug)
#include <stdio.h>
int main()
{
int n,t,u,i=0;
double L,R;
printf("请输入两个小于1000的数:");
scanf("%lf %lf",&L,&R);
printf("请输入一个小于10的整数:");
scanf("%d",&n);
if(R<L)
{
u=R;
R=L;
L=u;
}
if(L/100!=0)
{
t=L;
while(t<=R)
{
if(t/100==n)
{
i++;
}
if(t/10%10==n)
{
i++;
}
if(t%10==n)
{
i++;
}
t++;
}
}
if(L/100==0&&L/10!=0)
{
t=L;
while(t<=R)
{
if(t/100==n)
{
i++;
}
if(t/10%10==n)
{
i++;
}
if(t%10==n)
{
i++;
}
t++;
}
}
if(L/10==0)
{
t=L;
while(t<=R)
{
if(t/100==n)
{
i++;
}
if(t/10%10==n)
{
i++;
}
if(t%10==n)
{
i++;
}
t++;
}
}
printf("%d",i);
return 0;
}
输入10个打乱的整数,使用冒泡排序将这10个数从小到大排好并输出(可以百度冒泡排序,但是要理解怎么用)
#include <stdio.h>
int main()
{
int i,j,t,a[10];
for(i=0;i<10;i++)
{
scanf("%d",&a[i]);
}
for(i=0;i<10;i++)
{
for(j=0;j<10;j++)
{
if(a[i]<a[j])
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
}
for(i=0;i<10;i++)
{
printf("%d ",a[i]);
}
return 0;
}
输入一个4x5的矩阵,输出它的转置矩阵
#include <stdio.h>
int main()
{
int i,j,a[10][10];
for(i=0;i<4;i++)
{
for(j=0;j<5;j++)
{
scanf("%d",&a[i][j]);
}
}
for(j=4;j>=0;j--)
{
for(i=3;i>=0;i--)
{
printf("%3d",a[i][j]);
}
printf("\n");
}
return 0;
}
夏天到了,各家各户的用电量都增加了许多,相应的电费也交的更多了。小玉家今天收到了一份电费通知单。小玉看到上面写:据闽价电[2006]27号规定,月用电量在150千瓦时及以下部分按每千瓦时0.4463元执行,月用电量在151~400千瓦时的部分按每千瓦时0.4663元执行,月用电量在401千瓦时及以上部分按每千瓦时0.5663元执行;小玉想自己验证一下,电费通知单上应交电费的数目到底是否正确呢。请编写一个程序,已知用电总计,根据电价规定,计算出应交的电费应该是多少。
#include <stdio.h>
int main()
{
int amount;
float money;
printf("请输入用电总计:");
scanf("%d",&amount);
if(amount<=150)
{
money=amount*0.4463;
}
else if(amount<=400)
{
money=150*0.4463+(amount-150)*0.4663;
}
else
{
money=150*0.4463+250*0.4663+(amount-400)*0.5663;
}
printf("应交电费为%.1f元",money);
return 0;
}
输入一串长度为20的字母串,其中有大写有小写,请将里面的大写转为小写,小写转为大写后输出
#include <stdio.h>
int main()
{
int i;
char a[20];
for(i=0;i<20;i++)
{
scanf("%c",&a[i]);
}
for(i=0;i<20;i++)
{
if(a[i]>='A'&&a[i]<='Z')
{
printf("%c",a[i]+='a'-'A');
}
else
{
printf("%c",a[i]+='A'-'a');
}
}
return 0;
}
补充
当形式参数是一维数组时,可以不说明数组的长度。