C Programming Basics: Types, Control Flow, Arrays, and Pointers

Hyacehila

概览

从这里我们开始最基础的编程语言的学习 C语言作为较为基础的语言没有复杂的面向对象工作并且具有尽可能简单的语法规则 所以从这里开始是个不错的注意

C语言的起源

C语言是为编程人员开发设计的语言 他基于B语言 目前是非常重要的语言之一 理解C语言能够更加深刻的理解计算机底层运行的逻辑

使用C语言的理由

控制特性

这种设计使得用户可以自上而下的规划整体的结构

高效性

由于更加接近底层 C语言有着汇编语言才具有的精细控制能力 此时我们可以让程序有更高的运行效率

可移植性

不香汇编语言和处理器关系密切 C语言可以非常轻易在各个系统之间进行迁移 只有拥有编译器就可以了

灵活性

非常多的操作系统和编译器用C语言编写 了解C语言可以了解很多语言的底层逻辑

面向编程人员

C语言允许我们访问硬件 操作内存 如此高的自由度是编程人员所需要的

其他程序设计语言的基础

在C语言中我们将会学习编程的基本思维和逻辑 用一个简单语言作为开头是有意义的

缺点

过高的灵活性自然需要承担更大的风险 更高的操作效率意味着非常多细致的修改 缺点和优点其实来自同一个地方

发展方向

C++是C语言的后续发展方向 他为C语言添加了模块化编程所需要的面向对象操作 实际上几乎所有C程序都可以看做一个C++程序 C语言的诸多优点让他经常用于嵌入式编程 微处理器编程 科学编程 操作系统开发 等等 学习C语言对以后很多方向学习都有意义

计算机的基本工作原理

在计算机组成原理中 我们已经对计算机的工作原理给出了重要解释 这里我们只用一句说了无数次的话来收尾 现在是高级语言的时代了 汇编语言已经渐渐的失去了市场

C语言的使用

  • 设定程序目标
  • 设计程序
  • 编写代码
  • 编译
  • 运行
  • 测试与调试
  • 维护和修改 在编译过程中 我们经历了高级语言向汇编语言的编译和汇编语言向机器语言的汇编 最后让我们的高级语言程变为了硬件能够理解的机器语言

编程机制

这里继续详细的介绍一下前面的一些重要过程 在完成设计好的C代码之后(.c结尾的文件) 编译器首先把我们的目标代码经过编译和汇编转化为目标代码文件 然后向他添加启动代码 这是程序和操作系统之间的接口 再添加我们使用的C库中的代码 把三者链接在一起 就形成我们需要的可执行文件 在目前的开发环境下 编译和链接这两个过程编译器都可以直接代劳 我们一般不需要处理

几种开发环境下的编译

Unix

1
2
3
4
5
cc inform.c  //编译指令
ls //列出文件
a.out //发现可执行文件
a.out //执行这个文件
a.o // 出现一个一般会被删除的目标代码文件

Linux

1
gcc inform.c //编译指令

其他的集成开发环境

我们不需要在命令行界面中操作了 此时编译位置取决于IDE的设计

语言标准

ANSI C 或者 C90

比较基本的C语言标准 他有一些基本的指导原则

  • 相信程序设计人员
  • 不要妨碍程序员要做的事情
  • 让语言保持短小
  • 只提供一个方法来完成一个操作
  • 牺牲可移植性来提高效率 这其实就是C语言的一些精神的体现

C99

只是进行了一些小的改变

  • 支持国际化编程
  • 修正和改进他的计算实用性 实际上C99 保持了原本的设计思路 后面会在需要的时候阐述发生的改变 我们会发现C99的改变保持了C语言的特性并且更加好用了

C语言概述

从这里开始简单的介绍C语言的语法规则了 前面的一章仅仅是对我们将要学习的内容的一个概览

简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>  //引入我们需要的包 stdio 是关于输入输出的基本程序包
int main(void) //函数的参数和返回值情况说明
{ // 花括号是常用的分隔符号
int num; // 声明 C语言要求声明变量
num = 1; //赋值
printf("I am a simple");
printf("computer.\n");
printf("My favorite number is %d because it is the first.\n",num);
//输出一些内容 \n 是换行 %d 是格式占位
return 0; // 函数的返回值
// //符号是C语言最常用的注释符号
}

变量的命名

我们应该使用具有意义的变量和函数的名字 并且不使用C语言已经使用的标识符 类比 Python基础:一些变量命名规则

调试与修改

debug是程序设计永远无法避免的问题 代码一旦复杂 人肯定会出现问题

错误的分类

  • 语法错误 编译器可以帮你分析
  • 语义错误 只能靠人自己来

一些DEBUG手法

  • 脑内编译
  • 使用调试器
  • 添加一些输出语句来调试

