第一部分、C语言的简介

C语言的编译

C 语言是一种编译型语言,源码都是文本文件,本身无法执行。必须通过编译器,生成二进制的可执行文件,才能执行。编译器将代码从文本翻译成二进制指令的过程,就称为编译阶段,又称为“编译时”(compile time),跟运行阶段(又称为“运行时”)相区分。

目前,最常见的 C 语言编译器是自由软件基金会推出的 GCC 编译器,它可以免费使用。本书也使用这个编译器。Linux 和 Mac 系统可以直接安装 GCC,Windows 系统可以安装 MinGW。但是,也可以不用这么麻烦,网上有在线编译器,能够直接在网页上模拟运行 C 代码,查看结果,下面就是两个这样的工具。

  1. CodingGround: https://tutorialspoint.com/compile_c_online.php
  2. OnlineGDB: https://onlinegdb.com/online_c_compiler
  3. 还有一个国内的额工具:https://www.it1352.com/Onlinetools/details/6

Hello World 示例

C 语言的源代码文件,通常以后缀名.c结尾。下面是一个简单的 C 程序hello.c。它就是一个普通的文本文件,任何文本编译器都能用来写。

#include <stdio.h>

int main(void) {
  printf("Hello World\n");
  return 0;
}

上面这个程序的唯一作用,就是在屏幕上面显示“Hello World”。

如果你已经安装了GCC,可输入以下命令进行编译

$ gcc hello.c

GCC 的-o参数(output 的缩写)可以指定编译产物的文件名。

$ gcc -o hello hello.c 

上面命令的-o hello指定,编译产物的文件名为hello(取代默认的a.out)。编译后就会生成一个名叫hello的可执行文件,相当于为a.out指定了名称。执行该文件,也会得到同样的结果。

GCC 的-std=参数(standard 的缩写)还可以指定按照哪个 C 语言的标准进行编译。

$ gcc -std=c99 hello.c

上面命令指定按照 C99 标准进行编译。 注意,-std后面需要用=连接参数,而不是像上面的-o一样用空格,并且=前后也不能有多余的空格。

window安装gcc编译环境

下载安装编译器:mingw-w64 https://sourceforge.net/projects/mingw-w64/files/ 找到最新版本的 x86_64-posix-seh,如果因为网络原因下载不了,可以直接找压缩包的资源。下完后解压到自己方便放的地方,找到其中bin文件夹,复制其路径,然后添加到系统环境变量中。 验证一下:

win+R-输入cmd-输入gcc -v

安装QT Creator继承开发工具

https://www.qt.io/zh-cn/development-tools

第二部分、基本语法

2.1 语句

C 语言规定,语句必须使用分号结尾,除非有明确规定可以不写分号。

int x = 1;

上面就是一个变量声明语句,声明整数变量x,并且将值设为1。

多个语句可以写在一行。

int x; x = 1;

上面示例是两个语句写在一行。所以,语句之间的换行符并不是必需的,只是为了方便阅读代码。

2.2 语句块

C 语言允许多个语句使用一对大括号{},组成一个块,也称为复合语句(compounded statement)。在语法上,语句块可以视为多个语句组成的一个复合语句。

{
  int x;
  x = 1;
}

上面示例中,大括号形成了一个语句块。

大括号的结尾不需要添加分号。

2.3 注释

C 语言的注释有两种表示方法

/* 单行注释 */

/*
  多行注释
*/

这种注释可以插在行内。

int open(char* s /* file name */, int mode);

上面示例中,/* file name */用来对函数参数进行说明,跟在它后面的代码依然会有效执行。

2.4 printf()

2.4.1、基本用法

printf("格式控制字符串",输出项列表)

printf()的作用是将参数文本输出到屏幕。它名字里面的f代表format(格式化),表示可以定制输出文本的格式。printf()不会在行尾自动添加换行符,所以你需要手动添加一下换行符。

printf("Hello World\n");

上面命令会在屏幕上输出一行文字“Hello World”。

