版本比较

标识

  • 该行被添加。
  • 该行被删除。
  • 格式已经改变。

Hello, World!

代码块
titlehelloworld.c
#include <stdio.h>

Day1-C语言概述

计算机与程序

从计算机运行原理的角度分析,计算机其实是非常不“智能”的,因为计算机所有要执行的动作,都要通过程序进行指定,没有了程序,计算机就是一堆电子元件的组合而已,即使通上电也不能执行什么操作。所以,计算机的智能,反映的其实是编程人员的智慧。

程序设计语言的发展

程序设计语言发展到现在,大致经历了三个阶段,分别是机器代码、汇编语言和高级语言三个阶段。计算机发展的初期,人们都是直接基于CPU的指令集编写二进制的机器代码,编写这种代码工作量巨大 ,不易发现和调试程序的错误,加之在一款CPU上编写的代码不能移植到另一款CPU上,所以开发效率不高。后来,人们通过使用一些具有具体意义的符号来代替CPU的指令,比如ADD表示加法指令,JPM表示跳转指令,以此发明了汇编语言。以下是一段Intel 8086 CPU的汇编程序。

代码块
themeConfluence
titlemain.c
start: mov ax,data  
  mov ds,ax
  lea dx,infon
  mov ah,9
  int 21h
  lea dx,buf
  mov ah,10
  int 21h
  mov cl, [buf+1]
  mov ch,0
  lea di,buf+2
  call datacate
  call ifyears
  jc a1
  lea dx,n
  mov ah,9
  int 21h
  jmp exit
a1:    lea dx,y
  mov ah,9
  int 21h
exit:  mov ah,4ch
  int 21h

汇编语言使得人们终于可以直接看懂指令的意义以及整个程序大致完成的工作。但是计算机已经不能识别汇编语言了,因为计算机只能识别由0和1组成的二进制代码。所以汇编语言需要一个叫做*汇编程序*的软件进行翻译,将具体的符号翻译成机器代码,计算机才可以识别。
从机器代码到汇编语言是编程语言的一大进步,汇编语言已经在朝着人的自然语言靠近了,但是汇编语言仍然具有代码逻辑复杂,无法结构化编程,与硬件高度相关,代码无法进行移植的特点,所以汇编语言仍是一种低级语言。
为了克服低级语言的缺点,人们开始创造各种高级语言,高级语言与人的自然语言更加靠近,它很接近人们使用自然语言的习惯,所以使用高级语言写出的程序很容易理解。而且,高级语言对程序的设计进行了高度的抽象与凝练,使得语言可以不依赖于具体的机器型号,程序开始很方便地可以在各种型号的计算机上进行移植。C、C++、JAVA、Python都是高级语言的代表。当然,随着计算机编程语言的发展,C已经被归类到中级语言的行列了。

C语言

C语言诞生于20世纪70年代。1972-1973年间,美国贝尔实验室的D.M.Ritchie在B语言的基础上设计了C语言。C语言设计之初的目的是为了描述与实现当时著名的UNIX操作系统。1973年,Ken Thompson和D.M.Ritchie用C语言重写了90%以上的UNIX系统,随着UNIX系统的广泛使用,C语言也迅速得到推广。
Alt textImage Removed

C语言特点

C语言是一种非常优秀的语言,在那个连操作系统都是用汇编语言编写的年代时,C语言的出现可谓是具有划时代的意义。总结起来,C语言有以下几个特点:
1、C语言是一种结构化的程序设计语言。结构化意味着在设计程序的时候,可以将程序分成不同的模块,分模块编写代码,最后将各个模块“组装”起来完成程序的设计,采用这种方式将程序细化到了人脑可以处理的难度,大大降低了程序设计与调试的难度。
2、C语言既有高级语言那样贴近人自然语言的特点,又不失低级语言的功能。使用C语言可以直接操作计算机的内存和CPU的各种寄存器,这使得使用C语言编生成的目标代码质量高,具有非常高的执行效率。
3、C语言存在规范的标准,使用同一标准写出的C语言代码可以无障碍地在各套CPU架构下进行移植,可移植性好也是C语言强大的体现。
基于以上种种特点,C语言又具有“王者”或“母语”之称,许多著名的系统软件都是用C语言编写的。
Alt textImage Removed

数学基础

所有的科学归根结底都是数学,编程尤其如此,深厚的数学功底在编程中是非常有用的。但由于课程的时间限制,我们没有那么多的时间去深入地学习数学,以下只介绍几个最基本的数学知识,以后的学习过程中,希望同学们能有意识地加强自己的数学水平。

二进制与数制转换

C语言中常用的进制是10进制,2进制,8进制,16进制,以下是它们的转换关系。(C语言中没有直接用2进制表示的数据,但有可以用8进制和16进制进行表示的数据。八进制以数字0开头,十六进制以0x或0X开头,16进制符号A~F不区分大小写。)

转成十进制

二进制:
101 = 1_2^2 + 0_2^1 + 1_2^0
101.1 = 1_2^2 + 0_2^1 + 1_2^0 + 1_2^(-1)
八进制:
074 = 7_8^1 + 4_8^0
十六进制:
0X3FE = 3_16^2 + 15_16^1 + 14_16^0