C语言关键词和语句

不要使用程序设计语言已经有的关键词 这会导致很大的问题 保留的语言标识符 比如那些下划线开头的名字已经已经被使用的函数名 也不可以使用 我们在为函数 和 变量等东西起名字的时候不能忘记他

C90

  1. auto:声明自动变量;
  2. break:跳出当前循环;
  3. case:开关语句分支;
  4. char:声明字符型变量或函数返回值类型;
  5. const:声明只读变量;
  6. continue:结束当前循环,开始下一轮循环;
  7. default:开关语句中的“默认分支”;
  8. do:循环语句的循环体;
  9. double:声明双精度浮点型变量或函数返回值类型;
  10. else:条件语句否定分支(与if连用);
  11. enum:声明枚举类型;
  12. extern:声明变量或函数是在其它文件或本文件的其它位置定义;
  13. float:声明浮点型变量或函数返回值类型;
  14. for:一种循环语句;
  15. goto:无条件跳转语句;
  16. if:条件语句;
  17. int:声明整型变量或函数;
  18. long:声明长整型变量或函数返回值类型
  19. register:声明寄存器变量;
  20. return:子程序返回语句(可以带参数,也可不带参数)
  21. short:声明短整型变量或函数;
  22. signed:声明有符号类型变量或函数;
  23. sizeof:计算数据类型或变量长度(即所占字节数)
  24. static:声明静态变量;
  25. struct:声明结构体类型;
  26. switch:用于开关语句;
  27. typedef:用以给数据类型取别名;
  28. unsigned:声明无符号类型变量或函数;
  29. union:声明共用体类型;
  30. void:声明函数无返回值或无参数,声明无类型指针;
  31. volatile:说明变量在程序执行中可被隐含地改变;
  32. while:循环语句的循环条件;
  33. asm:用于在C语言中嵌入汇编指令;
  34. fortran:为Fortran语言链接而设定的条件性支持类型指令符;

C99

  1. inline:用来定义一个类的内联函数,引入它的主要原因是用它代替C中表达式形式的宏定义;
  2. restrict:只可以用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式。即所有修改该指针指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改,这样做的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码;
  3. _Bool:布尔类型的数据,其值为0或1,主要用来判断条件能否成立的真假;
  4. _Complex:用来表示复数类型;
  5. _Imaginary:用来表示虚数类型;
  6. _Pragma:与#pragma指令相同的功能;

C11

  1. _Alignas:指定某个变量按照其他数据类型对齐;
  2. _Alignof:指定数据类型内存对齐的字节数;
  3. _Atomic:原子类型说明符和限定符;
  4. _Static_assert:声明在编译时有效,它将测试由用户指定且可以转换为布尔值的整数表达式表示的软件断言。如果表达式的计算结果为零(false),编译器将发出用户指定的消息,并且编译因错误而失败;
  5. _Noreturn:表明调用完成后的函数不返回主调函数,目的是告诉用户和编译器,这个特殊的函数不会把控制返回主调程序,告诉用于以免滥用该函数,通知编译器可以优化一些代码。
  6. _Thread_local:它会影响变量的存储周期,被修饰的变量具有线程周期,这些变量在线程开始的时候被生成,在线程结束的时候被销毁。并且每一个线程都拥有一个独立的变量实例。可以和static和extern关键字联合使用,这将影响变量的链接属性;
  7. _Generic:可以简单地将一组具有不同类型却有相同功能的函数抽象为一个统一的接口; 控制语句已经添加在关键词里面了 至于运算符 我们在后面再谈

数据和C

在这一章我们将要介绍更多的关于C语言的基本知识 非常的简单

简单的示例

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
int main(void)
{
float weight; //这里我们给出了新的数据类型 float浮点数可以处理小数了
float value;
scanf("%f",&weight); //scanf 语句可以提供输入的交互 &符号是用来获得地址的
value = 770*weight*14;
printf("%.2f",value);
return 0;
//声明各种类型的变量的时候就是申请了存储空间并且建立这个变量
}

各种数据类型我们在关键字一处已经给出了介绍 C语言关键词和语句 使用这么多中数字类型就是为了在需要的时候节约存储空间 如果赋值的数字超过了类型允许的范围 则会出现问题 关于各种数据类型的二进制存储我们在别的地方会给出详细的解释

其他进位制度的数字

八进制数据的标识符是 %o 十六进制数字对应了 %x 和 %X 如果我们愿意在 说明符中添加 # 就可以实现数字前显示进位制度

字符

打印字符

我们使用 char 作为字符的标识 一般我们只会在C语言中使用ASCII编码 Unicode编码可以显示更多的语言 不过放在C里面意义可能并没有那么大

1
char a = 'x';

这是基本的声明方式

