C语言学习重点概要(4)——数组、函数、生成随机数

目录

前言

一、数组

1. 数组

2. 一维数组

        1.创建语法

        2. 初始化

        3.数组下标

        4.使用数组中的元素

        5. 数组中元素的存放

        6. 数组的大小

3. 二维数组

        1. 二维数组和多维数组

        2. 二维数组的创建

        3.二维数组的初始化

        4. 二维数组的下标、元素存放、数组大小的特性与一维数组相同

4. 变长数组

• 更换Clang编译器

二、函数

1.函数

2.库函数

3. 自定义函数

        1. 自定义函数的语法

        2. 自定义函数的使用

        3. 嵌套调用和链式访问

        4.作用域和生命周期

        5. static和extern

 • extern用来声明外部符号

 • static修饰局部变量

• static修饰全局变量

三、生成随机数

1. rand函数

2. srand函数

3.time函数

4. 生成随机数

总结



前言

写完上一篇文章后,作者在构思后认为掌握了前三篇文章的内容后,如果直接做扫雷小游戏,有些细节还是不太好说明,所以这里继续介绍一些必要的知识点。即关于数组、函数、生成随机数的知识点


一、数组

1. 数组

数组是一组相同类型元素的集合,存放在数组内的值被称为数组的元素

• 数组存放的元素个数不能为0

• 数组存放的元素数据类型相同

• 可以分为一维数组和多维数组(2维及以上),最常用的是一维数组和二维数组。

2. 一维数组

        1.创建语法

type arr_name[常量值];
//比如
int age[10];

• type指定数组内存放元素的数据类型,可以是内置类型(如int,float,short,double,bool等等),也可以是自定义类型。

• arr_name是指数组的名字,可以根据定义标示符的规则自己创建。

• [ ]中的常量值表示创建的数组可以容纳多少个元素。不进行初始化时不能省略

• 数组也是有类型的,是type[常量]类型,如图中的例子age数组的类型是int[10]

        2. 初始化

type arr_name[常量]={元素,元素,..... };
//例如
int age1[1]={ 13 };
int age2[]={ 1,2,3,4 };
int age3[4]={1};

上面的语法都是符合规则的,从中我们可以发现下面几个特性。

• 数组的初始化需要使用{ }来存放元素,元素的个数不能超过[ ]内常量的值

• 一维数组的初始化可以省略常量值,这时编译器会根据你初始化时存放元素的个数自动匹配数组的大小

• 初始化时若元素的个数小于数组的容量,那么空位将会进行默认初始化,对于int类型,会用0来填补空位(其他类型可以使用监视来查看)

        3.数组下标

数组中有很多元素,如何精准地选取其中一个进行操作呢?此时需要引入数组下标的概念。下标就是表示这是数组中第几个元素的序号。

C语言规定,下标从0开始,数组有n个元素,就对应有n-1个下标。比如

        4.使用数组中的元素

想对数组中的元素操作只需要写出数组名,再使用下标引用操作符[ ],并写入元素对应的下标即可。比如

代码运行后打印了arr中下标为4的元素,即5。在对下标为4的元素输入了8后,打印出来的结果又变成了8。

        5. 数组中元素的存放

数组中元素在内存中的存放是连续的,并且存放的地址通常是由低到高的。这是一个重要的特性。

这里用代码来验证,不理解没关系,后续文章中作者会介绍,只需知道这个特性即可。

        6. 数组的大小

数组的大小由存放元素的数据类型和个数决定,是 元素类型大小*个数,即所有元素的大小之和

可以用sizeof()得到,由此也实现了求出数组元素个数的方式,即sizeof(arr) / sizeof(arr[0])求得。

这里也顺便推荐下作者常用的一种在循环中使用数组的方式。

即先求得数组元素的个数count,然后让一个变量 i 从0开始,循环条件为 i < count,调整为 i++,这样就可以对数组中所有的元素一次操作。

3. 二维数组

        1. 二维数组和多维数组

一个二维数组由多个一维数组组成。即二维数组的元素是一维数组。更高维的数组同理。

        2. 二维数组的创建

type arr_name[常量值1][常量值2];

//如
int arr[2][3];

• 常量值1表示行,常量值2表示列,例子中是一个有2行3列的二维数组。

• 其他规则与一维数组相同

        3.二维数组的初始化

int age1[2][3]={ {1,2,3} , {4,5,6} };
int age2[2][3]={ 1,2,3,4 };
int age3[][3] ={ 1,2,3,4 };

上面的语法都是符合规则的,从中我们可以发现下面几个特性。