printf()是在标准库的头文件stdio.h定义的。使用这个函数之前,必须在源码文件头部引入这个头文件。

#include <stdio.h>

int main(void) {
  printf("Hello World\n");
}

上面示例中,只有在源码头部加上#include ,才能使用printf()这个函数

2.4.2 占位符

printf()可以在输出文本中指定占位符。所谓“占位符”,就是这个位置可以用其他值代入。

// 输出 There are 3 apples
printf("There are %i apples\n", 3);

上面示例中,There are %i apples\n是输出文本,里面的%i就是占位符,表示这个位置要用其他值来替换。占位符的第一个字符一律为百分号%,第二个字符表示占位符的类型,%i表示这里代入的值必须是一个整数。

printf()的占位符有许多种类

  • %a:浮点数。
  • %c:单个字符。char x =’a’ ; printf(“%c”,x); 输出结果为 ‘a’
  • %d:十进制整数。
  • %i:整数,基本等同于%d。
  • %s:字符串。
  • %%:输出一个百分号。
  • %x:十六进制数

2.4.3、输出格式

printf()可以定制占位符的输出格式。

(1)限定宽度

printf()允许限定占位符的最小宽度。

printf("%5d\n", 123); // 输出为 "  123"

上面示例中,%5d表示这个占位符的宽度至少为5位。如果不满5位,对应的值的前面会添加空格。

(2)总是显示正负号

默认情况下,printf()不对正数显示+号,只对负数显示-号。如果想让正数也输出+号,可以在占位符的%后面加一个+。

printf("%+d\n", 12); // 输出 +12
printf("%+d\n", -12); // 输出 -12

上面示例中,%+d可以确保输出的数值,总是带有正负号。

(3)限定小数位数

输出小数时,有时希望限定小数的位数。举例来说,希望小数点后面只保留两位,占位符可以写成%.2f。

// 输出 Number is 0.50
printf("Number is %.2f\n", 0.5);

上面示例中,如果希望小数点后面输出3位(0.500),占位符就要写成%.3f。

第三部分、变量

3.1、变量名

变量名在 C 语言里面属于标识符(identifier),命名有严格的规范。

只能由字母(包括大写和小写)、数字和下划线(_)组成。 不能以数字开头。 长度不能超过63个字符。 下面是一些无效变量名的例子。

$zj
j**p
2cat
Hot-tab
tax rate
don't

上面示例中,每一行的变量名都是无效的。

注意:C语言对字母大小写敏感

3.2、变量的声明

C 语言的变量,必须先声明后使用。如果一个变量没有声明,就直接使用,会报错。

每个变量都有自己的类型(type)。声明变量时,必须把变量的类型告诉编译器。

int height;

上面代码声明了变量height,并且指定类型为int(整数)。

若不指定类型,则会报错:

height;
 error: c undeclared (first use in this function)

PS : 在C语言中,变量可以直接声明赋值或者先声明再赋值

3.3、变量的作用域

变量量的作用域,在C语言中作用域分为文件作用域和块作用域,这就跟我们js里面的全局作用域及ES6中的块级作用域是一致的。

文件作用域(file scope)指的是,在源码文件顶层声明的变量,从声明的位置到文件结束都有效。

int x = 1;

int main(void) {
  printf("%i\n", x);
}

上面示例中,变量x是在文件顶层声明的,从声明位置开始的整个当前文件都是它的作用域,可以在这个范围的任何地方读取这个变量,比如函数main()内部就可以读取这个变量。

块作用域(block scope)指的是由大括号({})组成的代码块,它形成一个单独的作用域。凡是在块作用域里面声明的变量,只在当前代码块有效,代码块外部不可见。

int a = 12;

if (a == 12) {
  int b = 99;
  printf("%d %d\n", a, b);  // 12 99
}

printf("%d\n", a);  // 12
printf("%d\n", b);  // 出错

上面例子中,变量b是在if代码块里面声明的,所以对于大括号外面的代码,这个变量是不存在的。