非打印字符

转义序列 含义
\a 警报
\b 退格
\f 换页
\n 换行
\t 水平制表符
\v 垂直制表符
\ 反斜杠
单引号
‘’ 双引号
? 问号
\0oo 八进制
\xhh 十六进制
\r 回车

使用数据类型

错误的转化数据的类型在C语言中是非常宽容的 但是依然要小心 正式这种宽容让C语言更容易出错误 printf 和scanf 是两个特殊的函数 他们对参数的数量没有任何的限制 所有更要仔细注意这里面可能的错误 因为编译器大几率不会给出明确的提示(C语言有对函数参数数量和类型的检验 但是对这种特殊的函数无法发挥作用) 因此 保证格式说明符的个数和后面参数的数目相同并且类型一致是重要的 printf 和 scanf 这种交互语句都基于缓冲区设计 也就是buffer 机制 如果缓冲区没有刷新 printf语句的结构不会显示在界面上 诸如scanf 和 \n 都会让缓冲区刷新

字符串的格式化输入和输出

前面我们介绍了 整数 浮点数 字符 等数据类型 这里我们来介绍C语言中的字符串 也是非常容易得

简单的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<string.h>
#define DENSITY 62.4
int main
{
float weight,volume;
int size,letters;
char name[40]; //字符数组就是字符串的核心表达方式
scanf("%s",name);
printf("%s",name);
printf("AAAAAA");
size = sizeof name;
letters = strlen(name);
printf("%d %d",letters,size);
return 0;
}

这里我们注意到了一点 字符数组就是指针格式被表达了 前面使用C宏进行了 define 引入了几个字符串相关的函数

字符串

我们使用双引号标识他是个字符串 就和单引号标识字符一样 字符数组也就是字符串用 \0 结尾 是一个非打印字符 用来标志结尾 其中%s 是我们给出的用来表示字符串的量 这意味着字符和只有一个字符的字符数组是不一样的 要知道 scanf 对空格 制表符 换行符作为结尾标识 所以他不是读取一句话的好方法 sizeof 给出占用的字节数 其实也可以用来判断长度 只是需要程序设计人员多考虑一点 strlen 这种单独设计的函数显然是个更好的选择

常量和预处理器

前面提到的define宏就是一个进行常量确定的很好方法 后面我们会介绍别的方法 凡是使用# 的预处理指令都不是被编译器执行的 而是要进行预处理 一般预处理干的事情都是非常轻松的 他只会进行一些死板的替换

1
const int A = 100;

这是是定义常量的很好方法 此时如果我们修改A会报错的

输入与输出指令

printf其实已经很熟悉了 核心点就是 格式占位符号和变量名两点 其中的f 意思是格式化 他可以实现变量输入输出格式的修正 这点我们在需要的时候查阅就可以 printf 其实是有返回值的 返回的是打印变量的个数 scanf和他原理是基本一致的 他也会按照指定的格式进行输入 理解这两个指令只需要理解格式化就可以了 本质上就是一种二进制的转换规则 格式化中的格式转换有时候是C语言复杂的核心 我们需要仔细考虑变量格式的变化 尤其是有些地方出现格式的自动转换 尽管我们认为自动转换往往是提升进行 不会损失信息 他也有返回值 如果读取失败 返回0 如果读取到文件结尾 返回 EOF 常量

运算符 表达式 语句

这里我们来介绍一些运算的符号 其实原理是容易理解的 同时的 我们介绍一些前面没提到过的语句

while循环

使用方法如下

1
2
3
while(a<b){
xxxxxxxxxxxx;
}

实现的作用也非常简单 在某个条件下 重复的执行某些语句 正是因为我们需要判断条件是否成立和执行某些语句 运算符才有了意义

基本运算符

赋值

1
a=b;

赋值符号右边的指给到左边的可修改变量 我们要求左值一定可修改 否则不可以赋值

一些其他的基础算符

1
2
3
4
5
6
7
8
9
10
11
12
13
a+b;
a-b;
a=-b;
a*b;
a++;
a/b; // 这是抛弃小数的除法
a%b //取余数
sizeof() //字节为单位返回大小
+=;
-=;
*=
/=;
%=;

运算符是有优先级 我们只需要知道他和数学一样 如果有记不清的地方 不妨加括号来实现 运算符不是炫技 我们要让代码可读才是核心

关系运算符

1
2
3
4
5
6
7
8
9
10
<;
>;
<=;
>=;
==;
!=;
//浮点数不进行相等判断 存在舍入误差
//关系运算符的结果是真值 0是否 其余非0的值是真
//目前已经开发了_Bool类型 不过在结果上和原本一样 只是变量类型变更
//优先级应当靠增加括号解决

表达式和语句