十进制转二进制

十进制整数转二进制:除二求余,逆序排列。
十进制小整数进制转二进制:乘二取整,顺序排列。
Alt textImage Removed

二进制与十六进制快速转换

111010101010101101
11,1010,1010,1010,1101
6,10,10,10,13
6,A,A,A,D
0x6AAAD
111010101010101101 = 0x6AAAD

数据范围

根据排列组合的知识,x个具有n种状态的元素,它们的组合一共有x的n次方种状态。根据这点,可以用来计算二进制的数据范围,比如,8个二进制位,由于每个二进制位只具有0和1两种状态,所以8个二进制位可以表示2的8次方共256种状态,从00000000到11111111,转换成十进制是0到255,所以8位二进制可以表示的数据范围是0到255。

计算机存储单位转换

单位

容量

bit

位,用于存储一个二进制位

Byte

字节,8bit等于一个Byte

KB

2^10个Byte

MB

2^10个KB

GB

2^10个MB

TB

2^10个GB

第一个C语言程序

代码块
languagec++
themeConfluence
linenumbersfalse
collapsefalse
#include 
int main() 
{
    printf("hello, huiwenworld\n");
}

编译:gcc hello.c –o hello
执行:./hello

第二个C语言程序

这个程序实现的效果是在终端命令行输出一行"hello, world",编译及执行这个程序:

代码块
root@DESKTOP-38B6GK1:~/C# gcc helloworld.c -o helloworld
root@DESKTOP-38B6GK1:~/C# ./helloworld
hello, world

sum = a + b

代码块
titlesum.c
#include <stdio.h>
代码块
languagec++
themeConfluence
linenumbersfalse
collapsefalse
#include 
int main() 
{
    int ia = 1;
    int jb = 2;
    int sum = ia + jb;
    printf("sum is %d\n", sum);
} 

这个程序先定义了两个变量 a 和 b ,值分别为 1 和 2 ,再定义变量sum并将a加b的和赋值给sum,最后输出sum的值,编译及执行这个程序:

代码块
root@DESKTOP-38B6GK1:~/C# gcc sum.c -o sum
root@DESKTOP-38B6GK1:~/C# ./sum
sum is 3

内存图

Alt textImage Removed下面是在32位Linux系统下,一个进程的虚拟内存分布图,当前只需要注意以下几点:

取地址与解地址
  1. 32位系统下,进程的虚拟内存大小为4GB,也就是2^32Byte,范围为0x00000000~0xFFFFFFFF。
code
  1. 将这4GB虚拟内存进行分类,其中低3GB内存归进程使用,称为用户空间,范围是0x0000000~0xBFFFFFFF,最高的1GB归内核使用,称为内核空间,范围是0xC0000000~0xFFFFFFFF。
language
  1. 进程的所有内容都必须存放在这4GB的内存里,比如程序的二进制代码,以及运行过程中需要使用的数据。
  2. 靠近0x00000000地址的一小部分空间进程不能使用,比如典型的NULL地址,如果往这个地址存放数据,则程序会崩溃。
  3. 靠近3GB用户空间顶端的一小段内存称为栈空间,这里用于存放程序的局部变量,比如上面sum.c中定义的变量a, b, sum。

Image Added

取地址与解地址

代码块
titleaddr_ref.c
#include <stdio.h>

c++
themeConfluence
linenumbersfalse
collapsefalse
#include 
int main()
{
    int ia = 1;
    int jb = 2;
    int sum = ia + j;
b;
    
    printf("a is %d\n", a);
    printf("b is %d\n", b);
    printf("sum is %d\n", sum);

    // 取地址
    printf("iaddress of addressa is %p\n", &(*(&i)))a);
    printf("jaddress of addressb is %p\n", &jb);
    printf("address of sum address is %p\n", &sum);

    // 解地址
    printf("ia is %d\n",  *(&ia) );
    printf("jb is %d\n",  *(&jb) );
    printf("sum is %d\n", *(&sum));
    
    // 以此类推...
    printf("address of a is %p\n", &(*(&a)));
}

编译运行效果:todo

这个程序先输出a, b, sum的值,再通过取地址符&获取到它们的内存地址,可以看到它们的地址的确位于靠近3G内存顶部的栈空间。拿到地址后,还可以通过解地址符*再解出这个地址对应的值。取地址和解地址的操作可以相互抵消。

五则运算

代码块
languagetitlecaculate.c++
themeConfluence
linenumbersfalse
collapsefalse
#include <stdio.h>

int main()
{
    int i = 20;
    int j = 3;
    printf("%d\n", i + j);
    printf("%d\n", i - j);
    printf("%d\n", i * j);
    printf("%d\n", i / j); // 整除
    printf("%d\n", i % j); // 取模
}

上面是整数的五则运算,注意整数的除法执行的是整除操作,结果仍是整数,而%则是取模操作,用于求余数,编译运行效果如下:

代码块
root@DESKTOP-38B6GK1:~/C# gcc caculate.c -o caculate
root@DESKTOP-38B6GK1:~/C# ./caculate
23
17
60
6
2

注释

注释是程序中的说明语句,用于解释某些语句的作用,其目的是为了方便阅读程序。注释不参与C语言的编译,任何注释都会在编译过程中都会被忽略。

注释形式

单行注释: 注释有两种形式,分别是单行注释和多行注释,单行注释的形式是//注释内容
多行注释: ,多行注释的形式是/*注释内容*/

注释规范

  1. 每个C文件开头都一段注释,标明文件的名称,作者,编写日期,文件描述与版本修改,下面是一个可以参考的例子:
代码块
languagec++
themeConfluence
linenumbersfalse
collapsefalse
/*=====================================================
*   Copyright (C) 2014 All rights reserved.
*   
*   Filename:calculate.c
*   Author:luqiang
*   Date:2015/01/12
*   Description:this is the description.
*
======================================================*/
  1. 每个函数的定义处都需要写注释,表示该函数的名称,作者,作用,以及该函数的输入输出值。
  2. 程序的关键代码需要用注释说明
  3. 注意注释不要过多,很多时候,可以通过合理的命名让程序具有*自解释性*。

,如下:

代码块
#include <stdio.h>

int main() {
    // 这是单行注释 

    /* 这是多行注释
    可以跨行 */
}


良好的注释可以提高代码的可读性,在编写大型项目的代码时,往往要遵循一定的规范对代码进行注释,以使代码更加容易理解和维护,这里有一些供参考的注释规范。

关键字

关健字又称保留字,是预留给C语言本身使用的名词,在C语言中不可以用作其他用途,比如,不可以用关键字给变量命名,C89规定的关键字共有32个。根据关键字的作用分为四类:

  1. 数据类型关键字(12个)
    int, char, short, long, float, double, signed, struct, unsigned, union, enum, void
  2. 控制语句关键字(12个)
    break, case, continue, default, do, else, for, goto, if, return, switch, while
  3. 存储类型关键字(4个)
    auto, extern, register, static
  4. 其他关键字(4个)
    const, sizeof, typedef, volatile

数据类型

基本数据类型:int char float double
结合数据类型:数组[],指针*
构造数据类型:结构体,联合体,枚举类型

数据类型用于指定一块内存的类型和大小,比如可以用于定义具有不同类型的变量,以存储不同范围的数据,C语言的数据类型可以分为以下四类:

  1. 基本数据类型:类似int,char,float,double这样的不可再划分的数据类型
  2. 结合数据类型:通过结合另一个数据类型构造出的类型,有数组和指针两种类型
  3. 构造数据类型:自定义的类型,包括结构体,联合体,枚举类型
  4. 空类型:void

变量

变量代表一段有名字的内存空间,定义变量的方式是 数据类型 + 名称,比如int i 定义了一个整型变量。名称,比如int i 定义了一个整型变量 i,它的类型是int,一般存储在程序的栈空间,占用大小是4字节。

标识符

用来表示程序中使用的变量名、数组名、函数名和其它由用户自定义的数据类型的名称。定义标识符的时候要注意以下几点:

  1. 只能由英文字母、数字和下划线构成,长度为1~32;只能由英文字母、数字和下划线构成,长度为1~32。
  2. 必须以字母或下划线“_”开头;”开头。
  3. 标识符严格区分大小写字母;标识符严格区分大小写字母。
  4. 不能以C语言的关键字作为标识符;不能以C语言的关键字作为标识符。
  5. 标识符选用应尽量做到“见名知意”,即选用有含义的英文单词或缩写,如sum, name, max等。

运算符、操作数、表达式

运算符是可以对数据进行运算的符号,比如sizeof以及运算符是可以对数据进行运算的符号,比如上面的五则运算,其中的+-*/ %等符号。、%就是运算符。

运算符操作的对象称为操作数,运算符与操作数组合成表达式。表达式结尾没有分号,如果在表达式两边加上一个括号,那么表达式最终可以得到一个值,这是表达式与语句的区别。

程序错误分类

程序错误

指程序出错的情况,程序的错误分为两类,一类是编译时错误,发生在程序编译期间,表示程序语法有问题,比如写错了关键字,或是某些语句有潜在的风险。另一类是运行时错误,这类错误表示程序语法没问题,可以通过编译,但程序逻辑有问题或是达不到预期的结果,比如执行除法运算时除数为0,或是原本要求执行乘法,但编码时写成了加法,导致结果不对。

这里重点说一下编译时错误,对于编译时的错误,可以分类两类,如下:

  • Error
    程序中有语法错误,编译不通过,不能生成可执行文件。
  • Warning
    程序中存在潜在的错误(比如printf未提供与占位符个数相等的参数),编译可以通过,可以生成执行文件。有些警告需要加-Wall选项才会提示Warning。

运行时错误

符合C语言语法规则,但是程序的逻辑设计与目的不符,比如程序要完成求乘法功能,写程序的时候写成了求加法。


目录