第四部分、流程控制语句

大部分语句的语法和作用都和JS一样,所以这里只介绍goto语句

goto 语句用于跳到指定的标签名。这会破坏结构化编程,建议不要轻易使用,这里为了语法的完整,介绍一下它的用法。

char ch;

top: ch = getchar();

if (ch == 'q')
  goto top;

上面示例中,top是一个标签名,可以放在正常语句的前面,相当于为这行语句做了一个标记。程序执行到goto语句,就会跳转到它指定的标签名。

infinite_loop:
  print("Hello, world!\n");
  goto infinite_loop;

上面的代码会产生无限循环。

goto 的一个主要用法是跳出多层循环。

for(...) {
  for (...) {
    while (...) {
      do {
        if (some_error_condition)
          goto bail;    
      } while(...);
    }
  }
}
   
bail:
// ... ...

上面代码有很复杂的嵌套循环,不使用 goto 的话,想要完全跳出所有循环,写起来很麻烦。

goto 的另一个用途是提早结束多重判断。

if (do_something() == ERR)
  goto error;
if (do_something2() == ERR)
  goto error;
if (do_something3() == ERR)
  goto error;
if (do_something4() == ERR)
  goto error;

上面示例有四个判断,只要有一个发现错误,就使用 goto 跳过后面的判断。

注意,goto 只能在同一个函数之中跳转,并不能跳转到其他函数。

第五部分、数据类型(基本类型)

Flower and water

C 语言的每一种数据,都是有类型(type)的,编译器必须知道数据的类型,才能操作数据。所谓“类型”,就是相似的数据所拥有的共同特征,那么一旦知道某个值的数据类型,就能知道该值的特征和操作方式。

基本数据类型有三种:字符(char)、整数(int)和浮点数(float)。复杂的类型都是基于它们构建的。

5.1、字符类型

字符类型指的是单个字符,类型声明使用char关键字。

char c = 'B';

上面示例声明了变量c是字符类型,并将其赋值为字母B。

C 语言规定,字符常量必须放在单引号里面。

在计算机内部,字符类型使用一个字节(8位)存储。C 语言将其当作整数处理,所以字符类型就是宽度为一个字节的整数。每个字符对应一个整数(由 ASCII 码确定),比如B对应整数66。

只要在字符类型的范围之内,整数与字符是可以互换的,都可以赋值给字符类型的变量。

char c = 66;
// 等同于
char c = 'B';

上面示例中,变量c是字符类型,赋给它的值是整数66。这跟赋值为字符B的效果是一样的。

两个字符类型的变量可以进行数学运算。

char a = 'B'; // 等同于 char a = 66;
char b = 'C'; // 等同于 char b = 67;

printf("%d\n", a + b); // 输出 133

上面示例中,字符类型变量a和b相加,视同两个整数相加。占位符%d表示输出十进制整数,因此输出结果为133。

char类型占一个字节,一个中文字符占3个字节(unicode表),所以char不可以存储中文。

char c = '我' ; // 错误写法

5.2、整数类型

5.2.1、简介

整数类型用来表示较大的整数,类型声明使用int关键字。

int a;

上面示例声明了一个整数变量a。

不同计算机的int类型的大小是不一样的。比较常见的是使用4个字节(32位)存储一个int类型的值,但是2个字节(16位)或8个字节(64位)也有可能使用。它们可以表示的整数范围如下。

  • 16位:-32,768 到 32,767。
  • 32位:-2,147,483,648 到 2,147,483,647。
  • 64位:-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。

5.2.2、signed,unsigned

C 语言使用signed关键字,表示一个类型带有正负号,包含负值;使用unsigned关键字,表示该类型不带有正负号,只能表示零和正整数。

对于int类型,默认是带有正负号的,也就是说int等同于signed int。由于这是默认情况,关键字signed一般都省略不写,但是写了也不算错。

signed int a;
// 等同于
int a;