表达式时运算符和操作数的整体 语句是由表达式和一些语法符号构成的 他们是程序构成的关键 复合语句 也就是多个语句被包含在一个花括号内 被称为代码块

循环

在这里完整的介绍循环的形成 以及一些使用方法

再谈while循环

while 是入口条件循环:先判断条件,条件为真时执行循环体,循环体内部通常要更新与条件相关的变量。

1
2
3
while (a < b) {
/* loop body */
}
1
2
3
4
5
6
7
8
9
10
status = scanf("%d",&num);
while(status==1){
xxxxxxx;
status = scanf("%d",&num);
}
//我们借助scanf返回值实现循环体的判断 这也是常用的手段 节约了再设立一次变量的资源
while(scanf("%d",&num)==1){
; //在不需要其他操作的时候 这样写是一个常见的写法
}
//这是另一个常用的简化写法 这里我们使用的一直是一个入口条件循环

计数循环

for循环

对于不同类型的循环 当然我们有更好的写法

1
2
3
4
5
for(count=1;count<=max;count++){
;
}
//这就是更简单的计数循环的写法
//for循环也是自由的 第二部分是任何的关系表达式 第三部分是任何的合法表达式

逗号运算符

1
2
3
4
5
6
7
8
for(count=1,cost=1;count<=max;count++){
;
}
//这里的逗号运算符让原本写一个句子的地方能写两个 起到分隔的作用
x = (y = 3,z);
//起到赋值的作用 用的不多 尽量避免
printf("%d",a);
//分隔符的作用

退出条件循环 do while

1
2
3
4
5
do{
printf("aaaaa");
scanf("%d",&a);
}while(a!=1);
//此时我们先执行一边函数体 再进行判断 如果满足 继续循环 和前面两种循环规则一样

嵌套循环

1
2
3
4
5
6
for(i=0;i<10;i++){
for(k=0;k<10;k++){
;
}
}
//随意嵌套 根据需求

循环辅助手段

1
2
3
4
5
6
7
8
9
10
11
12
for(i=0;i<10;i++){
for(k=0;k<10;k++){
continue;
}
}
for(i=0;i<10;i++){
for(k=0;k<10;k++){
break;
}
}
## continue 结束本次循环 开启该循环中的下一轮循环
## break 循环结束 离开这一层循环结构

数组

数组是用来存储类型相同的一些相关新的的

1
2
3
4
5
6
7
8
9
10
int array[10];
//建立某个类型的数组
a = array[0];
//访问数组中的元素 从0开始是第一个
//C语言不检查数组的越界 这点需要自行注意
//这被称为可变长度数组 VLA 基本上大部分的编译器都已经支持了它 当然除了宇宙第一IDE
for(i=0;i<10;i++){
scanf("%d",%array[i]);
}
//数组和for循环一起搭配是个很好的注意

使用函数的返回值

1
2
3
4
5
6
7
8
9
10
double power(double n,int p){
double pow = 1;
int i;
for(i=1;i<=+p;i++){
pow*=n
}
return pow;
}
//这就是建立有返回函数的基本方式
//使用函数之前进行声明当然是必要的 不过我们在后面会再次提到

分支和跳转

除了分支和跳转的内容 我们这里也会介绍一些IO函数和他们的使用

if语句

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
while(scanf("%f") == 1){
if(tem<FREEZING){
x++;
}
else if(x>0){
x = 0;
}
else{
tem = 0;
}
}
#这就是分支也就是if的最基本写法 嵌套的写法也是自然地
ch = gerchar();
## 这个函数不接受任何参数 他只会从输入设备拿一个字符 并且返回
putchar(ch);
## 和getchar相反 这两个函数只用来处理字符 效率比scanf printf 高得多
while((ch = getchar())!= '\n'){
xxxxxxxx;
}
#这是非常C语言的一个写法 先调用函数 然后把它的结果拿来运算
a && b
a || b
a ! b
#这三个运算符都是C非常常用的逻辑运算符 我们要避免有些不符合语法的数学语言出现在程序里
#switch case 在这里就不介绍了 和if作用没有差别

goto 语句

在这里提示 如果不是必要 不要使用 也就是我们要绝对谨慎的使用 在更高级的语言中 不再存在这个写法 这正是因为他不必要还容易引发错误

1
2
3
4
#这个语句需要搭配使用
part2:xxxxxxxx;
goto part2;
#所有的goto使用场景均可以考虑使用其他的语句来代替 使用判断然后goto是最容易引发错误的方法

字符的输入 输出 确认

  • Title: C Programming Basics: Types, Control Flow, Arrays, and Pointers
  • Author: Hyacehila
  • Created at : 2023-03-23 15:35:43
  • Link: https://hyacehila.github.io//blog/2023/03/23/c-programming-basics-learning-notes/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments