C语言基础——认识C语言(四)

2023-03-30 00:00:00 函数 操作 代码 变量 数组
序文
欢迎阅读  霖海岛LHaisland  原创的C语言合集的第四篇文章,本文字数为6696字,全文阅读需要约25分钟。由于笔者能力有限,如有疏漏或错误的地方,希望读者朋友们不吝斧正,谢谢大家。
上篇文章地址:C语言基础——认识C语言(三)

本文我们将介绍函数、数组、操作符、关键字的补充、#define定义宏

八、函数

1.函数的概念:

构成C程序的基本单元是函数,函数包含程序的可执行代码。每个C程序都至少有一个函数,即主函数main()。

维基百科中对函数的定义:子程序。

在计算机科学中,子程序是一个大型程序中的某部分代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
2.函数的分类:
(1)标准函数(库函数):库函数(Library function)是将函数封装入库,供用户使用的一种方式。方法是把一些常用到的函数编好后放到一个文件里,供不同的人进行调用。为了支持可移植性和提高代码的效率,所以C语言的基础库中提供了一系列类似的库函数。
注:使用库函数,必须包含#include对应的头文件。这件事在之前的文章我们已经多次强调了。
(2)自定义函数:自定义函数是程序员为了完成某项任务而编写的函数,目的是为了实现某项的功能或让主程序更简洁。程序员在使用自定义函数之前,必须先声明和定义。
3.函数的语法:
一个函数的定义分为函数头函数体两个部分。函数定义的语法格式如下:
返回值类型 函数名(参数列表)//函数头{函数体(函数实现特定功能的过程);//函数体}

函数头用来标志一个函数代码的开始,这是一个函数的入口,函数头分成返回值类型、函数名称和参数列表3个部分。

函数体位于函数头的下方,由一对大括号括起来,大括号决定了函数体的范围。

