计算机系统大作业

1. C语言的语言元素

1.1 程序结构

对于任意一个程序来说,其都是从main函数开始执行。

1.1.1 循序结构

int sum(int a, int b)
{
	int c;
	c = a + b;
	return c;
}

如图中所示的循序结构函数的代码,循序结构又称顺序结构,其按照语句出现的先后顺序依次执行语句。对于循序结构来说,在忽略某个语句调用了其他函数并导致其他结构出现的情况下,其中的语句都会被执行,但每个语句只会执行一次。

1.1.2 分支

C语言中实现分支结构的语句有两种:if语句和switch语句。
分支结构先谷底循序结构来说,在忽略某个语句调用了其他函数并导致其他结构出现的情况下,其中的语句会被执行一次或不执行,并且总存在不执行的分支(if和else分支都存在的情况下)。

if语句
int function_if(int x)
{
	if(x < 0)
		return 0;
	else
		return x;
}

如图所示的ReLU函数为典型的if语句实现。if根据括号后表达式的真假决定是否执行该分支。对于有着多种选择的情况,可以有两种实现方法:
第一种:嵌套if语句,即在if语句的某个分支中再写入一个if语句。这种实现方法可以在多种分支存在共同的前置条件时使用。将前置条件作为外层if的判断,后置条件则放入内层if语句中
第二种:使用else if。以图中代码举例,若x需要选择大于0、小于0、等于0三个分支时,利用if,else if,else实现三个分支,执行时会依次测试其中的分支,选中时直接进入。
if语句在逻辑上有着较为清楚的实现,但当分支过多时,依次对比会导致执行速度下降。

switch语句
void function_switch(char c)
{
	switch(c)
	{
	case 'a':
		cout << 1 << endl;
		break;
	case 'b':
		cout << 2 << endl;
		break;
	default:
		cout << 3 << endl;
		break;
	}
}

如图所示为一个switch语句的实现。switch语句在逻辑上比较清晰,但不能像if语言一样处理复杂分支。switch语句主要面向一个条件有着多个可能实现的分支的情况。其在对比的时候,因底层实现与if语句不同,只需要一次对比即可选中所需分支,在有着大量分支的情况下效率远高于if语句,但switch语句空间占用较大,是典型的空间换时间的策略。

1.1.3 循环

C语言中有三种语句可以实现循环结构:for、while、do-while。
循环结构与上述两种结构最大的不同在于,循环结构内的语句可以执行不止一次,并且存在不执行的情况,即循环0次。下面以计算从start到end的连续自然数之和的程序为例,介绍三种循环结构。

for循环
int function_for(int start, int end)
{
	int sum = 0;

	for(register int i = start; i <= end; i++)
		sum += i;
	
	return sum;
}

for循环的一般形式为:
for(单次表达式;条件表达式;末尾循环体)
{
中间循环体
}
其中,表示式皆可以省略,但分号不可省略,因为“;”可以代表一个空语句,省略了之后语句减少,即为语句格式发生变化,则编译器不能识别而无法进行编译。
for循环小括号里第一个分号前为一个为不参与循环的单次表达式,其可作为某一变量的初始化赋值语句, 用来给循环控制变量赋初值; 也可用来计算其它与for循环无关但先于循环部分处理的一个表达式。
分号之间的条件表达式是一个关系表达式,其为循环的正式开端,当条件表达式成立时执行中间循环体。
执行末尾循环体后将再次进行条件判断,若条件还成立,则继续重复上述循环,当条件不成立时则跳出当下for循环。
for循环在实现访问连续内存空间时较为方便,逻辑清晰。

while循环
int function_while(int start, int end)
{
	int sum = 0;

	while(start <= end)
	{
		sum += start;
		start++;
	}

	return sum;
}

while循环与for循环等价。但语法中只保留用于判断是否退出循环的条件表达式。其余需要在对应位置实现。
while循环在实现例如遍历链表等访问不连续内存空间时使用方便,逻辑清晰

do-while循环
int function_dowhile(int start, int end)
{
	int sum = 0;
	int i = start - 1;

	do
	{
		i++;
		sum += i;
	}while(i < end);

	return sum;
}

do-while循环与上述两种循环的不同点在于:只要循环条件设置的好,上述的两种循环可以不执行循环体内的语句,而do-while循环至少会执行一次循环体内的语句。其他基本与while循环等价。
do-while循环时候实现至少执行一次的循环,适合实现至少需要执行一次的循环。例如:用于迭代的计数器初值为0时计算1到n的累加和。

1.2 变量

1.2.1 全局变量

int integer = 0;
unsigned int uinteger = 0;
float real = 3.14;
int integers[2] = {0, 1};
int *pinteger = &integer;
int &qinteger = integer;

全局变量能被程序中所有的函数以及对象调用。换句话说它的作用域为整个程序。
全局变量被保存在可执行文件的数据段或者.bss段中。
全局变量可以被extern和static修饰。对于一个拥有多个源文件的工程来说,每个文件都可以拥有自己的全局变量,并且对于任意文件都可调用。而被extern修饰的全局变量声明表示该全局变量在其他文件中。被static修饰的全局变量则只能被当前源文件内的函数与对象调用。

1.2.2 局部变量

int function_for(int start, int end)
{
	int sum = 0;

	for(register int i = start; i <= end; i++)
		sum += i;
	
	return sum;
}

以for循环的代码为例,其中出现的所有变量均为局部变量
局部变量的作用域为:定义局部变量时,程序执行到的语句所在的花括号包围的范围。
与全部变量不同,局部变量被保存在被称为栈的结构中进行管理。

1.2.3 寄存器变量

int function_for(int start, int end)
{
	int sum = 0;

	for(register int i = start; i <= end; i++)
		sum += i;
	
	return sum;
}

以for循环的代码为例,其中用于计数的i即为寄存器变量
寄存器变量与上述两种变量不同,其被保存在存取速度最快的寄存器当中。用register修饰局部变量即可定义寄存器变量

1.3 数据类型

1.3.1 常量

#define INTEGER 20
#define REAL 3.14
#define CHARACTER 'c'
#define STRING "string"

常数可分为整型常量(图中INTEGER)、实型常量(图中REAL)、字符型常量(图中CHARACTER)与字符串型常量(STRING)。对于每一种常量,其在底层的实现与其对应的变量相同,但常量的值不能更改,并且存储在文件的只读区域。
整型常量、实型常量与字符型常量直接存储在代码段当中,当被使用时,在取指令阶段即可取出。
字符串型常量略有不同,由于其一般内存占用较大,不能直接写入代码中,因此被存储在只读数据段中,代码段只保留其首地址。

1.3.2 整型数

int integer = 0;
unsigned int uinteger = 0;

整型数分为有符号整型与无符号整型。二者所能表示的数的数量相同,但区间不同。对于一个k位的整型数来说,有符号整型表示-2(k-1)~2(k-1)-1范围内的所有整数,而无符号整型表示02^k-1范围内的所有整数。二者直接存在着互相转换的情况。当二者均在02(k-1)-1这个区间内时,二者的值相等,类型转换并不会带来值的改变;但是当有符号数在-2(k-1)0或无符号数在2^(k-1)2k-1时,类型转换会导致值的变化,其关系可表示为公式:有符号数+2k=无符号数。

1.3.3 浮点数

float real = 3.14;

与整型数可以精确表示整数不同,浮点数只能精确表示部分实数。对于大部分实数来说,浮点数只能近似表示。根据近似的精度不同,浮点数可以分为单精度浮点型与双精度浮点型。二者在组成上一致,只在二进制位数上不同,从而导致了精度的不同。
对于一个浮点数,其二进制代码可分为三个部分:符号位,阶码和尾数。将一个待转换的实数用科学计数法的二进制形式表示,其符号对应着符号位,数量级对应着阶码,精确值的小数部分的前n位对应着尾数,n为尾数的位数。

1.3.4 数组

int integers[2] = {0, 1};

数组即为一定数量的某种数据类型的集合。数组在内存中占用一段连续的存储单元,因此可以快速访问其中的元素。字符串类型的变量在底层实现上为一个以\0结尾的字符型数组。数组名的本质是一个指针,指向数组的起始地址。

1.3.5 指针

int *pinteger = &integer;

指针的本质为一个整数,它表示指针所指向的对象在虚拟内存中的首地址。

1.3.6 引用

int &qinteger = integer;

引用本质上相当于给它表示的对象起了一个别名,二者指向的是内存中的同一个对象,对其中一个的修改也会导致另一个的改变。

1.3.7 结构体

struct st
{
	char name[20];
	unsigned age;
	unsigned id;
} ics_me; 

有了上述的数据类型,已经足够实现C语言所有的功能了。但是,在具体使用时仍然会存在问题。首先,抽象层次不够高,不利于人的理解。其次,无法同时管理不同的数据类型,或者说给不同数据类型之间显式的建立关系。因此,C语言提供了结构体。结构体将一系列数据结构封装为用户自定义的一种数据结构,提高了抽象水平,更有利于人类理解。
结构体占用的空间并不简单的等于其中所有数据结构的内存大小相加,而是大于等于相加之和,这是由于对齐的存在。其对齐要求与结构体中内存占用最大的那个类型的对齐要求相同。
以将图中的结构体的第一个字段改为单一字符后的结构体为例,其空间占用为4+4+4=12字节,而不是2+4+4=10字节。因为对于无符号型来说,其需要满足地址可被4整除的地址要求,因此整个结构体的地址也需满足该要求。带来的结果是:字符型后面的2字节被空在那不被使用。

1.4 函数

函数是指一段可以直接被另一段程序或代码引用的程序或代码,例如程序结构当中所具的函数的例子。对于函数来说,其内部代码本可以写入主程序当中。但是,假如一段程序调用了100次某函数,如果该函数存在错误,我们只需修改函数一次;对于写在主程序中的情况,则需要找到这100次并修改。因此封装成函数有利于代码复用,并且提高了程序的可读性。
函数的通用形式为:
返回类型 名字(形式参数表列)
{
函数体语句
return 表达式;
}
参数传递方式有三种,传值,传地址,传引用。传值的情况下,函数将实参的值复制给形参,函数体对形参的修改并

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值