int类型也可以不带正负号,只表示非负整数。这时就必须使用关键字unsigned声明变量。

unsigned int a;

整数变量声明为unsigned的好处是,同样长度的内存能够表示的最大整数值,增大了一倍。比如,16位的signed int最大值为32,767,而unsigned int的最大值增大到了65,535。

unsigned int里面的int可以省略,所以上面的变量声明也可以写成下面这样。

unsigned a;

5.2.3、整数的子类型

如果int类型使用4个或8个字节表示一个整数,对于小整数,这样做很浪费空间。另一方面,某些场合需要更大的整数,8个字节还不够。为了解决这些问题,C 语言在int类型之外,又提供了三个整数的子类型。这样有利于更精细地限定整数变量的范围,也有利于更好地表达代码的意图。

  • short int(简写为short):占用空间不多于int,一般占用2个字节(整数范围为-32768~32767)。
  • long int(简写为long):占用空间不少于int,至少为4个字节。
  • long long int(简写为long long):占用空间多于long,至少为8个字节。
    short int a;
    long int b;
    long long int c;
    

5.2.4、整数类型的极限值

有时候需要查看,当前系统不同整数类型的最大值和最小值,C 语言的头文件limits.h提供了相应的常量,比如SCHAR_MIN代表 signed char 类型的最小值-128,SCHAR_MAX代表 signed char 类型的最大值127。

为了代码的可移植性,需要知道某种整数类型的极限值时,应该尽量使用这些常量。

  • SCHAR_MIN,SCHAR_MAX:signed char 的最小值和最大值。
  • SHRT_MIN,SHRT_MAX:short 的最小值和最大值。
  • INT_MIN,INT_MAX:int 的最小值和最大值。
  • LONG_MIN,LONG_MAX:long 的最小值和最大值。
  • LLONG_MIN,LLONG_MAX:long long 的最小值和最大值。
  • UCHAR_MAX:unsigned char 的最大值。
  • USHRT_MAX:unsigned short 的最大值。
  • UINT_MAX:unsigned int 的最大值。
  • ULONG_MAX:unsigned long 的最大值。
  • ULLONG_MAX:unsigned long long 的最大值。

5.2.5、整数的进制

C 语言的整数默认都是十进制数,如果要表示八进制数和十六进制数,必须使用专门的表示法。

八进制使用0作为前缀,比如017、0377。

int a = 012; // 八进制,相当于十进制的10

十六进制使用0x或0X作为前缀,比如0xf、0X10。

int a = 0x1A2B; // 十六进制,相当于十进制的6699

有些编译器使用0b前缀,表示二进制数,但不是标准。

int x = 0b101010; 注意,不同的进制只是整数的书写方法,不会对整数的实际存储方式产生影响。所有整数都是二进制形式存储,跟书写方式无关。不同进制可以混合使用,比如10 + 015 + 0x20是一个合法的表达式。

printf()的进制相关占位符如下。

  • %d:十进制整数。
  • %o:八进制整数。
  • %x:十六进制整数。
  • %#o:显示前缀0的八进制整数。
  • %#x:显示前缀0x的十六进制整数。
  • %#X:显示前缀0X的十六进制整数。
    int x = 100;
    printf("dec = %d\n", x); // 100
    printf("octal = %o\n", x); // 144
    printf("hex = %x\n", x); // 64
    printf("octal = %#o\n", x); // 0144
    printf("hex = %#x\n", x); // 0x64
    printf("hex = %#X\n", x); // 0X64
    

5.2.6、整数的运算

整数和整数进行运算,得到的结果依然是整数

int a = 10 ;
int b = 3 ;
int result = a / b ;
printf("%d",result); // 3 
// 小数部分会被舍去,不存在类型转换

5.3、浮点数类型

任何有小数点的数值,都会被编译器解释为浮点数。所谓“浮点数”就是使用 m * be 的形式,存储一个数值,m是小数部分,b是基数(通常是2),e是指数部分。这种形式是精度和数值范围的一种结合,可以表示非常大或者非常小的数。