• 第一种被成为完全初始化,再一次验证了二维数组的元素是一维数组

• 从第二种可以发现,同样可以只初始化部分元素,剩余位置会进行默认初始化。元素会先按顺序先填第一行的每一列再填入下一行(下面有图片说明)

• 从第三种中可以发现,在二维数组初始化时,行数可以省略,列数不能省略。这也好理解,上面我们有说,二维数组的元素是一维数组,列数也可以理解为这个一维数组中元素的个数;如果我们不归定一维数组中元素的个数,那究竟要向第一个一维数组中填入多少个元素后,再填入下一个数组呢?显然这是不确定的,编译器也不知道。

        4. 二维数组的下标、元素存放、数组大小的特性与一维数组相同

• 无论是行还是列,下标均从0开始

• 无论跨不跨行,在内存中的存放都连续,且由低到高

• 数组的大小也是每个元素大小的和

4. 变长数组

在C99前,数组的创建时,数组大小只能使用常量,这十分不方便。而在C99中引入列变长数组(简称VLA)的规则,允许我们可以用变量表示数组的大小。比如下面的操作是允许的。

int n = a + b;
int arr[n];

•  变长数组并不是说它的大小可以变化,而是指可以用变量描述它的大小,当它创建后,是不可以改变大小的。

• 变长数组不能初始化,这是因为变长数组的长度是在程序运行时确定的,而一般的数组通常在编译时确定大小。

但我们发现,在VS2022中这样的操作仍然会报错,因为它并不支持使用这个特性。所以我们需要更换编译器。

• 更换Clang编译器

首先要下载Clang编译器,如果在安装时已下载可以跳过本步骤。

先去找到这个应用

下载好后按下面的图片继续操作

二、函数

1.函数

函数(function),又叫子程序。 是为了完成某项特定任务的一小段代码。

• 函数可以大致分为库函数和自定义函数

• 一个好的函数应满足高内聚、低耦合的特性:即专注于单一职责,尽量减少对于其他模块的依赖

2.库函数

C语言的国际标准ANSI C规定了一些常用函数的标准,被称为标准库。各个编译器厂商根据这个标准,给出了一些可以实现标准中功能的函数,被称为库函数

• 使用库函数需要包含对应的头文件。比如用printf()需要先写 #include <stdio.h>

• 不同厂商中,相同的库函数实现逻辑很可能是不同的。因为标准库只给出了返回值,功能等标准,具体的实现并没有给出规定

下面是C语言官网给出的库函数标准,在里面可以了解到各种库函数的标准,如返回值,功能介绍,参数类型等等。

C Standard Library headers - cppreference.com

3. 自定义函数

自定义函数顾名思义,是程序员自己设计的函数。这使得对于重复性高的某些工作,我们可以实现“一劳永逸”的效果。

        1. 自定义函数的语法

ret_type fun_name(形式参数)
{

}

//例如
int ADD(int x, int y)
{
    int c = x + y;
    return c;
}

• ret_type 指返回值的类型,函数也可以没有返回值,这时写void。如果不写,默认为返回int类型的值。

• fun_name是函数名。同样可以遵循标示符的创建规则去创建。

• 形式参数的写法是 数据类型+变量名。不同的形参用 , 隔开。

• 函数是可以没有参数的。但是()不能省略。如int fuc();  ()是函数调用操作符,调用无参数的函数时,()也不能省略

• 形参的名字可以和实际参数等参数的名字重合,这是因为形参是对实际参数的一份临时拷贝,只有在调用函数时才会使用,调用完毕后后面的数据会对它进行覆盖,通常不会对实际参数造成影响。(这一点我们可以用反汇编观察到,后续作者可能会在其他文章中介绍)

• { }括起来的是函数体,即真正实现功能的代码。

• return是用来返回函数的返回值的,可以返回数值或者表达式。对于void类型的函数可以不写,也可以写成return; 

• 当执行到return语句时,函数会获得返回值,并立即停止调用(就像循环中的break;一样,读到后,后面的代码将不会执行)

• return返回的值如果与函数描述的返回值类型不同,将会将其强制转化为符合类型的值

• 非void类型的函数,如果有分支语句,必须保证每种情况下都有return返回值,否则编译会出错

• 非void类型的函数,如果不写return返回值,将返回未知的值

        2. 自定义函数的使用

下面的使用方式都是合规的

函数使用时的语法为Fuc(传入的实际参数);  如图中的ADD(a,b);

