C语言函数(函数嵌套、递归调用)+局部变量和全局变量+extern关键字的使用+Visual Studio简单的使用教程+数据存储类别+内部函数外部函数

上一篇文章:编译预处理知识点梳理:宏定义+文件包含+条件编译

写在前面

Embrace The Journey

“There is nothing sexy or meme-worthy about the journey. It’s hard. It’s painful. It’s not glossy and doesn’t lend itself to a hashtag or a glib tweet. It will never trend on Twitter. This is not news […] so if you have a passion and aspire to greatness — if you want to see what you are truly made of, or just how far you can go and what you are truly capable of — forget the hack. Commit to the daily pressure that compels infinitesimal progress over time. Wake up before dawn and apply yourself in silent anonymity. Practice your craft — in whatever shape or form that may be — late into the evening with relentless rigor. Embrace the fear. Let go of perfection. Allow yourself to fail. Welcome the obstacles. Forget the results. Give yourself over to your passion with every fiber of who you are. And live out the rest of your days trying to do better.”
--------------- Rich Roll

中文大意:

这一路

  • 你满怀激情,渴望成就一番事业,渴望找到真正的自己,想看看自己能走多远,到底有什么能耐。可是一路上,没有迷人的风景,非常困难和艰苦,也很平谈,没有值得一提的事情。
  • 你只能老老实实走下去。
  • 日复一日,你感受着压力,强迫自己一点点提升。天不亮就起床,在寂静中用各种手段,严格锤炼自己,提高技艺,直到夜深人静。你开始习惯内心的恐惧,放弃追求完美,忍受一次次的失败,不相信捷径,不去想结果。
  • 终于有一天,你找到了真正的自我,发挥出百分之百的激情,发现接下来每一天,一切都在变得更好一点。

博观而约取,厚积而博发。



函数概述

之前的博客也说过,可以把一个复杂的任务分解为子任务,每一个子任务都是一个较小的功能模块,通过实现这些小功能模块来实现复杂的问题,从而使得比较复杂的问题变得简单化。