下面我们来写一个简单的计算器,实现加减乘除四项功能。
#include<stdio.h>
void menu() //简易菜单 返回值类型为void的函数没有返回值{ printf("************************************\n"); printf("**** 1.Add 2.Sub ****\n"); printf("**** 3.Mul 4.Div ****\n"); printf("**** 0.退出 ****\n"); printf("************************************\n");}
void Add(int x, int y) { printf("%d\n", x + y);//实现加法}
void Sub(int x, int y) { printf("%d\n", x - y);//实现减法}
void Mul(int x, int y) { printf("%d\n", x * y);//实现乘法}
void Div(int x, int y) { printf("%d\n", x / y);//实现除法}
int main()//程序的入口{ int input = ;//进行初始化 int x = ; int y = ; menu();//调用我们简易设计的菜单函数 printf("请选择:>\n"); scanf("%d", &input);//让使用者选择功能 if (input == 1)//选择1,进入加法 { printf("请输入两个操作数,空格隔开\n"); scanf("%d %d", &x, &y); Add(x, y); } if (input == 2)//选择2,进入减法 { printf("请输入两个操作数,空格隔开\n"); scanf("%d %d", &x, &y); Sub(x, y); } if (input == 3)//选择3,进入乘法 { printf("请输入两个操作数,空格隔开\n"); scanf("%d %d", &x, &y); Mul(x, y); } if (input == 4)//选择4,进入除法 { printf("请输入两个操作数,空格隔开\n"); scanf("%d %d", &x, &y); Div(x, y); } if (input == )//选择0,退出 { printf("已退出程序\n"); } return ;}

我们来解释一下这段代码:首先我们给出了五个自定义函数,分别是menu(菜单)、Add(加法)、Sub(减法)、Mul(乘法)、Div(除法)。即使main函数在代码段的第32行,我们的程序依然也是从main函数开始自上而下的运行,首先我们定义了整形(整数形式 int)变量input、x、y,然后调用了我们的菜单函数menu。

所以我们看到的是这个画面,然后我们需要使用者去输入0-4这几个数字来确定使用者想要使用的功能,这里我们的input就是来存放使用者选择的功能的数字,然后使用if判断语句来分别展开我们的功能,如果使用者选择了1,那么input就等于1,也就进入了以下的部分。

这里使用者按照我们printf函数所给的提示 输入两个数字 比如10 20,通过scanf函数来将10 20分别存入进x,y,然后调用Add函数进行运算。
所以我们得到以下结果:

这段代码明显可以优化,因为我们使用的是int(整形),所以无法计算小数,可以更改为float或者double,这个程序被我们使用了一次就结束了,没有使用循环方便我们多次运行,可以使用do...while循环语句搭配switch case判断语句优化。如果读者朋友感兴趣,可以复制代码段自己尝试进行优化(在文章结尾笔者也会附上优化好的代码段)。

函数这个内容也是未来会单独讲解的章节,这里不过多讲解,目的是能让读者朋友了解函数大概的功能。其实读者朋友应该可以发现,加减乘除这么简单的功能为什么要写这么多函数,这不是多此一举吗?

这里笔者来回答一下,如果程序的任务比较简单,全部的代码可以都写在main()函数中。但是,在实际开发中,程序的任务往往比较复杂,如果全部的代码都写在main()函数中,main()函数将非常庞大臃肿,结构复杂,代码重复。自定义函数就是把编程里面需要反复使用、或者具有特定逻辑功能的代码块封装起来(成为一种模块),使得整体编程过程逐步为模块,分而治之。我们可以根据程序的逻辑和任务的分工把代码划分到不同的自定义函数中,main()更关心业务逻辑和处理流程,需要执行具体任务的时候,调用这些自定义的函数就可以了。函数的特点就是简化代码,代码复用。

九、数组(Array)

数组是在程序设计中,为了处理方便,把具有相同类型的若干元素按有序的形式组织起来的一种形式。这些有序排列的同类数据元素的集合称为数组。数组是用于储存多个相同类型数据的集合。

数组说明的一般形式为:类型说明符 数组名[常量表达式],其中类型说明符是任一种基本数据类型或构造数据类型。数组名是用户定义的数组标识符。方括号中的常量表达式表示数据元素的个数,也称为数组的长度。

C语言中给了数组的定义:一组相同类型元素的集合

1.一维数组的创建:
type ArrayName[size];
type 是指数组的元素类型。
size 是一个常量表达式,用来指定数组的大小。
注:数组创建,在C99标准之前,[]中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念。
例如:int arr[5]={1,2,3,4,5};
我们把这样的一组数据的集合称为数组,它所包含的每一个数据叫做数组元素,所包含的数据的个数称为数组长度,例如int arr[5];就定义了一个长度为5的整型数组,名字是arr。

数组中的每个元素都有一个序号,这个序号从0开始,而不是从我们熟悉的1开始,称为下标(Index)。

例如上面的int arr[5]={1,2,3,4,5};个元素1的下标就是0,所以arr[0]=1。

 我们来总结一下常见的数组的定义方式:
char ch[8];——定一个长度为8的字符型数组
float k[7];——定义一个长度为7的浮点型(小数)数组

int arr[5];——定义一个长度为5的整形数组

2.一维数组的初始化:

数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。

数组初始化需要注意的事项:
  • 可以只给部分元素赋值。当{ }中值的个数少于元素个数时,只给前面部分元素赋值。
例如int arr[10]={1,2,3,4,5};则arr[5]~arr[9]自动初始化为0。
对于short、int、long,就是整数 0;
对于char,就是字符 '\0';
对于float、double,就是小数 0.0;
  • 只能给元素逐个赋值,不能给数组整体赋值。
错误写法:arr[10]=1
  • 如给全部元素赋值,那么在定义数组时可以不给出数组长度。
例如:int arr[5]={1,2,3,4,5}; 等价于 int arr[]={1,2,3,4,5};
注:数组是一个整体,它的内存是连续的;也就是说,数组元素之间是相互挨着的,彼此之间没有一点点缝隙。下图演示了int arr[5];在内存中的存储情形:
这里的0、1、2、3、4就是数组的下标,arr[0]、arr[1]、arr[2]、arr[3]、arr[4],就是数组元素。

3.数组的使用

在学习过程中,我们经常会使用循环结构将数据放入数组中(也就是为数组元素逐个赋值),然后再使用循环结构输出(也就是依次读取数组元素的值)。

下面我们进行示例:

简单再说一下个for循环,i初始为0,次进入循环arr[0]=(0+1),也就是arr[0]=1,对应的依次直到arr[9]=10为止,因为循环的条件是i<10。所以在第二个循环依次输出时,结果为上图。

这里我们也可以让用户输入(使用scanf):


十、操作符

这里我们只了解一些常见的操作符,没有说到的操作符将在遇到的时候讲解。

1.算数操作符:

+  -  *  /  %

分别为:加、减、乘、除、求余(取模)

2.位移操作符:

符号:<<   >>

符号说明:
<<    --->   左移运算符,用于将整数左移指定位数
>>    --->   右移运算符,用于将整数右移指定位数
移位规则:

<< (左移运算符):不论算术移位还是逻辑移位,均将左边的数舍弃,右边空缺位补0(左边丢弃,右边补0)

>>(右移运算符):当进行逻辑移位时,右边位丢弃,左边空缺位补0(右边丢弃,左边补0)

当进行算术移位时,右边位丢弃,左边位补原数的符号位(右边丢弃,左边补符号位)

3.赋值操作符:

=   +=   -=   *=   /=   %=   >>=   <<=   &=   |=    ^=
符号说明:
= ---> 进行简单赋值操作
+=、-=、*=、/=、%=、>>=、<<=、&=、|=、^= ---> 复合赋值符,进行复合赋值操作。
这里用+=进行举例
例:a+=3 等价于 a=a+3

4.单目操作符:

5.关系操作符:

6.逻辑操作符
&&   逻辑或(只要有一个表达式为假便为假)
  ||     逻辑与(只要有一个表达式为真便为真)
7.条件操作符
exp1 ? exp2 : exp3
符号说明:
exp1 ? exp2 : exp3    --->   三目表达式,若表达式1(exp1)为真,则返回表达式2(exp2),否则返回表达式3(exp3)。
对于操作符的详细讲解笔者安排在了未来的第五章,我们目前仅了解一些简单的操作符即可。
十一、关键字的补充
在C语言基础——认识C语言(二)中我们已经提到了C语言中的32个关键字,下面我们来补充说明其中3个重要的关键字。
1.关键字tpyedef
typedef 顾名思义是类型定义,这里应该理解为类型重命名

示例:

优点:当跨平台移植程序时,我们只需要修改一下 typedef 的定义即可,而不用对其他源代码做任何修改。
2.关键字static

static在c语言中是“静态”的意思,用于在函数中声明“静态变量”用在每一次调用后,该变量并不在函数返回时立即释放,变量中保存的值,可以保留到下一次调用时继续有效。

在C语言中:static是用来修饰变量和函数的:
(1)修饰局部变量-称为静态局部变量 
现在我们来对比一下两个代码段:

读者朋友可以对比代码1和代码2的效果理解static修饰局部变量的意义。
static 修饰局部变量的时候,其实改变的是局部变量的存储位置,局部静态变量存储位置变为了内存,不再存储在栈里。
注:变量的作用域是不变的,只是生命周期延长了。
(2)修饰全局变量-称为静态全局变量 
在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
这样定义的变量也称为全局静态变量:在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。也就是上述作用二中提到的在模块内(但在函数体外)声明的静态变量。
定义全局静态变量的好处:
它的作用域只在这个文件中,不会被其他文件所访问,修改,是一个本地的局部变量。其他文件中可以使用相同名字的变量,不会发生冲突。
示例:

这里我们可以看到,结果为2022。
当我们用static修饰g_val时,发生如下结果:


这里再次说明:一个全局变量具有外部链接属性的 (就是可以被其他源文件引用)但是被static修饰后外部链接属性就变成了内部链接属性 只能在自己所在的源文件内部使用不能在其他文件内部使用。
(3)修饰函数-称为静态函数
函数的使用方式与全局变量类似,在函数的返回类型前加上static,就是静态函数。其特性如下:
  • 静态函数只能在声明它的文件中可见,其他文件不能引用该函数
  • 不同的文件可以使用相同名字的静态函数,互不影响。

总结:

在修饰变量的时, static修饰的静态局部变量只执行一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。

static修饰全局变量的时,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以。

static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。static修饰的局部变量存放在全局数据区的静态变量区。

当然,有的公司编码规范明确规定只用于本文件的函数要全部使用static关键字声明,这是一个良好的编码风格。

无论如何,要在实际编码时注意自己的编码习惯,尽量体现出语言本身的优雅和编码者的编码素质。

注:
静态局部变量使用static修饰符定义,即使在声明时未赋初值,编译器也会把它初始化为0。
3.关键字register

register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。

但是使用register修饰符有几点限制。

首先,register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。

其次,因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。

由于寄存器的数量有限,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。

在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。

早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。关于register关键字,在《C与指针》这一本书的都151页,作者这样说到:“在许多机器上,register变量所产生的代码将比静态内存中的变量和堆栈中的变量所产生的代码执行速度更快。但是这取决于你的环境,许多当前的编译器比程序员更加懂得怎样合理分配寄存器。”

实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令。

十二、#define定义宏

相信读者朋友们在了解了函数与操作符之后,通过这张图就可以了解到#define定义宏的作用,即使笔者这里没有输出ret的结果,读者朋友们也应该知道ret的结果。

这里需要注意的是宏函数的正确写法是每一个元素加括号,并且在外层也加括号。原因我们在未来的运算符优先级的章节会强调。

在这里我们补充一下上文提到的优化后的计算器:

#include<stdio.h>
void menu() //简易菜单{ printf("************************************\n"); printf("**** 1.Add 2.Sub ****\n"); printf("**** 3.Mul 4.Div ****\n"); printf("**** 0.退出 ****\n"); printf("************************************\n");}
void Add(double x, double y){ printf("%.2f\n", x + y);//实现加法}
void Sub(double x, double y){ printf("%.2f\n", x - y);//实现减法}
void Mul(double x, double y){ printf("%.2f\n", x * y);//实现乘法}
void Div(double x, double y){ printf("%.2f\n", x / y);//实现除法}
int main(){ int input = ; //初始化 double x = 0.0f; double y = 0.0f; do //do while循环 { menu(); printf("请选择:>\n"); scanf("%d", &input); switch (input) { case 1: printf("请输入两个操作数,空格隔开\n"); scanf("%f %f", &x, &y); Add(x, y); break; case 2: printf("请输入两个操作数,空格隔开\n"); scanf("%f %f", &x, &y); Sub(x, y); break; case 3: printf("请输入两个操作数,空格隔开\n"); scanf("%f %f", &x, &y); Mul(x, y); break; case 4: printf("请输入两个操作数,空格隔开\n"); scanf("%f %f", &x, &y); Div(x, y); break; case : printf("退出程序\n"); break; default: //当用户没有选择1-4时进行提示 printf("输入错误\n"); break; } } while (input);//只有当while()时停止循环,对应为退出 return ;}

至此我们已经初步了解函数、数组、操作符、关键字的补充#define定义宏 的大致内容

由于篇幅有限,下一次内容我们将分享指针、结构体,彼时我们的C语言基础——认识C语言章节也将结束,将开启第二章节——分支与循结构
如果您在阅读中发现内容错误或者有好的想法以及建议,欢迎在公众号内给我留言,我将不胜感激!
感谢您能够阅读到这里,我们下次再会!
编辑:   霖海岛LHaisland
审核:NEU-Mangosteen
图文来源:   霖海岛LHaisland

相关文章