• 函数需要先声明再使用,如上图二。函数的定义是一种特殊的声明,如果前文已经有了定义,就不用再声明了,如上图一。函数的定义也可以在其他文件中,使用前先声明即可,库函数实际上就是这样操作的,库函数的声明在头文件中。我们也可以如此操作,下面有图像演示。需要注意的是,自己定义的头文件用 " "括起来,而不是 < >

• 函数的形参和实参必须匹配。个数,顺序,类型都得一致。

        

        3. 嵌套调用和链式访问

函数支持嵌套调用和链式访问。

其中嵌套调用是指函数之间相互调用;链式访问指的是将一个函数的返回值作为另一个函数的参数。

        

        4.作用域和生命周期

作用域:某个名字可以作用的范围。

生命周期:变量的创建(申请内存)到变量的销毁(回收内存)之间的时间段。

• 局部变量的作用域是变量所在的局部范围;生命周期是从创建开始,到出作用域后。

• 全局变量的作用域是整个项目;生命周期是整个程序的生命周期

        5. static和extern

 • extern用来声明外部符号

具有外部链接属性的变量,函数等,通过extern声明可以实现跨源文件使用。如图操作,即可在test.c中使用function.c中的自定义函数ADD()了

 • static修饰局部变量

static表示静止的,当static修饰局部变量时,它的作用域不变,但生命周期会变长,不会随着出作用域而销毁,当程序结束时才会销毁。本质是:被static修饰的局部变量不再在栈区创建,而是在静态区创建。我们可以对比下面两段代码。

可见,当用static修饰时,局部变量a在每次循环时不会再重置。

• static修饰全局变量

当static修饰全局变量时,该变量将只能在本源文件中使用,不能在其他源文件中使用。

这是因为,全局变量默认有外部链接属性,而static修饰后,外部链接属性变成了内部链接属性。即使在其他文件中声明了,也不能使用。

具体对比下面的两种操作。

三、生成随机数

生成随机数是我们会经常用到的一种操作,下面为大家介绍一种方式。

1. rand函数

C语言中提供了一个叫rand的函数,可以用来生成随机数。它包含在头文件<stdlib.h>中。函数原型如下

int rand(void);

rand会返回一个伪随机数(即该随机数是有一个“种子”决定的),随机数的范围是0~RAND_MAX,最大值在不通编译器上可能不同,但大部分是32767。

这里我们来测试一下

发现两次执行的结果居然是一模一样的。这是因为rand的种子默认是1,两次使用并没有改变种子。

所以要想生成真正的随机数,需要改变种子。

2. srand函数

srand函数是用来初始化随机数生成器的,可以改变rand函数生成随机数时使用的种子。

所以在使用rand函数前,通常会先用srand函数。

它同样包含在头文件stdlib.h中。

原型如下

void srand(unsigned int seed);

3.time函数

C语言中有一个叫time的函数,可以获得时间戳。它包含在头文件time.h中。它的原型如下。

time_t time(time_t*timer);

• time函数的返回值类型为time_t,本质上是32位或者64位的整数类型

• time函数的返回值是时间戳,时间戳指的是用当前日历的时间减掉1970年1月1日0时0秒0分的差值,单位是秒。

• time函数的参数timer如果是非NULL指针,函数会将差值放在timer指向的内存中带回去(只需理解这样得到的不是时间戳,后续作者会有对指针讲解的文章)

• 如果timer是NULL,就只返回时间戳

4. 生成随机数

有了上面三种函数,我们就可以用下面的方法生成随机数了。

srand((unsigned int)time(NULL));
rand();

• 用时间戳来获取一个种子,这样每次运行程序时,时间戳都不同,获得的种子就不同。

• 由于time函数返回的值是time_t类型,不满足srand函数的参数类型,所以我们强制转换。

• 这种方式下,如果要多次生成随机数,srand函数也只需使用一次,不要频繁使用。因为计算机执行程序的时间非常短,而时间戳的单位是秒,这就使得多次调用可能并不会改变种子。’

我们再用上面rand中的代码测试一下。

这下两次执行后生成的随机数就不同了。

如果想将随机数的生成限定在一个范围内,我们可以结合前面的知识,使用一些算术操作符。

比如将随机数的生成限定在1~100内


总结

本文为大家介绍了数组、函数和生成随机数的一种方法。学完本篇文章和前面的文章,我们就可以完成很多程序的设计了。包括实现经典的扫雷游戏。考虑到扫雷游戏将会写出大概200行代码,结合作者自己的经验,这对于一个新手来说,是很容易出现各种意想不到的失误的。所以下篇作者将会编辑一个番外篇,简单介绍一下可能会用到的基础调试技巧,然后写扫雷游戏的实现,帮助大家练习几篇文章中学到的知识点。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值