加深代码理解系列…..

Welcome to the wild no heroes and villains.

高级计算机语言和编译器

C语言本身是高级语言,所谓高级编程语言,就是一种独立于机器,面向过程(POP)或是面向对象(OOP)的编程语言、
C语言就是一门面向过程的编程语言。
使用高级语言可以在更抽象的层面表达程序员的想法而不必要考虑CPU在完成任务时具体需要哪些步骤。(叒又是抽象,所以具象是否是表示接近一项事物的本质呢?)
计算机自己并不理解高级语言其高级指令,在这里编译器派上了用场,它将高级语言程序翻译成计算机可以理解的机器语言指令集程序。
在学习C语言之前,有必要了解一下其编程机制。

C的编程机制

C是可移植性语言,所以可以在多种环境中运行使用,包括UNIX,Linux,MS-DOS,Windows,Mac OS
常说C\C++是跨平台语言,其原理是使用平台相关的编译器生成对应平台的可执行文件,如在Windows中.c.cpp文件将被编译成PE格式,而在Linux系统中将会被编译成ELF格式
以上提到的也许只是部分环境,但终究他们有一些共有的方面。

C的代码被储存在文本文件中,该文件被称为“源代码文件 - Source code file
通常由 basename基本名 + .c (extension)扩展名构成

C通过编译和链接将源代码文件转换为可执行文件。编译器将源代码转换成中间代码(通常为机器语言代码),链接器把中间代码与其他代码进行合并(合并代码),最后再与库文件系统的标准启动代码合并生成可执行文件。

Data Types

上溢(overflow) 计算导致的数字过大,超过当前类型所能表达的范围时,就发生了上溢,现代C语言发生这种情况会给上溢的数据赋值一个表示无穷大的特定值。

使用数据类型时,若是将一个值初始化给不同类型的变量时,编译器会自动转换成变量匹配的类型,这可能会导致部分数据丢失。 比如将一个double 值赋给 float 变量,由于C语言只会保留float的前六位精度,其他位的数据将被丢弃。

Refresh Output

关于printf 何时将语句输出到终端,起初printf 会将语句发送到缓冲区 (buffer )【作为中间储存区域】 中,由于C标准的明确规定, printf 在遇到 换行符、 缓存区满时会将内容输出到屏幕,这一过程为刷新缓存区。 如果没有换行符且缓存区也没有满,但是下一条语句为 scanf()语句时,scanf要求用户输入,此时 printf 将被迫将缓存区内容输出

Pointer & Multiple Array - 指针与多维数组

指针是一个值为内存地址的变量(或数据对象)

*(星号) , 即间接运算符(indirection operator), 即间接获取指针指向地址处的数据

其也称为 解引用运算符(dereferencing operator)

e.g.

1
2
char *pt;
// 对pt的描述 , pt的类型: 指向char类型的指针

Declaration of Pointer 指针声明

指针的声明需要指定指针所指向的变量的类型,不同的数据类型占据不同的内存空间

通常在声明的时候有空格, 间接运算符和指针名之间的空格可有可无(一般省略)

1
2
int (*arr)[4];
// arr是一个指向int类型数组的指针, 且每个数组中含有4个元素

REFER TO PAGE 304

为什么要使用圆括号? 因为[]的优先级高于* 如果此处不使用圆括号, 则 int *arr[4] 表示 arr is a array point to pointer

arr[4] 为一个数组, *表示arr数组中含有4个指针,int 表示每个指针指向的类型为int

注意使用数组法声明多维数组时,第二个方括号中的数据不能为空. 否则编译器无法知道.

数组名(第一个元素的地址) + 1 需要加上多少字节才能到第二个元素的地址

1
2
int sum(int ar[][4],int rows); 
// ar+1 编译器将会将当前地址向后移动 16个字节 (int 占4字节 4*4=16)

如果是空的编译器就不知道如何处理 如何得到后续地址

size_t = long unsigned int 64位系统中 size_t = long long unsigned int

声明指向多维数组的指针时 只能省略第一个方括号中的值, 第一对方括号用于表示这是一个指针…

Variable Length Array - 变长数组

C的数组维数 必须是常量 , 不能使用变量来代替

为了创建能处理任意大小的二维数组的函数 C99中添加了变长数组 , 允许使用变量表示数组的维度

‘变’指的是在创建数组的时候可以使用变量表示数组维度 , 而非可以改变数组的大小

注意如果作为VLA变长数组的维度的参数(变量) 必须于VLA之前声明.

函数原型中的VLA参数名可以省略,但必须使用 星号 代替

Function Types - 函数类型

函数类型指的是函数的返回值类型,而非函数的参数类型

且不要搞混函数的声明 declaration定义 definition, 函数声明是告知编译器函数的类型, 定义则是提供实际的代码

⚔ Exercises - Pick ur weapon and face it.

实践是理论的基础,是理论的出发点和归宿点,对理论起决定作用,理论必须与实践紧密结合,理论必须接受实践的检验,为实践服务,随着实践的发展而发展。

咳咳…就是些小练习…..

Fibonacci Sequence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int fibonacci (int);
int main () {
int result = fibonacci(5);
printf("%d",result);
return 0;
}

/* Fibonacci 数列从第三项开始 , 每一项等于前两项之和 , 递归实现 */
int fibonacci (int count) {
if (count == 1 || count == 2 ) {
return 1;
}
else {
return fibonacci(count-1) + fibonacci(count - 2);
}
}

Quick sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include <stdlib.h>
void quickSort(int *,int,int);
int main() {
int arr[] = {19,97,9,17,1,8};
printf("Running ...detecting %d elements in array.\nThe result after executing quickSort is: \n",sizeof(arr)/sizeof(*arr));

quickSort(arr,0,sizeof(arr)/sizeof(*arr));
for (int i = 0 ; i < sizeof(arr)/sizeof(*arr);i++) {
printf("%2d\n",arr[i]);
}
}
/** 设定中间值(亦称 '轴心点') , 一般取数组中第一个值
left 与 right 交替遍历 (递增递减操作 left++ right--) ,
将 < 轴心点的值 置于 左边 , > 轴心点的值 置于 右边 , 相等的情况 不进行移动
遍历实现后 , 最后left与right 重合时的下标就是 基准值k , 此时 轴心点实际值 下标即 k

此时以 k 为基准 , 其左边的值都小于它 , 右边的值都大于它
此时将存在 左右两个子序列 [start(0) - (k-1)] 和 [(k+1) - end] , 重复以上遍历方式即可.

可以使用递归实现 **/
void quickSort(int *arr,int start,int end) {
if(start < end) {
// 定义轴心点 \ 左右索引号
int pivot = *arr,l = start,r = end-1;

/* 遍历寻找 小于 轴心点的值 替换到左边 */
while(l < r) {
while(l < r && arr[r] >= pivot ) {
r--;
}
// 得到符合条件的 r 值, 置于 pivot 左边
if (l < r) {
// 注意 l 前置递增 先用再加 , 依旧从 l[0] 开始
arr[l++] = arr[r];
}
// left 中 寻找第一个大于 轴心点的值 , 若非 , 则递增 l
while(l < r && arr[l] <= pivot ) {
l++;
}
if (l < r) {
arr[r--] = arr[l];
}
}
// l = r
arr[l] = pivot;

quickSort(arr,0,l-1);
quickSort(arr,l+1,r);
}
}