现在简单对前面的内容进行补充

  • 函数的意义
    函数是组成程序的基本单位,每一个C程序中包含一个main()主函数和若干个其他函数。程序的执行永远是从main()函数开始,调用其他函数后仍然回到main()函数,程序在main()函数结束时结束。可以认为函数是因为被调用才执行的,main()函数也需要被调用才会执行。一般认为main()函数是被系统调用的。C语言中,所有函数之间的关系都是平行的,任何函数都不隶属于其他函数,每个函数的定义是独立进行的,所以不能在一个函数内部再嵌套定义一个函数
    函数之间可以相互调用,也可以嵌套调用。函数还可以自己调用自己,称为递归调用。(调用函数时没有层数限制,可以多个函数嵌套,但是考虑到结构清晰和内存限制,一个函数层次不宜过多)
    假如我在函数1中调用函数2,函数1习惯上被称为主调函数,函数2习惯上被称为被调函数
  • 函数的设计原则
    因为C语言的函数实现的是模块的功能,所以模块设计原则也适用于函数的设计
    设计函数时要注意:
    1.函数功能独立,每个函数实现一个特定的功能。每个函数与其他函数的关系除了调用关系外,不能再有其他关系,这样才能保证在修改本函数时不会对其他函数产生影响。(特定情况下使用全局变量使各个函数间保持联系,在前面的博客中使用了全局变量的二维数组来进行推箱子小游戏的编程全局变量与局部变量在之前的博客中提到过,本文后面会再说
    2.函数的规模大小要适中,一个函数中不要实现太多功能,否则不利于代码的重利用。(当然不是绝对的,具体问题具体分析,必要时可以把函数写得大一些,不必记住这些死的规则,简单了解便好)
  • 函数的分类
    C语言的函数有两种:
    1.前人开发C语言时已经写好的库函数,我们直接拿过来调用就行了。只不过需要先以 #include <XXX.h> 的形式先把这些函数的声明包含进来,XXX.h头文件中是一些库函数的声明,相当于一个导游。比如我们要使用printf()函数时,需要先#include <stdio.h>
    2.用户自定义函数,根据具体问题的需要,用户自己写函数供自己调用。在之前的博文中,已经提到过子函数。

函数的定义与调用

  • 定义:
    函数类型 函数名 (参数表)
    {
    语句;
    }

    调用:
    函数名(实际参数表); 实际参数表就是要传入的参数,简称实参
    这个在前面的博文中已经说过了,链接为C语言代码示范与讲解+C语言编程规范及基础语法+编程实战,目录3.2详细讲解中已经说过了子函数定义。
  • {}花括号里面的是函数体,知道就行了
  • 子函数返回值可以是一个逻辑判断语句,判断为真返回1,判断为假返回0
  • 空函数:void nothing() {}
    可以使用空函数进行占位,等程序功能扩展时加入具体有意义的内容。
  • 返回值的类型与函数类型保持一致,之前的博文中说过
  • 传入的实参的数据类型要与形参的数据类型保持一致
  • 实参可以是常量、变量、表达式或者有返回值的子函数,只要确保数据类型一致即可
  • 对于无参函数,实参表可以没有,但是括号不能省略
  • 如果一个函数是有返回值的函数,那么可以在另外一个有实参的函数中作为实参进行调用,含义是将这个函数的返回值作为另外一个函数的实参,这个就是函数的嵌套调用
  • 函数返回值:return (返回值表达式);
    返回值外面的括号可要可不要,这个之前也是提到过的
  • 函数定义的形参表只能在该函数内使用,它属于子函数内部的局部变量,只有函数被调用时才会给形参分配存储单元,一旦子函数运行结束,分配的存储单元就会被回收
  • 求素数和代码案例
    输出范围在[x,y]之间的所有素数,并计算这些素数之和
#include <stdio.h>
int prime(int m)
{
	int i;
	for(i=2;i<m;i++)
		if(m%==0)
			return 0;
	if(i>=m)
	{
		printf("%4d",m);
		return 1;
	}
}
void main()
{
	int x,y,i,t;
	int s=0;
	printf("input x & y:\n");
	scnaf("%d%d",&x,&y);
	if(x>y)
	{
		t=x;
		x=y;
		y=t;
	}
	for(i=x;i<=y;i++)
		if(prime(i))
			s=s+i;
	putchar('\n');
	printf("The Sum of prime number in x and y is %d\n",s);
}

函数的调用

嵌套调用

嵌套调用就是一个函数调用另一个函数,而另外一个函数又调用了其他函数,这个就是函数的嵌套调用,有点类似于嵌套循环。函数的嵌套调用没有层数限制。

  • 利用函数的嵌套调用计算S = f(-n) + f(-n+1) + f(-n+2) + f(-n+3) + … … + f(n-3) + f(n-2) + f(n-1) + f(n)
    其中函数f(x)为:
    在这里插入图片描述
  • 代码:
#include <stdio.h>

第1行为编译预处理

double sum(int n);//n为子函数sum的形参
double f(double x);//x为子函数f的形参

第2、3行为两个子函数的声明

main()
{
	int n;
	printf("input n:\n");
	scanf("%d",&n);
	printf("%f\n",sum(n));//调用了子函数sum,将输入的n的值传给子函数sum的形参n
}

第4~10行为主函数部分,主要实现输入和输出

double sum(int n)
{
	int i;
	double s=0;
	for(i=-n;i<=n;i++)
		s+=f(1.0*i);//这里有个数据类型转换,1.0是double型,整型的i要转换成double型,并将计算的结果作为实参传给子函数f的形参
	return s;
}

第11~18行为第一个子函数的具体代码,实现-n到n之间的循环

double f(double x)
{
	if(x==0||x==2)
		return 0;
	else if(x>0&&x!=2)
		return (x+1)/(x-2);
	else
		return (x-1)/(x-2);
}

最后为第二个子函数的具体代码,实现函数f(x)的功能

递归调用

  • 递归就是一个函数调用自己,分为直接调用自己和间接调用自己,即直接递归间接递归
    直接递归:函数自己函数体中出现调用自己的语句
    间接递归:函数调用另一个函数,而另一个函数反过来又调用了这个函数,即通过调用另一个函数间接地调用了自己,叫做间接递归。
    无论是直接递归还是间接递归,它们的本质其实就是函数的调用而已
    其实没必要死记硬背,我觉得这些东西都是人们为了方便交流而起的一个名字而已,目的是为了让你能够在听到这些名字的时候知道它是个什么东西,好方便大家交流,知道就可以了,最关键的是会用就行了。

递归实现循环作用

#include <stdio.h>
f(int x)
{
	if(x/2>0)
		f(x/2)
	printf("%d   ",x);
}
main()
{
	f(10)
	printf("\n");//或putchar('\n');
}

主函数传给子函数f的参数为10,10来参与子函数中的运算,判断10/2=5>0,所以5作为实参传给子函数自己作为形参,再次执行判断5/2=2(整型相除自动省去小数部分),2大于0,所以2作为实参再次执行判断2/2=1,1大于0,再次执行判断1/2=0,此时0不大于0,所以输出为0,再回到主函数,输出个换行,然后整个主函数程序结束


递归算法逆序输出一个整数

#include <stdio.h>
void reverse(int num)
{
	if(num<=9)
		printf("%d",num);
	else
	{
		printf("%d",num%10);
		reverse(num/10);
	}
}
int main()
{
	int n;
	scanf("%d",&n);
	reverse(n);
	return 0;
}

递归求阶乘

n*factorial(n-1);//英文单词factorial即阶乘

#include <stdio.h>
long factorial(int n)
{
	long f;
	if(n>1)
		f=factorial(n-1)*n;
	else
		f=1;
	return (f);//返回值的括号可加可不加
}
main()
{
	int n;
	long y;
	printf("input a inteager number:\n");//英文单词inteager整数
	scanf("%d",&n);
	y=factorial(n);
	printf("%d!=%ld\n",n,y);
}

用递归法求斐波那契数列的第n项

斐波那契数列:1,1,2,3,5,8,13,21……
从第三项开始,前两项的和等于该项。

#include <stdio.h>
long fib(int n)
{
	if(n==1||n==2)
		return 1;
	else
		return fib(n-1)+fib(n-2);
}
main()
{
	int n;
	long f;
	printf("input n:\n");
	scanf("%d",&n);//输入要求的第n项
	f=fib(n);
	printf("%d\n",f);
}

用递归法求两个正整数的最大公约数

#include <stdio.h>
int gcd(int m,int n)
{
	int g;
	if(m%n==0)
		g=n;
	else
		g=gcd(n,m%n);
	return g;
}
void main()
{
	int m,n;
	printf("input m & n:\n");
	scanf("%d%d",&m,&n);
	printf("the sum in x and y is %d\n",gcd(m,n));
}

递归算法解决汉诺塔问题

汉诺(Hanoi)塔问题。古代某寺庙中有一个梵塔,塔内有3个底座A、B和C,座A上放着64个大小不等的盘,其中大盘在下,小盘在上。有一个和尚想把这64个盘从座A搬到座B,但一次只能搬一个盘,搬动的盘只允许放在其他两个座上,且大盘不能压在小盘上。现要求用程序模拟该过程,并输出搬动步骤。

#include <stdio.h>
void hanoi(int n,char a,char b,char c);
int main()
{
	int n;
	printf("input the number of disk:");
	scanf("%d",&n);
	printf("the steps for %d disk are:\n",n);
	hanoi(n,'a','b','c');

	return 0;
}
void hanoi(int n,char a,char b,char c)
{
	if(n==1)
		printf("%c-->%c\n",a,b);
	else
	{
		hanoi(n-1,a,c,b);
		printf("%c-->%c\n",a,b);
		hanoi(n-1,c,b,a);
	}
}

在这里插入图片描述
假如有三个盘,那么按照规则,将三个盘搬运到B座上的步骤如下:
在这里插入图片描述
需要7步才能完成。如果是n个盘子需要2的n次方减1次。当n为64时,搬动次数约为10的19次幂次。


再来看一个,这个是之前的博客C语言代码示范与讲解+C语言编程规范及基础语法+编程实战中第三部分第42案例中说过的,但是没有详细介绍,这里再拿来补充一下。

递归算法解决爬楼梯问题

现在有n阶楼梯,有个小孩去爬楼梯,一次只能爬1阶或2阶,求爬到顶端共有多少种不同的方法
假如楼梯阶数为3,我可以1+1+1爬上去,我也可以1+2爬上去,我也可以2+1爬上去,所以共有3种爬到顶端的方法;
假如楼梯阶数为6,我可以1+1+1+1+1+1爬上去,我也可以2+1+1+1+1爬上去,也可以1+2+1+1+1爬上去,也可以1+1+2+1+1爬上去,也可以1+1+1+2+1爬上去,也可以1+1+1+1+2爬上去,也可以2+2+1+1爬上去,也可以2+1+2+1爬上去,也可以2+1+1+2爬上去,也可以1+2+2+1爬上去,也可以1+2+1+2爬上去,也可以1+1+2+2爬上去,也可以2+2+2爬上去,所以共有13种爬到顶端地方法。
通过这种一个个列举的方法,只要不嫌麻烦,也可以,但是当我输入地楼梯阶数为100的时候,恐怕没有人乐意会去一个个列举,所以我们请计算机来帮忙,但是我得告诉计算机该怎么做,所以要编程
代码:(递归法)

#include <stdio.h>
int f(int n)
{
	if (n == 1) return 1;
	if (n == 2) return 2;

	return f(n - 1) + f(n - 2);

}
main()
{
	int n, t;
	scanf_s("%d", &n);
	t = f(n);
	printf("%d\n", t);
}

输入的n为楼梯的阶数,当我输入n为6,为了方便理解,我做了一个表格:
在这里插入图片描述
再简化一下,我做了一个树状图:
在这里插入图片描述
我们可以逆向思维,想象着我们站在楼梯的最顶端,我们想要下楼梯,一次只能下一阶或两阶,求总共有多少种走完台阶的方法。第一步可以-1或-2,然后再-1或-2,实现多个分支,直到只剩下1阶或2阶为止,如果只剩下1阶了,只有一种方法,如果只剩下2阶了,可以直接-2下去,也可以-1-1下去,两种方法,所以我们把所有末端分支上的数值加在一起就是所有走完台阶的方法。将所有末端分支的返回值加在一起就是我们想要的结果13。
在这里插入图片描述
连续运行多次就可以发现这是一个去掉第一项的斐波那契数列

数组作为函数参数

  • 判断字符串中单词的数量(数组作为函数实参)
#include  <stdio.h>
int ispace(char c)
{
	if(c==' ')
		return 1;
	return 0;
}
main()
{
	char str[30];
	int n=0,i;
	printf("input a string!\n");
	gets(str);
	for(i=0;str[i]!=0;i++)
	{
		if(ispace (str[i]))
			n++;
	}
	printf("The number words in dialoag is %d\n",n+1);
}

  • 一维数组作为函数的参数时,数组名在函数传递过程中,既可以作形参,也可以作实参。数组名作函数参数时,要求形参和实参都必须是类型相同的数组(或指向数组的指针变量),且都必须有明确的数组说明。数组名作为函数的实参与形参,数组是指向同一地址空间的。当主函数执行时,这段空间由实参数组控制,当被调用函数执行时,该段空间由形参数组使用,当函数调用结束,这段空间的使用权又回到实参数组。
  • 设计一个程序,要求能实现数组元素输入、输出、排序等功能。要求输入、输出、排序分别用不同的函数实现
#include <stdio.h>
#define N 10
void input(int a[]);
void output(int a[]);
void sort(int a[]);
void main()
{
	int a[N]={0};
	input(a);
	sort(a);
	output(a);
}
void input(int x[])
{
	int i;
	for(i=0;i<N;i++)
		scanf("%d",&x[i]);
}
void output(int x[])
{
	int i;
	for(i=0;i<N;i++)
		printf("%d ",x[i]);
	putchar('\n');
}
void sort(int a[])
{
	int i,j,t;
	for(i=0;i<9;i++)
		for(j=0;j<9-i;j++)
			if(a[j]>a[j+1])
			{
				t=a[j];
				a[j]=a[j+1];
				a[j+1]=t;
			}
}
  • 多维数组名作为函数参数
    二维数组和一维数组一样,二维数组既可以作为函数的实参,也能作为函数的形参,二维数组可以省略行长度,但不能省略列长度。
  • 将一个N*N矩阵的最外围元素顺时针移动
#include <stdio.h>
#define N 4
void output(int x[N][N])
{
	int i,j;
	for(i=0;i<4;i++)
	{
		for(j=0;j<4;j++)
			printf("%4d",x[i][j]);
		putchar('\n');
	}
}
void fun(int x[N][N])
{
	int t[N],i;
	for(i=0;i<N;i++)
		t[i]=x[i][N-1];
	for(i=0;i<N;i++)
		x[i][N-1]=x[0][i];
	for(i=0;i<N;i++)
		x[0][N-1-i]=x[i][0];
	for(i=0;i<N;i++)
		x[i][0]=x[N-1][i];
	for(i=0;i<N;i++)
		x[N-1][i]=t[N-1-i];
}
main()
{
	int a[N][N]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
	printf("the original array:\n");
	output(a);
	printf("after change:\n");
	fun(a);
	output(a);
}

在这里插入图片描述

局部变量与全局变量

关于局部变量和全局变量,其实前面的博客C语言代码示范与讲解+C语言编程规范及基础语法+编程实战一文中的第三部分目录32.局部变量与全局变量中已经提到过,这里我直接引用过来,并稍微增加一点内容:

局部变量

  • 在程序中使用的变量都定义在函数的内部,它们的有效使用范围被局限于所在的函数内。变量的有效使用范围称为变量的作用域。
    因此主函数只有通过参数传递,才能把实参数据传递给函数使用;同样,形参的改变也不会影响到实参变量。这种变量的有效使用范围,最大程度确保了各函数之间的独立性,避免函数之间的相互干扰。 C语言中把定义在函数内部的变量称为局部变量,局部变量的有效作用范围局限于所在函数内部。形参是局部变量。
  • C语言还允许定义作用于复合语句中的局部变量,其有效使用范围被局限于复合语句中的局部变量,其有效使用范围被局限于复
    合语句内,一般用作小范围内的临时变量
    ,举例:
int main(void)
{
	int a=1;	//主函数的局部变量
		{	//复合语句开始
			int b=2;	//复合语句内的局部变量
			...
		}	//复合语句结束
		printf("%d",a);

		return 0;
}

在标准C语言中变量的定义必须在函数的最顶部,但是在C++中可以把变量的定义放在函数的中间

全局变量

  • 局部变量虽然保证了函数的独立性,但程序设计有时还要考虑不同函数之间的数据交流,及各函数的某些统一设置。当一些变量需要被多个函数共同使用时,参数传递虽然是一个办法,但必须通过函数调用才能实现,并且函数只能返回一个结果,这会使程序设计受到很大的限制。为了解决多个函数间的变量共用,C语言允许定义全局变量。定义在函数外而不属于任何函数的变量称为全局变量。全局变量的作用范围是从定义开始到程序所在文件的结束,它对于作用范围内所有的函数都起作用。全局变量的定义格式与局部变量完全一致,只是定义位置不在主函数内,它可以定义在程序的头部,也可以定义在两个函数的中间或程序尾部,只要定义在函数外即可
  • 局部变量和全局变量的作用范围不同,在定义的时候允许它们重名。当某个函数的局部变量与全局变量同名时,在该函数中全局变量不起作用,而又局部变量起作用。对于其他不存在同名变量的函数,全局变量仍然有效。同样,当函数局部变量与复合语句的局部变量同名时,以复合语句为准。
  • 全局变量可以帮助解决函数多结果返回的问题,但全局变量更多地用于多函数间的全局数据表示
#include <stdio.h>
float result_real, result_imag;//全局变量,用于存放函数结果
//函数声明
void complex_prod(float real1, float imag1, float real2, float imag2);
void complex_add(float real1, float imag1, float real2, float imag2);
int main()
{
	float imag1, imag2, real1, real2;
	printf("Enter 1st complex number (real and imaginary):");
	scanf_s("%f%f",&real1,&imag1);
	printf("Enter 2nd complex number (real and imaginaty):");
	scanf_s("%f%f", &real2, &imag2);
	complex_add(real1, imag1, real2, imag2);//求复数之和
	printf("addition of complex is %f+%fi\n",result_real,result_imag);
	complex_prod(real1, imag1, real2, imag2);
	printf("product of complex is %f+%fi\n",result_real,result_imag);

	return 0;
}
void complex_add(float real1, float imag1, float real2, float imag2)
{
	result_real = real1 + real2;
	result_imag = imag1 + imag2;
}
void complex_prod(float real1, float imag1, float real2, float imag2)
{
	result_real = real1 * real2 - imag1 * imag2;
	result_imag = real1 * imag2 + real2 * imag1;
}
extern全局变量的说明
  • 全局变量的定义和外部变量的说明是两回事全局变量的定义必须在所有的函数之外,且只能定义一次。而全局变量的说明,出现在要使用该外部变量的函数内,且可以出现多次
    声明的格式为:
    extern 数据类型 外部变量名
    例如:extern float a,b;
  • 全局变量定义在函数之外,可以是任意位置,但其可以被使用的范围是从定义处到源文件末尾,对于定义处之前的函数如果想使用全局变量,必须使用extern关键字声明,例如:
#include <stdio.h>
#define PI 3.141592
void fcircle(int r)
{
	extern double s,c;//s和c是在最后定义的
	//所以定义处前面的函数要使用这两个变量就要使用关键字extern声明
	s=PI*r*r;
	c=2*PI*r;
}
main()
{
	int r;
	extern double s,c;
	printf("input radias:\n");
	scanf("%d",&r);
	fcircle(r);
	printf("s=%10.2f,c=%10.2f\n",s,c);
}
double s,c;//这两个全局变量定义在了最后

如果不用extern关键字声明全局变量,以上代码这样修改就可以了:

#include <stdio.h>
#define PI 3.141592
double s,c;//这两个全局变量定义在最前面
void fcircle(int r)
{
	s=PI*r*r;
	c=2*PI*r;
}
main()
{
	int r;
	printf("input radias:\n");
	scanf("%d",&r);
	fcircle(r);
	printf("s=%10.2f,c=%10.2f\n",s,c);
}
利用extern扩大外部变量的作用域+Visual Studio简单的使用教程

这里使用VS做一个示例:
打开VS:file----New----Project:
在这里插入图片描述
在这里插入图片描述
选择Empty Project,保存项目位置,点击Create
在这里插入图片描述
右击Source Files,Add New item
在这里插入图片描述
选择C++ File:
在这里插入图片描述
文件名后面的 .cpp改为 .c
在这里插入图片描述
.cpp是c++的源文件,.c是C语言的源文件,点击Add
然后同样的步骤再创建一个 .c的源文件:我给他命名为source2.c
在这里插入图片描述
在第一个源文件中写入:

#include <stdio.h>
int n;
main()
{
	printf("input n:\n");
	scanf_s("%d",&n);
	printf("%d\n",fun(n));//调用另一个源文件中的fun()函数
}

因为变量n是在source.c中定义的全局变量,所以在source2.c文件中若想要使用,需要使用extern关键字声明:

int fun()
{
	extern int n;
	int i,s=0;
	for(i=1;i<=n;i++)
		s+=i;
	return s;
}

也可以把声明写在最上面:

extern int n;
int fun()
{
	int i,s=0;
	for(i=1;i<=n;i++)
		s+=i;
	return s;
}

在这里插入图片描述
运行:
在这里插入图片描述
其实在VS编译器中不对全局变量n进行声明也是可以正常运行的,但是在DevC++中无法进行这样的骚操作,必须修改代码为:
在这里插入图片描述
这样才可以正常运行:
在这里插入图片描述
这只是个非常简单的示例,以后用到的可能会更加复杂

数据的存储类别

  • 在C语言中每个变量都有以下三个属性:
    1.数据类型
    2.作用域
    3.存储类别:变量在内存中的存储方式,不同的存储方式决定了变量在内存中的存在时间,即生存周期,根据变量的生存周期的不同可将变量分为静态存储方式和动态存储方式
  • 动态存储与静态存储
    一般认为C原语言程序在内存中是分成几个存储区来存储代码和数据的:
    1.代码区:
    C语言源代码经过编译链接后形成可执行的机器代码,这部分代码是CPU执行的指令部分,只读,可共享
    2.只读数据区:
    存储程序中不会被改变的数据,如各种常量及符号常量等,这些数据不允许被修改
    3.静态存储区:
    全局变量和静态变量都存储在此处,其中初始化的全局变量和静态变量存放在初始化数据区,未初始化的全局变量和静态变量存储在未初始化数据区。未初始化的全局变量和静态变量,系统会自动将数值型初始化为0,字符型初始化为'\0'。这些变量只有程序结束才会被释放。
    4.动态存储区:
    动态存储区又分为堆和栈
    :由程序员进行分配和释放。一般使用malloc()、calloc()等函数申请的内存空间都是在堆上,这些内存空间如果不再使用应该由程序员用命令释放。如free()函数,如果程序员不释放,程序结束后可由操作系统回收。
    :由编译器自动分配释放。函数中所有使用的形参及未加static修饰的普通变量都存储在此处。这些变量当函数被调用时才会被分配内存空间,一旦函数调用结束,所分配空间马上释放。
    所以静态存储区的变量在程序执行的全过程中始终占据着大小固定的存储单元,直到程序运行结束才予以释放。动态存储的变量是指变量的存储单元在程序运行过程中由系统动态地分配和回收,当定义它们的函数被调用时才会被分配内存空间,函数调用结束,系统收回变量所占内存

根据变量是静态存储方式还是动态存储方式,可将变量分为4种:auto变量、static变量、register变量、extern变量,一下对前三个详细介绍,最后一个extern变量已经在上面说过了

auto变量

  • auto型变量是经常使用的变量,平时使用的变量都是auto型变量,通常情况下为了编程的简洁,直接把auto省略掉了,其实它的定义是这样的:auto int n;,由于auto可以省略所以平时使用的时候都是直接int n;
  • auto变量是动态存储方式,是局部变量,作用范围局限于被定义的函数内;当函数别调用时,系统为之分配内存空间,函数调用结束,所分配的空间被释放
  • auto变量定义后没有初始化,其值是不确定的
  • 由于自动变量的作用域和生存周期,都局限于函数体或复合语句内,所以允许不同函数体或复合语句中定义重名的变量。
    例如:
//余数操作
#include <stdio.h>
int fun(int a,int b)
{
	auto int t;
	if(a<b)
	{
		int t=a;//此处定义的t只在这个if后面的这个花括号复合语句中有效
		a=b;
		b=t;
	}
	t=a%b;
	return t;
}
main()
{
	int a,b,t;
	scanf("%d%d",&a,&b);
	t=fun(a,b);
	printf("%d   \n",t);
}
  • 存储类别和数据类型表示符在定义的时候可以省去一个,比如:
    我要定义一个整型的变量n,完整的定义为:auto int n;
    假如我省去auto,int n;那就是默认为动态存储的
    假如我省去int,auto n;那就默认为整型的
    注意这种操作在VS编译器中可以成功编译,在DevC++中会报错
    在这里插入图片描述
    在这里插入图片描述

static变量

  • 静态局部变量:
    局部变量之前加上关键字static,局部变量就被定义成了一个局部静态变量,局部静态变量和全局变量一样存储在静态存储区
    关于静态局部变量请参见我之前的博客C语言代码示范与讲解+C语言编程规范及基础语法+编程实战一文中的第三部分的目录34.3:由于存储单元被保留,一旦含有静态局部变量的函数再次调用,则静态局部变量会被重新激活,上一次函数调用后的值仍然保存着,可供本次调用使用。 下面是利用静态局部变量求阶乘案例:
#include <stdio.h>
double fact_s(int n);//这里的double可以改为int
int main()
{
	auto int i, n;//自动变量的定义(局部变量为自动变量),auto可以省略,局部变量有变量生存周期//全局变量的生存周期为整个程序
	printf("Input n:");
	scanf_s("%d",&n);
	for (i = 1; i <= n; i++)
		printf("%3d!=%.0f\n",i,fact_s(i));//%.0f可以改为%d
	return (0);//return返回值的括号可要可不要,无影响
}
double fact_s(int n)//double可以改为int
{
	//静态局部变量的定义用static 类型名 变量名
	static double f = 1;//double可以改为int
	f = f * n;

	return (f);//return返回值的括号可要可不要,无影响
}
  • 静态全局变量:
    static关键字也可以加在全局变量的前面,定义的变量为静态全局变量意思是该变量只能在定义它的源文件中使用,其他源文件中不能使用,一个C语言程序可以由多个源文件(.c)组成,在一个源文件中定义的全局变量可以被本程序中的每个源文件使用。但是如果在全局变量前面加上了static关键字,就构成了静态的全局变量,可以使用的范围只在该源文件内部。静态全局变量可以避免其他模块对全局变量的调用,防止出现错误,同时也降低了模块之间的耦合度。

register变量

一般情况下,变量数组等都是存储在内存中的,如果程序中某个代码段(如循环)会对一个变量反复读写,这样就需要频繁访问内存,如果将频繁操作的变量放在CPU内部,就不需要访问内存了。C语言提供了寄存器变量。这种变量存放在CUP的寄存器中,使用时不需要访问内存,而直接从寄存器中读写,寄存器变量的定义形式为:register 数据类型 变量名;

  • 变量存储在寄存器中读写操作比存储在内存中快,所以建议将循环次数较多的循环控制变量及循环体内反复使用的变量均定义为寄存器变量
  • 计算机系统中寄存器的数目是有限的,所以不能把所有的变量全部定义为寄存器变量。C语言编译系统会自动将超过数目限制的寄存器变量当作自动变量进行处理
  • 只有局部变量和形参可以作为寄存器变量
  • 对于寄存器变量,当函数调用时,会占用一些寄存器存放寄存器变量,当函数调用结束后,释放所占用的寄存器
  • 不同编译系统对寄存器变量的处理是不一样的,有的编译系统直接把寄存器变量作为auto变量,分配内存空间,并不真的将它们存放在CPU的寄存器中
    举例:
#include <stdio.h>
int fun(int n)
{
	register int s=0,i;
	for(i=1;i<=n;i++)
		s+=i;
	return s;
}
main()
{
	int n;
	printf("input n:\n");
	scanf("%d",&n);
	printf("%d\n",fun(n));
}

程序中的fun()函数的i为循环变量,s为每次循环需要累加操作,所以经常使用,因此将其定义为寄存器变量

内部函数与外部函数

内部函数
内部函数就是在函数名和函数类型前面加上static的函数,即:
static 类型标识符 函数名(形参表);
函数前面加上static,是对该函数使用范围的限制,意思是将该函数的使用范围限制在该源文件内部,其他源文件不可使用。
一个项目通常情况下不是一个程序员开发的,而是团队合作,多个程序员共同开发的。使用内部函数可以使不同程序员在进行开发的时候,不必考虑自己的函数是否和其他人定义的函数重名的问题。不同的文件中同名的内部函数,互不干扰。

外部函数
定义函数时,如果函数类型前未加static关键字,可以认为都是外部函数。外部函数的作用域是整个程序,程序中的任何一个文件都可以使用它。外部函数的完整定义形式为extern 类型标识符 函数名(形参表);,其中extern可以省略。外部函数在调用它的文件中使用时,需要提前声明,(在VS编译器中,调用外部函数时也可以不声明。声明可以使代码读起来更易懂,一看就可以直到它是个外部函数),声明的形式为:extern 函数类型 函数名(形参表),函数名(形参表);函数的类型相同时,一个extern关键字后面可以声明多个函数
外部函数的使用举例:如图
在这里插入图片描述
也可以把外部函数的声明写在主函数内部:如图
在这里插入图片描述
二者最后的运行结果为:
在这里插入图片描述
在VS编译器中,不对外部函数进行声明,也是可以正常运行的,但是在DevC++中必须对外部函数进行声明,否则无法使用。

函数设计举例

IP地址二进制形式输出

输入一个十进制形式的IP地址,将其转换为二进制形式输出。例如,输入210.31.197.23,输出为11010010 00011111 11000101 00010111

#include <stdio.h>
char b[9];
void binary(int d);
void init();
void output();
main()
{
	int d1,d2,d3,d4;
	d1=d2=d3=d4=0;
	printf("input dec ip address:\n");
	scanf("%d.%d.%d.%d",&d1,&d2,&d3,&d4);
	printf("%3d.%3d.%3d.%3d\n",d1,d2,d3,d4);
	printf("bin ip address:\n");
	init();//初始化数组b
	binary(d1);//转换成二进制
	output();//输出
	init();//初始化数组b
	binary(d2);
	output();
	init();
	binary(d3);
	output();
	init();
	binary(d4);
	output();
}
void output()
{
	int i;
	for(i=7;i>=0;i--)
		printf("%c",b[i]);
	printf(" ");
}
void binary(int d)
{
	int i=0,n;
	while (d>0)
	{
		n=d%2;
		d/=2;
		b[i++]=n+'0';
	}
}
void init()
{
	int i;
	for(i=0;i<8;i++)
		b[i]='0';
	b[i]=0;//超出范围的字符数组赋空值
}

在这里插入图片描述

字符串替换

统计一个字符串str中出现另一个字符串s的个数,并将所有的字符串s替换为t字符串,所有的字符串都由键盘输入

#include <stdio.h>
#include <string.h>
char str[80];//定义全局变量
char s[10];
char t[10];
void output();
void input();
int count();
void replace();
main()
{
	input();
	printf("before replace, the string is:\n");
	output();
	printf("The total number of s in string is %d\n",count());
	replace();
	printf("after replace, the string is:\n");
	output();
}
void output()
{
	puts(str);
}
void input()
{
	printf("please input string:\n");
	gets(str);
	printf("input s:\n");
	gets(s);
	printf("input t:\n");
	gets(t);
}
int count()
{
	int i,j=0,n=0,k;
	int l=strlen(s);
	for(i=0;str[i]!=0;i++)
	{
		j=0;
		if(str[i]==s[j])
		{
			k=i;
			while(s[j]!=0)
			{
				if(str[k]!=s[j])
					break;
				j++;k++;
			}
			if(j>=l)
				n++;
		}
	}
	return n;
}
void replace()
{
	char str1[80];
	int i,j=0,n=0,k,m;
	int l=strlen(s);
	for(i=0;str[i]!=0;i++)
	{
		j=0;
		if(str[i]==s[j])
		{
			k=i;
			while(s[j]!=0)
			{
				if(str[k]!=s[j])
					break;
				j++;k++;
			}
			if(j>=l)
			{
				for(m=0;s[m]!=0;m++)
					str1[n++]=t[m];
			}
			else
			{
				str1[n++]=str[i];
			}
		}
	}
	str1[n]=0;
	strcpy(str,str1);
}

在这里插入图片描述



下一篇文章

内存编址+C语言指针+指针作为函数参数+指向一维数组的指针

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackey_Song_Odd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值