浮点数的类型声明使用float关键字,可以用来声明浮点数变量。

float c = 10.5;

上面示例中,变量c的就是浮点数类型。

float类型占用4个字节(32位),其中8位存放指数的值和符号,剩下24位存放小数的值和符号。float类型至少能够提供(十进制的)6位有效数字,指数部分的范围为(十进制的)-37到37,即数值范围为10-37到1037。

有时候,32位浮点数提供的精度或者数值范围还不够,C 语言又提供了另外两种更大的浮点数类型。

  • double:占用8个字节(64位),至少提供13位有效数字。
  • long double:通常占用16个字节。 注意,由于存在精度限制,浮点数只是一个近似值,它的计算是不精确的,比如 C 语言里面0.1 + 0.2并不等于0.3,而是有一个很小的误差。
    if (0.1 + 0.2 == 0.3) // false
    

double可以想象成我们JS中的number类型,都是64位的双精度数

5.4、布尔类型

C 语言原来并没有为布尔值单独设置一个类型,而是使用整数0表示伪,所有非零值表示真。

int x = 1;
if (x) {
  printf("x is true!\n");
}

上面示例中,变量x等于1,C 语言就认为这个值代表真,从而会执行判断体内部的代码。

C99 标准添加了类型_Bool,表示布尔值。但是,这个类型其实只是整数类型的别名,还是使用0表示伪,1表示真,下面是一个示例。

_Bool isNormal;

isNormal = 1;
if (isNormal)
  printf("Everything is OK.\n");

头文件stdbool.h定义了另一个类型别名bool,并且定义了true代表1、false代表0。只要加载这个头文件,就可以使用这几个关键字。

#include <stdbool.h>

bool flag = false;

上面示例中,加载头文件stdbool.h以后,就可以使用bool定义布尔值类型,以及false和true表示真伪。

第六部分、常量

量表示数据,常量的意思就是固定不能改变的数据

6.1、常量的类型

  • 整型常量
    • 十进制常量。例如123,-12,0。
    • 八进制常量。八进制常量都是以0开头的。例如0123
  • 实型常量
    • 小数形式
  • 字符常量
    • 字符常量都是用单引号’‘包裹的。例如: ‘a’ , ‘b’ , ‘c’
    • 字符常量只能有一个字符。
    • 特殊情况下,可以有两个字符,比如转移字符。’\n’ ,’\t’
  • 字符串常量
    • 双引号包裹的。

第七部分、scanf函数

  • scanf函数用于接收键盘输入的内容,是一个阻塞式函数,程序会停在scanf函数出现的地方,知道接收到数据才会执行后面的代码。
  • scanf函数的调用格式为:
    scanf("格式控制字符串",地址列表)
    例如: scanf("%d", &num)
    

第八部分、运算符

8.1、sizeof运算符

sizeof运算符可以用来计算一个变量或常量、数据类型所占的内存字节数。

sizeof的几种形式

  • sizeof(变量/常量) sizeof(10)
  • sizeof 变量/常量 sizeof 10
  • sizeof(数据类型) sizeof(float)

注意:sizeof是一个运算符,且优先级高于 + *

#include <stdio.h>
// 注意:sizeof是一个运算符,且优先级高于 + * 等算数运算符

int main(void){
    int a = 10;
    double b = 3.14;
    double res = sizeof a + b;
    printf("res = %2f\n", res); // 7.14
}

8.2、逗号运算符

  • 逗号运算符的功能是把多个表达式连接起来组成一个表达式
  • 逗号运算符会重左到右依次取出每个表达式的值,最后整个逗号表达式的值等于最后一个表达式的值。
#include <stdio.h>
int main(void){
    int a = 10, b = 20, c;
    c = (a + b, a + 1);
    printf("%d\n", c);// 11
}

参考文章:

  1. C语言教程
  2. C语言入门看这一篇就够了
  3. CNote