c语言(http://c.biancheng.net/view/1714.html)

2022年05月14日 阅读数:3
这篇文章主要向大家介绍c语言(http://c.biancheng.net/view/1714.html),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

目录html

 

一、编程基础前端

1.1 通俗地理解什么是编程语言java

1.2 C语言到底是一门怎样的语言?linux

1.3 C语言是菜鸟和大神的分水岭程序员

1.4 英语和数学很差,能学编程吗?golang

1.5 进制详解:二进制、八进制和十六进制算法

1.6  二进制和八进制、十六进制的转换数据库

1.7 数据在内存中的存储(二进制形式存储)编程

1.8 载入内存,让程序运行起来后端

1.9 ASCII编码,将英文存储到计算机

二、c语言初探

2.1 编程时请选择正确的输入法,严格区分中英文

2.2  什么是源文件?

2.3   C语言编译和连接详解(通俗易懂,深刻本质)

2.4   C语言编译器(C语言编程软件)彻底攻略(包含全部平台)

2.4.1  桌面操做系统

2.4.2 嵌入式系统

2.5 什么是IDE(集成开发环境)?

2.5 什么是工程/项目?

2.6 哪款C语言编译器(IDE)适合初学者?

2.6.1 Windows 下如何选择 IDE?

2.7  C语言程序的错误和警告

2.8 分析第一个C语言程序

2.9 C语言代码中的空白符

三、变量和数据类型

3.1 大话C语言变量和数据类型

3.1.1 变量(Variable)

3.1.2 数据类型(Data Type)

3.1.3 数据的长度(Length) 

3.2   在屏幕上输出各类类型的数据

3.3  C语言中的整数(short,int,long)

3.4   C语言中的二进制数、八进制数和十六进制数

3.5 C语言中的正负数及其输出

3.6   C语言中的小数(float,double)

3.7   在C语言中使用英文字符

3.8 C语言转义字符

3.9  C语言标识符、关键字、注释、表达式和语句

3.10  C语言加减乘除运算

3.11 C语言数据类型转换(自动类型转换+强制类型转换)

3.11.1 自动类型转换

3.11.2 强制类型转换

3.11.3  类型转换只是临时性的

3.11.4   自动类型转换 VS 强制类型转换

四、C语言输入输出

4.1  C语言数据输出大汇总以及轻量进阶

4.2 C语言scanf:读取从键盘输入的数据(含输入格式汇总表)

五、C语言循环结构和选择结构

5.1 C语言if else语句详解

5.1.1 只使用if语句

5.1.2  多个if else语句

5.2  C语言关系运算符详解

5.3 C语言逻辑运算符详解

5.4   C语言switch case语句详解

5.5   C语言条件运算符详解

5.6  C语言while循环和do while循环详解

5.6.1 while

5.6.2   do-while循环

5.7  C语言for循环(for语句)详解

5.8   C语言break和continue用法详解(跳出循环)

6    c语言数组

6.1  什么是数组?C语言数组的基本概念

6.1.1 数组的概念和定义

6.1.2   数组内存是连续的

6.1.3   数组的初始化

6.2    C语言字符数组和字符串详解

6.2.1  字符串结束标志(划重点)

6.2.2   字符串长度

6.3   C语言字符串处理函数

6.3.1  字符串链接函数 strcat()

6.3.2  字符串复制函数 strcpy()

6.3.3    字符串比较函数 strcmp()

6.4  对C语言数组的总结

6.4.1  对数组的总结

七、 C语言函数详解(包括声明、定义、使用等)

7.1  什么是函数?C语言函数的概念

7.1.1  C语言中的函数和数学中的函数

7.1.2  库函数和自定义函数

7.1.3  参数

7.1.4 返回值

7.2  C语言函数定义(C语言自定义函数)

7.2.1 C语言无参函数的定义

7.2.2  C语言有参函数的定义

7.3  C语言形参和实参的区别(很是详细)

7.3.1  形参和实参的区别和联系

7.4   C语言return的用法详解,C语言函数返回值详解

7.5  C语言函数调用详解(从中发现程序运行的秘密)

7.6   C语言函数声明以及函数原型

7.7  C语言全局变量和局部变量

7.7.1   局部变量

7.7.2    全局变量

7.8  C语言变量的做用域,加深对全局变量和局部变量的理解

7.8.1 在函数内部定义的变量(局部变量)

7.8.2  在全部函数外部定义的变量(全局变量)

7.9  忽略语法细节,从总体上理解函数

八、  C语言预处理命令(宏定义和条件编译)

8.1  C语言预处理命令是什么?

8.2  C语言#include的用法详解(文件包含命令)

8.3  C语言#define的用法,C语言宏定义

8.4 、C语言带参数的宏定义

8.5  C语言带参宏定义和函数的区别

8.6  C语言#if、##ifdef、#ifndef的用法详解,C语言条件编译详解

8.6.1    #if 的用法

8.6.2  #ifdef 的用法

8.6.3 #ifndef 的用法

8.6.4 三者之间的区别

8.7  C语言预处理命令总结

九、C语言指针详解

9.1  C语言指针是什么?

9.2 C语言指针变量的定义和使用(精华)

9.2.1 定义指针变量

9.2.2 经过指针变量取得数据

9.2.3 关于 * 和 & 的谜题

9.2.4 对星号*的总结

十、C语言结构体详解

10.1 C语言结构体详解,C语言struct用法详解

10.1.1 结构体变量

10.1.2  成员的获取和赋值

10.2  C语言结构体数组详解

10.3 C语言结构体指针(指向结构体的指针)详解

10.4  C语言枚举类型(C语言enum用法)详解

10.5  C语言共用体(C语言union用法)详解

10.6  C语言位域(位段)详解

10.7  C语言位运算(按位与运算、或运算、异或运算、左移运算、右移运算)

10.7.1 按位与运算(&)

10.7.2  按位或运算(|)

10.7.3  按位异或运算(^)

10.7.4  取反运算(~)

10.7.5  左移运算(<<)

10.7.6 右移运算(>>)

十一、 C语言重要知识点补充

11.1   C语言typedef的用法详解

11.2  C语言const的用法详解,C语言常量定义详解

11.3  C语言随机数生成教程,C语言rand和srand用法详解


一、编程基础

1.1 通俗地理解什么是编程语言

           咱们能够经过”语言“来控制计算机,让计算机为咱们作事情,这样的语言就叫作编程语言(Programming Language)。

           编程语言有固定的格式和词汇,咱们必须通过学习才会使用,才能控制计算机。

          编程语言有不少种,经常使用的有C语言C++Java、C#、Python、PHP、JavaScript、Go语言、Objective-C、Swift、汇编语言等,每种语言都有本身擅长的方面,例如:

      

     能够将不一样的编程语言比喻成各国语言,为了表达同一个意思,可能使用不一样的语句。例如,表达“世界你好”的意思:

  • 汉语:世界你好;
  • 英语:Hello World
  • 法语:Bonjour tout le monde

    在编程语言中,一样的操做也可能使用不一样的语句。例如,在屏幕上显示“C语言中文网”:

  • C语言:puts("C语言中文网");
  • PHP:echo "C语言中文网";
  • Java:System.out.println("C语言中文网");

             编程语言相似于人类语言,由直观的词汇组成,咱们很容易就能理解它的意思,例如在C语言中,咱们使用 puts 这个词让计算机在屏幕上显示出文字;puts 是 output string(输出字符串)的缩写。

           编程语言是用来控制计算机的一系列指令(Instruction),它有固定的格式和词汇(不一样编程语言的格式和词汇不同),必须遵照,不然就会出错,达不到咱们的目的。

           C语言(C Language)是编程语言的一种,学习C语言,主要是学习它的格式和词汇

          这些具备特定含义的词汇、语句,按照特定的格式组织在一块儿,就构成了源代码(Source Code),也称源码或代码(Code)

          那么,C语言确定规定了源代码中每一个词汇、语句的含义,也规定了它们该如何组织在一块儿,这就是语法(Syntax)。它与咱们学习英语时所说的“语法”相似,都规定了如何将特定的词汇和句子组织成能听懂的语言。

         编写源代码的过程就叫作编程(Program)。从事编程工做的人叫程序员(Programmer)。程序员也很幽默,喜欢自嘲,常常说本身的工做辛苦,地位低,像农民同样,因此称本身是”码农“,就是写代码的农民。也有人自嘲称是”程序猿“。

1.2 C语言到底是一门怎样的语言?

             对于大部分程序员,C语言是学习编程的第一门语言,不多有不了解C的程序员。

            C语言除了能让你了解编程的相关概念,带你走进编程的大门,还能让你明白程序的运行原理,好比,计算机的各个部件是如何交互的,程序在内存中是一种怎样的状态,操做系统和用户程序之间有着怎样的“爱恨情仇”,这些底层知识决定了你的发展高度,也决定了你的职业生涯。

             若是你但愿成为出类拔萃的人才,而不只仅是码农,这么这些知识就是不可逾越的。也只有学习C语言,才能更好地了解它们。有了足够的基础,之后学习其余语言,会举一反三,很快上手,7 天了解一门新语言不是神话。

            C语言概念少,词汇少,包含了基本的编程元素,后来的不少语言(C++Java等)都参考了C语言,说C语言是现代编程语言的开山鼻祖绝不夸张,它改变了编程世界。

       C语言诞生于20世纪70年代,年龄比咱们都要大,咱们将在《C语言的三套标准:C8九、C99和C11》一节中讲解更多关于C语言的历史。

        固然,C语言也不是没有缺点,毕竟是70后老人,有点落后时代,开发效率较低,后来人们又在C语言的基础上增长了面向对象的机制,造成了一门新的语言,称为C++,咱们将在《C语言和C++到底有什么关系》中讲解。

          编程语言的发展大概经历了如下几个阶段:汇编语言 --> 面向过程编程 --> 面向对象编程

  • 汇编语言是编程语言的拓荒年代,它很是底层,直接和计算机硬件打交道,开发效率低,学习成本高;
  • C语言是面向过程的编程语言,已经脱离了计算机硬件,能够设计中等规模的程序了;
  • Java、C++、Python、C#、PHP 等是面向对象的编程语言,它们在面向过程的基础上又增长了不少概念。

          C语言出现的时候,已经度过了编程语言的拓荒年代,具有了现代编程语言的特性,可是这个时候尚未出现“软件危机”,人们没有动力去开发更加高级的语言,因此也没有太复杂的编程思想。

          C语言是计算机产业的核心语言

         也许是机缘巧合,C语言出现后不久,计算机产业开始爆发,计算机硬件愈来愈小型化,愈来愈便宜,逐渐进入政府机构,进入普通家庭,C语言成了编程的主力军,得到了史无前例的成功,操做系统、经常使用软件、硬件驱动、底层组件、核心算法、数据库、小游戏等都使用C语言开发。

          雷军说过,站在风口上,猪都能飞起来;C语言就是那头猪,无论它好很差,反正它飞起来了。

         C语言在计算机产业大爆发阶段被万人膜拜,无疑会成为整个软件产业的基础,拥有核心地位。

         软件行业的不少细分学科都是都是基于C语言的,学习数据结构、算法、操做系统、编译原理等都离不开C语言,因此大学将C语言做为一门公共课程,计算机相关专业的同窗都要学习。

         C语言被誉为“上帝语言”,它不但奠基了软件产业的基础,还创造了不少其它语言,例如:

  • PHP、Python 等都是用C语言开发出来的,虽然平时作项目的时候看不到C语言的影子,可是若是想深刻学习 PHP 和 Python,那就要有C语言基础了。
  • C++ 和 Objective-C 干脆在C语言的基础上直接进行扩展,增长一些新功能后变成了新的语言,因此学习 C++ 和 Objective-C 以前也要先学习C语言。

1.3 C语言是菜鸟和大神的分水岭

          程序是在内存中运行的(咱们将在《载入内存,让程序运行起来》一节中详细说明),一名合格的程序员必须了解内存,学习C语言是了解内存布局的最简单、最直接、最有效的途径,C语言简直是为内存而生的,它比任何一门编程语言都贴近内存。

          所谓内存,就是咱们常说的内存条,就是下图这个玩意,相信你确定见过。

           

           全部的程序都在拼尽全力节省内存,都在竭尽全力提升内存使用效率,计算机的整个发展过程都在围绕内存打转,不断地优化内存布局,以保证能够同时运行多个程序。

           不了解内存,就学不会进程和线程,就没有资格玩中大型项目,没有资格开发底层组件,没有资格架构一个系统,命中注定你就是一个菜鸟,成不了什么气候。

1.4 英语和数学很差,能学编程吗?

             编程语言起源于美国,是由英文构成的,其中包括几十个英文的关键字以及几百个英文的函数,除非须要对文本进行处理,不然通常不会出现中文。可是,它们都是孤立的单词,不构成任何语句,不涉及任何语法。

            几十个关键字很少,用得多了天然会记住,相信你们也不会担忧。下面是C语言中的 32 个关键字:

         

1.5 进制详解:二进制、八进制和十六进制

                咱们平时使用的数字都是由 0~9 共十个数字组成的,例如 一、九、十、29七、952 等,一个数字最多能表示九,若是要表示10、11、二十9、一百等,就须要多个数字组合起来。

              例如表示 5+8 的结果,一个数字不够,只能”进位“,用 13 来表示;这时”进一位“至关于十,”进两位“至关于二十。

              由于逢十进一(满十进一),也由于只有 0~9 共十个数字,因此叫作十进制(Decimalism)。十进制是在人类社会发展过程当中天然造成的,它符合人们的思惟习惯,例如人类有十根手指,也有十根脚趾。

              进制也就是进位制。进行加法运算时逢X进一(满X进一),进行减法运算时借一当X,这就是X进制,这种进制也就包含X个数字,基数为X。十进制有 0~9 共10个数字,基数为10,在加减法运算中,逢十进一,借一当十。

              二进制

          咱们不妨将思惟拓展一下,既然能够用 0~9 共十个数字来表示数值,那么也能够用0、1两个数字来表示数值,这就是二进制(Binary)。例如,数字 0、一、十、1十一、100、1000001 都是有效的二进制。

            在计算机内部,数据都是以二进制的形式存储的,二进制是学习编程必须掌握的基础。本节咱们先讲解二进制的概念,下节讲解数据在内存中的存储,让你们学以至用。

二进制加减法和十进制加减法的思想是相似的:

  • 对于十进制,进行加法运算时逢十进一,进行减法运算时借一当十;
  • 对于二进制,进行加法运算时逢二进一,进行减法运算时借一当二。

下面两张示意图详细演示了二进制加减法的运算过程。

         

              八进制

             除了二进制,C语言还会使用到八进制。

            八进制有 0~7 共8个数字,基数为8,加法运算时逢八进一,减法运算时借一当八。例如,数字 0、一、五、七、1四、73三、6700一、25430 都是有效的八进制。

            下面两张图详细演示了八进制加减法的运算过程。

           

                        十六进制

                        除了二进制和八进制,十六进制也常用,甚至比八进制还要频繁。

                    十六进制中,用A来表示10,B表示11,C表示12,D表示13,E表示14,F表示15,所以有 0~F 共16个数字,基数为16,加法运算时逢16进1,减法运算时借1当16。例如,数字 0、一、六、九、A、D、F、41九、EA3二、80A三、BC00 都是有效的十六进制。

                     注意,十六进制中的字母不区分大小写,ABCDEF 也能够写做 abcdef。

1.6  二进制和八进制、十六进制的转换

          其实,任何进制之间的转换均可以使用上面(http://c.biancheng.net/view/1725.html)讲到的方法,只不过有时比较麻烦,因此通常针对不一样的进制采起不一样的方法。将二进制转换为八进制和十六进制时就有很是简洁的方法,反之亦然。

        1) 二进制整数和八进制整数之间的转换

        二进制整数转换为八进制整数时,每三位二进制数字转换为一位八进制数字,运算的顺序是从低位向高位依次进行,高位不足三位用零补齐。下图演示了如何将二进制整数 1110111100 转换为八进制:

          

          从图中能够看出,二进制整数 1110111100 转换为八进制的结果为 1674。

         八进制整数转换为二进制整数时,思路是相反的,每一位八进制数字转换为三位二进制数字,运算的顺序也是从低位向高位依次进行。下图演示了如何将八进制整数 2743 转换为二进制:

           

           从图中能够看出,八进制整数 2743 转换为二进制的结果为 10111100011。 

           2) 二进制整数和十六进制整数之间的转换

           二进制整数转换为十六进制整数时,每四位二进制数字转换为一位十六进制数字,运算的顺序是从低位向高位依次进行,高位不足四位用零补齐。下图演示了如何将二进制整数 10 1101 0101 1100 转换为十六进制:

           

           从图中能够看出,二进制整数 10 1101 0101 1100 转换为十六进制的结果为 2D5C。

          十六进制整数转换为二进制整数时,思路是相反的,每一位十六进制数字转换为四位二进制数字,运算的顺序也是从低位向高位依次进行。下图演示了如何将十六进制整数 A5D6 转换为二进制:

            

            从图中能够看出,十六进制整数 A5D6 转换为二进制的结果为 1010 0101 1101 0110。

            在C语言编程中,二进制、八进制、十六进制之间几乎不会涉及小数的转换,因此这里咱们只讲整数的转换,你们学以至用足以。另外,八进制和十六进制之间也极少直接转换,这里咱们也再也不讲解了。

1.7 数据在内存中的存储(二进制形式存储)

              计算机要处理的信息是多种多样的,如数字、文字、符号、图形、音频、视频等,这些信息在人们的眼里是不一样的。但对于计算机来讲,它们在内存中都是同样的,都是以二进制的形式来表示。

              内存条是一个很是精密的部件,包含了上亿个电子元器件,它们很小,达到了纳米级别。这些元器件,实际上就是电路;电路的电压会变化,要么是 0V,要么是 5V,只有这两种电压。5V 是通电,用1来表示,0V 是断电,用0来表示。因此,一个元器件有2种状态,0 或者 1。

            咱们经过电路来控制这些元器件的通断电,会获得不少0、1的组合。例如,8个元器件有 28=256 种不一样的组合,16个元器件有 216=65536 种不一样的组合。虽然一个元器件只能表示2个数值,可是多个结合起来就能够表示不少数值了。

            咱们能够给每一种组合赋予特定的含义,例如,能够分别用 1101000、00011100、111111十一、00000000、0101010一、10101010 来表示 C、语、言、中、文、网 这几个字,那么结合起来 1101000 00011100 11111111 00000000 01010101 10101010 就表示”C语言中文网“。

           通常状况下咱们不一个一个的使用元器件,而是将8个元器件看作一个单位,即便表示很小的数,例如 1,也须要8个,也就是 00000001。

             1个元器件称为1比特(Bit)或1位,8个元器件称为1字节(Byte),那么16个元器件就是2Byte,32个就是4Byte,以此类推:

  • 8×1024个元器件就是1024Byte,简写为1KB;
  • 8×1024×1024个元器件就是1024KB,简写为1MB;
  • 8×1024×1024×1024个元器件就是1024MB,简写为1GB。

              如今,你知道1GB的内存有多少个元器件了吧。咱们一般所说的文件大小是多少 KB、多少 MB,就是这个意思。单位换算以下:

  • 1Byte = 8 Bit
  • 1KB = 1024Byte = 210Byte
  • 1MB = 1024KB = 220Byte
  • 1GB = 1024MB = 230Byte
  • 1TB = 1024GB = 240Byte
  • 1PB = 1024TB = 250Byte
  • 1EB = 1024PB = 260Byte

               咱们平时使用计算机时,一般只会设计到 KB、MB、GB、TB 这几个单位,PB 和 EB 这两个高级单位通常在大数据处理过程当中才会用到。

             你看,在内存中没有abc这样的字符,也没有gif、jpg这样的图片,只有0和1两个数字,计算机也只认识0和1。因此,计算机使用二进制,而不是咱们熟悉的十进制,写入内存中的数据,都会被转换成0和1的组合。

1.8 载入内存,让程序运行起来

           若是你的电脑上安装了QQ,你但愿和好友聊天,会双击QQ图标,打开QQ软件,输入帐号和密码,而后登陆就能够了。

           那么,QQ是怎么运行起来的呢?

           首先,有一点你要明确,你安装的QQ软件是保存在硬盘中的。

          双击QQ图标,操做系统就会知道你要运行这个软件,它会在硬盘中找到你安装的QQ软件,将数据(安装的软件本质上就是不少数据的集合)复制到内存。对!就是复制到内存!QQ不是在硬盘中运行的,而是在内存中运行的。

           为何呢?由于内存的读写速度比硬盘快不少。

          对于读写速度,内存 > 固态硬盘 > 机械硬盘。机械硬盘是靠电机带动盘片转动来读写数据的,而内存条经过电路来读写数据,电机的转速确定没有电的传输速度(几乎是光速)快。虽然固态硬盘也是经过电路来读写数据,可是由于与内存的控制方式不同,速度也不及内存。

          因此,无论是运行QQ仍是编辑Word文档,都是先将硬盘上的数据复制到内存,才能让CPU来处理,这个过程就叫做载入内存(Load into Memory)。完成这个过程须要一个特殊的程序(软件),这个程序就叫作加载器(Loader)。

        CPU直接与内存打交道,它会读取内存中的数据进行处理,并将结果保存到内存。若是须要保存到硬盘,才会将内存中的数据复制到硬盘。

        例如,打开Word文档,输入一些文字,虽然咱们看到的不同了,可是硬盘中的文档没有改变,新增的文字暂时保存到了内存,Ctrl+S才会保存到硬盘。由于内存断电后会丢失数据,因此若是你编辑完Word文档忘记保存就关机了,那么你将永远没法找回这些内容。

        虚拟内存

            若是咱们运行的程序较多,占用的空间就会超过内存(内存条)容量。例如计算机的内存容量为2G,却运行着10个程序,这10个程序共占用3G的空间,也就意味着须要从硬盘复制 3G 的数据到内存,这显然是不可能的。

            操做系统(Operating System,简称 OS)为咱们解决了这个问题:当程序运行须要的空间大于内存容量时,会将内存中暂时不用的数据再写回硬盘;须要这些数据时再从硬盘中读取,并将另一部分不用的数据写入硬盘。这样,硬盘中就会有一部分空间用来存放内存中暂时不用的数据。这一部分空间就叫作虚拟内存(Virtual Memory)。

           3G - 2G = 1G,上面的状况须要在硬盘上分配 1G 的虚拟内存。

          硬盘的读写速度比内存慢不少,反复交换数据会消耗不少时间,因此若是你的内存过小,会严重影响计算机的运行速度,甚至会出现”卡死“现象,即便CPU强劲,也不会有大的改观。若是经济条件容许,建议将内存升级为 4G,在 win七、win八、win10 下运行软件就会比较流畅了。

          总结:CPU直接从内存中读取数据,处理完成后将结果再写入内存。

           

1.9 ASCII编码,将英文存储到计算机

               计算机是以二进制的形式来存储数据的,它只认识 0 和 1 两个数字,咱们在屏幕上看到的文字,在存储以前都被转换成了二进制(0和1序列),在显示时也要根据二进制找到对应的字符。

               可想而知,特定的文字必然对应着固定的二进制,不然在转换时将发生混乱。那么,怎样将文字与二进制对应起来呢?这就须要有一套规范,计算机公司和软件开发者都必须遵照,这样的一套规范就称为字符集(Character Set)或者字符编码(Character Encoding)。

                严格来讲,字符集和字符编码不是一个概念,字符集定义了文字和二进制的对应关系,为字符分配了惟一的编号,而字符编码规定了如何将文字的编号存储到计算机中

                字符集为每一个字符分配一个惟一的编号,相似于学生的学号,经过编号就可以找到对应的字符。

               能够将字符集理解成一个很大的表格,它列出了全部字符和二进制的对应关系,计算机显示文字或者存储文字,就是一个查表的过程。在计算机逐步发展的过程当中,前后出现了几十种甚至上百种字符集,有些还在使用,有些已经淹没在了历史的长河中,本节咱们要讲解的是一种专门针对英文的字符集——ASCII编码。   

                拉丁字母(开胃小菜)

                在正式介绍 ASCII 编码以前,咱们先来讲说什么是拉丁字母。估计也有很多读者和我同样,对于拉丁字母、英文字母和汉语拼音中的字母的关系不是很清楚。
               拉丁字母也叫罗马字母,它源自希腊字母,是当今世界上使用最广的字母系统。基本的拉丁字母就是咱们常常见到的 ABCD 等26个英文字母。

                拉丁字母、阿拉伯字母、斯拉夫字母(西里尔字母)被称为世界三大字母体系。

                拉丁字母原先是欧洲人使用的,后来因为欧洲殖民主义,致使这套字母体系在全球范围内开始流行,美洲、非洲、澳洲、亚洲都没有逃过西方文化的影响。中国也是,咱们如今使用的拼音其实就是拉丁字母,是彻彻底底的舶来品。

                后来,不少国家对 26 个基本的拉丁字母进行了扩展,以适应本地的语言文化。最多见的扩展方式就是加上变音符号,例如汉语拼音中的ü,就是在u的基础上加上两个小点演化而来;再如,áà就是在a的上面标上音调。

               总起来讲:

  • 基本拉丁字母就是 26 个英文字母;
  • 扩展拉丁字母就是在基本的 26 个英文字母的基础上添加变音符号、横线、斜线等演化而来,每一个国家都不同。

                ASCII 编码

               计算机是美国人发明的,他们首先要考虑的问题是,如何将二进制和英文字母(也就是拉丁文)对应起来。

               当时,各个厂家或者公司都有本身的作法,编码规则并不统一,这给不一样计算机之间的数据交换带来不小的麻烦。可是相对来讲,可以获得广泛承认的有 IBM 发明的 EBCDIC 和此处要谈的 ASCII。
               咱们先说 ASCII。ASCII 是“American Standard Code for Information Interchange”的缩写,翻译过来是“美国信息交换标准代码”。看这个名字就知道,这套编码是美国人给本身设计的,他们并无考虑欧洲那些扩展的拉丁字母,也没有考虑韩语和日语,我大中华几万个汉字更是不可能被重视。
               但这也无可厚非,美国人本身发明的计算机,固然要先解决本身的问题

                ASCII 的标准版本于 1967 年第一次发布,最后一次更新则是在 1986 年,迄今为止共收录了 128 个字符,包含了基本的拉丁字母(英文字母)、阿拉伯数字(也就是 1234567890)、标点符号(,.!等)、特殊符号(@#$%^&等)以及一些具备控制功能的字符(每每不会显示出来)。
               在 ASCII 编码中,大写字母、小写字母和阿拉伯数字都是连续分布的(见下表),这给程序设计带来了很大的方便。例如要判断一个字符是不是大写字母,就能够判断该字符的 ASCII 编码值是否在 65~90 的范围内。
               EBCDIC 编码正好相反,它的英文字母不是连续排列的,中间出现了屡次断续,给编程带来了一些困难。如今连 IBM 本身也不使用 EBCDIC 了,转而使用更加优秀的 ASCII。
                ASCII 编码已经成了计算机的通用标准,没有人再使用 EBCDIC 编码了,它已经消失在历史的长河中了。

                ASCII 编码一览表

                标准 ASCII 编码共收录了 128 个字符,其中包含了 33 个控制字符(具备某些特殊功能可是没法显示的字符)和 95 个可显示字符。

           

            

            

            

            

             

            

            

             上表列出的是标准的 ASCII 编码,它共收录了 128 个字符,用一个字节中较低的 7 个比特位(Bit)足以表示(27 = 128),因此还会空闲下一个比特位,它就被浪费了。

              稍微有点C语言基本功的读者可能认为C语言使用的就是 ASCII 编码,字符在存储时会转换成对应的 ASCII 码值,在读取时也是根据 ASCII 码找到对应的字符。这句话是错误的,严格来讲,你可能被大学老师和C语言教材给误导了。

              C语言有时候使用 ASCII 编码,有时候却不是,而是使用后面两节中即将讲到的 GBK 编码和 Unicode 字符集

二、c语言初探

2.1 编程时请选择正确的输入法,严格区分中英文

            计算机起源于美国,C语言C++Java、JavaScript 等不少流行的编程语言都是美国人发明的,因此在编写代码的时候必须使用英文半角输入法,尤为是标点符号,初学者必定要引发注意。

            全角和半角输入法的区别

           全角和半角的区别主要在于除汉字之外的其它字符,好比标点符号、英文字母、阿拉伯数字等,全角字符和半角字符所占用的位置的大小不一样。
           在计算机屏幕上,一个汉字要占两个英文字符的位置,人们把一个英文字符所占的位置称为“半角”,相对地把一个汉字所占的位置称为“全角”。
           标点符号、英文字母、阿拉伯数字等这些字符不一样于汉字,在半角状态它们被做为英文字符处理,而在全角状态做为中文字符处理,请看下面的例子。

            另外最重要的一点是:“相同”字符在全角和半角状态下对应的编码值(例如 Unicode 编码、GBK 编码等)不同,因此它们是不一样的字符。

2.2  什么是源文件?

            在开发软件的过程当中,咱们须要将编写好的代码(Code)保存到一个文件中,这样代码才不会丢失,才可以被编译器找到,才能最终变成可执行文件。这种用来保存代码的文件就叫作源文件(Source File)。

            每种编程语言的源文件都有特定的后缀,以方便被编译器识别,被程序员理解。源文件后缀大都根据编程语言自己的名字来命名,例如:

  • C语言源文件的后缀是.c
  • C++语言(C Plus Plus)源文件的后缀是.cpp
  • Java 源文件的后缀是.java
  • Python 源文件的后缀是.py
  • JavaScript 源文件后置是.js

             源文件其实就是纯文本文件,它的内部并无特殊格式,能证实这一结论的典型例子是:在 Windows 下用记事本程序新建一个文本文档,并命名为demo.txt,输入一段C语言代码并保存,而后将该文件强制重命名为demo.c(后缀从.txt变成了.c),发现编译器依然可以正确识别其中的C语言代码,并顺利生成可执行文件。

           源文件的后缀仅仅是为了代表该文件中保存的是某种语言的代码(例如.c文件中保存的是C语言代码),这样程序员更加容易区分,编译器也更加容易识别,它并不会致使该文件的内部格式发生改变。

2.3   C语言编译和连接详解(通俗易懂,深刻本质)

          咱们平时所说的程序,是指双击后就能够直接运行的程序,这样的程序被称为可执行程序(Executable Program)。在 Windows 下,可执行程序的后缀有.exe.com(其中.exe比较常见);在类 UNIX 系统(Linux、Mac OS 等)下,可执行程序没有特定的后缀,系统根据文件的头部信息来判断是不是可执行程序。
          可执行程序的内部是一系列计算机指令和数据的集合,它们都是二进制形式的,CPU 能够直接识别,毫无障碍;可是对于程序员,它们很是晦涩,难以记忆和使用。
           例如,在屏幕上输出“VIP会员”,C语言的写法为:puts("VIP会员");

           

           你感觉一下,直接使用二进制是否是想撞墙,是否是受到一吨重的伤害?

           直接使用二进制指令编程对程序员来讲简直是噩梦,尤为是当程序比较大的时候,不但编写麻烦,须要频繁查询指令手册,并且除错会异常苦恼,要直接面对一堆二进制数据,让人眼花缭乱。另外,用二进制指令编程步骤繁琐,要考虑各类边界状况和底层问题,开发效率十分低下。
          这就倒逼程序员开发出了编程语言,提升本身的生产力,例如汇编、C语言、C++Java、Python、Go语言等,都是在逐步提升开发效率。至此,编程终于再也不是只有极客能作的事情了,不了解计算机的读者通过必定的训练也能够编写出有模有样的程序。

          编译(Compile)

          C语言代码由固定的词汇按照固定的格式组织起来,简单直观,程序员容易识别和理解,可是对于CPU,C语言代码就是天书,根本不认识,CPU只认识几百个二进制形式的指令。这就须要一个工具,将C语言代码转换成CPU可以识别的二进制指令,也就是将代码加工成 .exe 程序的格式;这个工具是一个特殊的软件,叫作编译器(Compiler)
         编译器可以识别代码中的词汇、句子以及各类特定的格式,并将他们转换成计算机可以识别的二进制形式,这个过程称为编译(Compile)
         编译也能够理解为“翻译”,相似于将中文翻译成英文、将英文翻译成象形文字,它是一个复杂的过程,大体包括词法分析、语法分析、语义分析、性能优化、生成可执行文件五个步骤,期间涉及到复杂的算法和硬件架构。

          C语言的编译器有不少种,不一样的平台下有不一样的编译器,例如:

  • Windows 下经常使用的是微软开发的 Visual C++,它被集成在 Visual Studio 中,通常不单独使用;
  • Linux 下经常使用的是 GUN 组织开发的 GCC,不少 Linux 发行版都自带 GCC;
  • Mac 下经常使用的是 LLVM/Clang,它被集成在 Xcode 中(Xcode 之前集成的是 GCC,后来因为 GCC 的不配合才改成 LLVM/Clang,LLVM/Clang 的性能比 GCC 更增强大)。

          你的代码语法正确与否,编译器说了才算,咱们学习C语言,从某种意义上说就是学习如何使用编译器。编译器能够 100% 保证你的代码从语法上讲是正确的,由于哪怕有一点小小的错误,编译也不能经过,编译器会告诉你哪里错了,便于你的更改。

          连接(Link)

        C语言代码通过编译之后,并无生成最终的可执行文件(.exe 文件),而是生成了一种叫作目标文件(Object File)的中间文件(或者说临时文件)。目标文件也是二进制形式的,它和可执行文件的格式是同样的。对于 Visual C++,目标文件的后缀是.obj;对于 GCC,目标文件的后缀是.o
        目标文件通过连接(Link)之后才能变成可执行文件。既然目标文件和可执行文件的格式是同样的,为何还要再连接一次呢,直接做为可执行文件不行吗?
        不行的!由于编译只是将咱们本身写的代码变成了二进制形式,它还须要和系统组件(好比标准库、动态连接库等)结合起来,这些组件都是程序运行所必须的。
        连接(Link)其实就是一个“打包”的过程,它将全部二进制形式的目标文件和系统组件组合成一个可执行文件。完成连接的过程也须要一个特殊的软件,叫作连接器(Linker)。
        随着咱们学习的深刻,咱们编写的代码愈来愈多,最终须要将它们分散到多个源文件中,编译器每次只能编译一个源文件,生成一个目标文件,这个时候,连接器除了将目标文件和系统组件组合起来,还须要将编译器生成的多个目标文件组合起来。
       再次强调,编译是针对一个源文件的,有多少个源文件就须要编译多少次,就会生成多少个目标文件。

       总结

      无论咱们编写的代码有多么简单,都必须通过「编译 --> 连接」的过程才能生成可执行文件:

  • 编译就是将咱们编写的源代码“翻译”成计算机能够识别的二进制格式,它们以目标文件的形式存在;
  • 连接就是一个“打包”的过程,它将全部的目标文件以及系统组件组合成一个可执行文件。

      若是不是特别强调,通常状况下咱们所说的“编译器”实际上也包括了连接器

2.4   C语言编译器(C语言编程软件)彻底攻略(包含全部平台)

         因为C语言的历史比较久,并且早期没有规范,整个计算机产业也都处于拓荒的年代,因此就涌现了不少款C语言编译器,它们各有特色,适用于不一样的平台,本节就来给你们科普一下。
         咱们分两部分介绍C语言的编译器,分别是桌面操做系统和嵌入式操做系统。

2.4.1  桌面操做系统

         对于当前主流桌面操做系统而言,可以使用 Visual C++GCC 以及 LLVM Clang 这三大编译器。

         Visual C++(简称 MSVC)是由微软开发的,只能用于 Windows 操做系统;GCC 和 LLVM Clang 除了可用于 Windows 操做系统以外,主要用于 Unix/Linux 操做系统。

          像如今不少版本的 Linux 都默认使用 GCC 做为C语言编译器,而像 FreeBSD、macOS 等系统默认使用 LLVM Clang 编译器。因为当前 LLVM 项目主要在 Apple 的主推下发展的,因此在 macOS中,Clang 编译器又被称为 Apple LLVM 编译器。

          MSVC 编译器主要用于 Windows 操做系统平台下的应用程序开发,它不开源。用户能够使用 Visual Studio Community 版原本无偿使用它,可是若是要把经过 Visual Studio Community 工具生成出来的应用进行商用,那么就得好好阅读一下微软的许可证和说明书了。

          而使用 GCC 与 Clang 编译器构建出来的应用通常没有任何限制,程序员能够将应用程序随意发布和进行商用。       

          MSVC 编译器对 C99 标准的支持就十分有限,加之它压根不支持任何 C11 标准,因此本教程中设计 C11 的代码例子不会针对 MSVC 进行描述。所幸的是,Visual Studio Community 2017 加入了对 Clang 编译器的支持,官方称之为——Clang with Microsoft CodeGen,当前版本基于的是 Clang 3.8。

          也就是说,应用于 Visual Studio 集成开发环境中的 Clang 编译器前端可支持 Clang 编译器的全部语法特性,然后端生成的代码则与 MSVC 效果同样,包括像 long 整数类型在 64 位编译模式下长度仍然为 4 个字节,因此各位使用的时候也须要注意。
         为了方便描述,本教程后面涉及 Visual Studio 集成开发环境下的 Clang 编译器简称为 VS-Clang 编译器。

2.4.2 嵌入式系统

       而在嵌入式系统方面,可用的C语言编译器就很是丰富了,好比:

  • 用于 Keil 公司 51 系列单片机的 Keil C51 编译器;
  • 当前大红大紫的 Arduino 板搭载的开发套件,可用针对 AVR 微控制器的 AVR GCC 编译器
  • ARM 本身出的 ADS(ARM Development Suite)、RVDS(RealView Development Suite)和当前最新的 DS-5 Studio;
  • DSP 设计商 TI(Texas Instruments)的 CCS(Code Composer Studio);
  • DSP 设计商 ADI(Analog Devices,Inc.)的 Visual DSP++ 编译器,等等。

           一般,用于嵌入式系统开发的编译工具链都没有免费版本,并且通常须要经过国内代理进行购买。因此,这对于我的开发者或者嵌入式系统爱好者而言是一道不低的门槛。
          不过 Arduino 的开发套件是可免费下载使用的,而且用它作开发板链接调试也十分简单。Arduino 所采用的C编译器是基于 GCC 的。
           还有像树莓派(Raspberry Pi)这种迷你电脑能够直接使用 GCC 和 Clang 编译器。此外,还有像 nVidia 公司推出的 Jetson TK 系列开发板也可直接使用 GCC 和 Clang 编译器。树莓派与 Jetson TK 都默认安装了 Linux 操做系统。
           在嵌入式领域,通常比较低端的单片机,好比 8 位的 MCU 所对应的C编译器可能只支持 C90 标准,有些甚至连 C90 标准的不少特性都不支持。由于它们一方面内存小,ROM 的容量也小;另外一方面,自己处理器机能就十分有限,有些甚至没法支持函数指针,由于处理器自己不包含经过寄存器作间接过程调用的指令。
           而像 32 位处理器或 DSP,通常都至少能支持 C99 标准,它们自己的性能也十分强大。而像 ARM 出的 RVDS 编译器甚至可用 GNU 语法扩展。
          下图展现了上述C语言编译器的分类。

        

2.5 什么是IDE(集成开发环境)?

        实际开发中,除了编译器是必须的工具,咱们每每还须要不少其余辅助软件,例如:

  • 编辑器:用来编写代码,而且给代码着色,以方便阅读;
  • 代码提示器:输入部分代码,便可提示所有代码,加速代码的编写过程;
  • 调试器:观察程序的每个运行步骤,发现程序的逻辑错误;
  • 项目管理工具:对程序涉及到的全部资源进行管理,包括源文件、图片、视频、第三方库等;
  • 漂亮的界面:各类按钮、面板、菜单、窗口等控件整齐排布,操做更方便。

           这些工具一般被打包在一块儿,统一发布和安装,例如 Visual Studio、Dev C++、Xcode、Visual C++ 6.0、C-Free、Code::Blocks 等,它们统称为集成开发环境(IDE,Integrated Development Environment)。 
          集成开发环境就是一系列开发工具的组合套装。这就比如台式机,一个台式机的核心部件是主机,有了主机就能独立工做了,可是咱们在购买台式机时,每每还要附带上显示器、键盘、鼠标、U盘、摄像头等外围设备,由于只有主机太不方便了,必须有外设才能玩的爽。
          集成开发环境也是这个道理,只有编译器不方便,因此还要增长其余的辅助工具。在实际开发中,我通常也是使用集成开发环境,而不是单独地使用编译器。

       通俗的称呼:有时候为了称呼方便,或者初学者没有严格区分概念,也会将C语言集成开发环境称做“C语言编译器”或者“C语言编程软件”。这里你们不要认为是一种错误,就把它当作“乡间俗语”吧。

2.5 什么是工程/项目?

         一个真正的程序(也能够说软件)每每包含多项功能,每一项功能都须要几十行甚至几千行、几万行的代码来实现,若是咱们将这些代码都放到一个源文件中,那将会让人崩溃,不但源文件打开速度极慢,代码的编写和维护也将变得很是困难。

        在实际开发中,程序员都是将这些代码分门别类地放到多个源文件中。除了这些成千上万行的代码,一个程序每每还要包含图片、视频、音频、控件、库(也能够说框架)等其它资源,它们也都是一个一个地文件。

       为了有效地管理这些种类繁杂、数目众多的文件,咱们有理由把它们都放到一个目录(文件夹)下,而且这个目录下只存放与当前程序有关的资源。实际上 IDE 也是这么作的,它会为每个程序都建立一个专门的目录,将用到的全部文件都集中到这个目录下,并对它们进行便捷的管理,好比重命名、删除文件、编辑文件等。

         这个为当前程序配备的专用文件夹,在 IDE 中也有一个专门的称呼,叫作“Project”,翻译过来就是“工程”或者“项目”。在 Visual C++ 6.0 下,这叫作一个“工程”,而在 Visual Studio 下,这又叫作一个“项目”,它们只是单词“Project”的不一样翻译而已,其实是一个概念。

         工程类型/项目类型

        “程序”是一个比较宽泛的称呼,它能够细分为不少种类,例如:

  • 有的程序不带界面,彻底是“黑屏”的,只能输入一些字符或者命令,称为控制台程序(Console Application),例如 Windows 下的 cmd.exe,Linux 或 Mac OS 下的终端(Terminal)。
  • 有的程序带界面,看起来很漂亮,可以使用鼠标点击,称为GUI程序(Graphical User Interface Program),例如 QQ、迅雷、Chrome 等。
  • 有的程序不单独出现,而是做为其它程序的一个组成部分,普通用户很难接触到它们,例如静态库、动态库等。

           不一样的程序对应不一样的工程类型(项目类型),使用 IDE 时必须选择正确的工程类型才能建立出咱们想要的程序。换句话说,IDE 包含了多种工程类型,不一样的工程类型会建立出不一样的程序。
           不一样的工程类型本质上是对 IDE 中各个参数的不一样设置;咱们也能够建立一个空白的工程类型,而后本身去设置各类参数(不过通常不这样作)。
          控制台程序对应的工程类型为“Win32控制台程序(Win32 Console Application)”,GUI 程序对应的工程类型为“Win32程序(Win32 Application)”。
          控制台程序是 DOS 时代的产物了,它没有复杂的功能,没有漂亮的界面,只能看到一些文字,虽然枯燥无趣,也不实用,可是它很是简单,不受界面的干扰,因此适合入门,我强烈建议初学者从控制台程序学起。等你们对编程掌握的比较熟练了,能编写上百行的代码了,再慢慢过渡到 GUI 程序。

2.6 哪款C语言编译器(IDE)适合初学者?

             这里咱们把“编译器”和“IDE(集成开发环境)”当作一个概念,再也不加以区分。

             C语言的集成开发环境有不少种,尤为是 Windows 下,多如牛毛,初学者每每不知道该如何选择,本节咱们就针对 Windows、Linux 和 Mac OS 三大平台进行讲解。

2.6.1 Windows 下如何选择 IDE?

           Windows 下的 IDE 多如牛毛,常见的有如下几种。

          1) Visual Studio

          Windows 下首先推荐你们使用微软开发的 Visual Studio(简称 VS),它是 Windows 下的标准 IDE,实际开发中你们也都在使用。
          为了适应最新的 Windows 操做系统,微软每隔一段时间(通常是一两年)就会对 VS 进行升级。VS 的不一样版本以发布年份命名,例如 VS2010 是微软于 2010 年发布的,VS2017 是微软于 2017 年发布的。
         不过 VS 有点庞大,安装包有 2~3G,下载不方便,并且会安装不少暂时用不到的工具,安装时间在半个小时左右。
         对于初学者,我推荐使用 VS2015。最好不用使用 VS2017,有点坑初学者。

         2) Dev C++

         若是你讨厌 VS 的复杂性,那么能够使用 Dev C++。Dev C++ 是一款免费开源的 C/C++ IDE,内嵌 GCC 编译器Linux GCC 编译器的 Windows 移植版),是 NOI、NOIP 等比赛的指定工具。Dev C++ 的优势是体积小(只有几十兆)、安装卸载方便、学习成本低,缺点是调试功能弱。

         NOI 是National Olympiad in Informatics的缩写,译为“全国青少年信息学奥林匹克竞赛”;NOIP 是National Olympiad in informatics in Provinces的缩写,译为“全国青少年信息学奥林匹克联赛”。NOI、NOIP 都是奥林匹克竞赛的一种,参加者多为高中生,获奖者将被保送到名牌大学或者获得高考加分资格。 

         3) Visual C++ 6.0

         Visual C++ 6.0(简称VC 6.0)是微软开发的一款经典的 IDE,不少高校都以 VC 6.0 为教学工具来说解C和C++。但VC 6.0是1998年的产品,很古老了,在 Win七、Win八、Win10 下会有各类各样的兼容性问题,甚至根本不能运行,因此不推荐使用。

         VC 6.0 早就该扔进垃圾桶了,但是依然有不少大学把它做为教学工具,而且选用的教材也以 VC 6.0 为基础来说解C语言和 C++,可见教学体制的极端落后,课程体系的更新远远跟不上技术的进步。

          4) Code::Blocks

         Code::Blocks 是一款开源、跨平台、免费的 C/C++ IDE,它和 Dev C++ 很是相似,小巧灵活,易于安装和卸载,不过它的界面要比 Dev C++ 复杂一些,不如 Dev C++ 来得清爽。

         5) Turbo C

         Turbo C 是一款古老的、DOS 年代的C语言开发工具,程序员只能使用键盘来操做 Turbo C,不能使用鼠标,因此很是不方便。可是 Turbo C 集成了一套图形库,能够在控制台程序中画图,看起来很是炫酷,因此至今仍然有人在使用。

         6) C-Free

          C-Free 是一款国产的 Windows 下的C/C++ IDE,最新版本是 5.0,整个软件才 14M,很是轻巧,安装也简单,界面也比 Dev C++ 漂亮。C-Free 的缺点也是调试功能弱。惋惜的是,C-Free 已经多年不更新了,组件都老了,只能在 XP、Win7 下运行,在 Win八、Win10 下可能会存在兼容性问题。

2.7  C语言程序的错误和警告

           一段C语言代码,在编译、连接和运行的各个阶段均可能会出现问题。编译器只能检查编译和连接阶段出现的问题,而可执行程序已经脱离了编译器,运行阶段出现问题编译器是无能为力的。
          若是咱们编写的代码正确,运行时会提示没有错误(Error)和警告(Warning)

           对于 VS、GCC、Xcode 等,若是代码没有错误,它们只会显示“生成成功”,不会显示“0个错误,0个警告”,只有代码真的出错了,它们才会显示具体的错误信息。

            错误(Error)表示程序不正确,不能正常编译、连接或运行,必需要纠正。
            警告(Warning)表示可能会发生错误(实际上未发生)或者代码不规范,可是程序可以正常运行,有的警告能够忽略,有的要引发注意。
           错误和警告可能发生在编译、连接、运行的任什么时候候。

           能够看出,C-Free 的错误提示信息比较少,不方便程序员纠错。VC 和 VS 的错误信息相似,只是中英文的差异。下图分析了 VC 6.0 的错误信息:

            

2.8 分析第一个C语言程序

            函数的概念
           在C语言中,有的语句使用时不能带括号,有的语句必须带括号。带括号的称为函数(Function)。

           C语言提供了不少功能,例如输入输出、得到日期时间、文件操做等,咱们只须要一句简单的代码就可以使用。可是这些功能的底层都比较复杂,一般是软件和硬件的结合,还要要考虑不少细节和边界,若是将这些功能都交给程序员去完成,那将极大增长程序员的学习成本,下降编程效率。

          好在C语言的开发者们为咱们作了一件好事,他们已经编写了大量代码,将常见的基本功能都完成了,咱们能够直接拿来使用。可是如今问题来了,那么多代码,如何从中找到本身须要的呢?一股脑将全部代码都拿来显然是很是不明智的。

          这些代码,早已被分门别类地放在了不一样的文件中,而且每一段代码都有惟一的名字。使用代码时,只要在对应的名字后面加上( )就能够。这样的一段代码可以独立地完成某个功能,一次编写完成后能够重复使用,被称为函数(Function)。读者能够认为,函数就是一段能够重复使用的代码。

          函数的一个明显特征就是使用时必须带括号( ),必要的话,括号中还能够包含待处理的数据。例如puts("C语言中文网")就使用了一段具备输出功能的代码,这段代码的名字是 puts,"C语言中文网" 是要交给这段代码处理的数据。使用函数在编程中有专业的称呼,叫作函数调用(Function Call)。

          自定义函数和main函数

        C语言自带的函数称为库函数(Library Function)。库(Library)是编程中的一个基本概念,能够简单地认为它是一些列函数的集合,在磁盘上每每是一个文件夹。C语言自带的库称为标准库(Standard Library),其余公司或我的开发的库称为第三方库(Third-Party Library)。

         除了库函数,咱们还能够编写本身的函数,拓展程序的功能。本身编写的函数称为自定义函数。自定义函数和库函数在编写和使用方式上彻底相同,只是由不一样的机构来编写。

          C语言规定,一个程序必须有且只有一个 main 函数。main 被称为主函数,是程序的入口函数,程序运行时从 main 函数开始,直到 main 函数结束(遇到 return 或者执行到函数末尾时,函数才结束)。

          也就是说,没有 main 函数程序将不知道从哪里开始执行,运行时会报错

         头文件的概念

          #include <stdio.h>是什么意思呢?
         C语言开发者们编写了不少经常使用函数,并分门别类的放在了不一样的文件,这些文件就称为头文件(header file)。每一个头文件中都包含了若干个功能相似的函数,调用某个函数时,要引入对应的头文件,不然编译器找不到函数。

         实际上,头文件每每只包含函数的说明,也就是告诉咱们函数怎么用,而函数自己保存在其余文件中,在连接时才会找到。对于初学者,能够暂时理解为头文件中包含了若干函数。

        引入头文件使用#include命令,并将文件名放在< >中,#include 和 < > 之间能够有空格,也能够没有。

        头文件以.h为后缀,而C语言代码文件以.c为后缀,它们都是文本文件,没有本质上的区别,#include 命令的做用也仅仅是将头文件中的文本复制到当前文件,而后和当前文件一块儿编译。你能够尝试将头文件中的内容复制到当前文件,那样也能够不引入头文件。

     .h中代码的语法规则和.c中是同样的,你也能够#include <xxx.c>,这是彻底正确的。不过实际开发中没有人会这样作,这样看起来很是不专业,也不规范。

          较早的C语言标准库包含了15个头文件,stdio.h 和 stdlib.h 是最经常使用的两个:

  • stdio 是 standard input output 的缩写,stdio.h 被称为“标准输入输出文件”,包含的函数大都和输入输出有关,puts() 就是其中之一。
  • stdlib 是 standard library 的缩写,stdlib.h 被称为“标准库文件”,包含的函数比较杂乱,可能是一些通用工具型函数,system() 就是其中之一

2.9 C语言代码中的空白符

            空格、制表符、换行符等统称为空白符(space character),它们只用来占位,并无实际的内容,也显示不出具体的字符。

            制表符分为水平制表符和垂直制表符,它们的 ASCII 编码值分别是 9 和 11。

  • 垂直制表符在现代计算机中基本再也不使用了,也无法在键盘上直接输入,它已经被换行符取代了。
  • 水平制表符至关于四个空格,对于大部分编辑器,按下 Tab 键默认就是输入一个水平制表符;若是你进行了个性化设置,按下 Tab 键也可能会输入四个或者两个空格。

              对于编译器,有的空白符会被忽略,有的却不能。

              须要注意的是,由" "包围起来的字符串中的空白符不会被忽略,它们会被原样输出到控制台上;而且字符串中间不能换行,不然会产生编译错误

三、变量和数据类型

3.1 大话C语言变量和数据类型

3.1.1 变量(Variable)

         现实生活中咱们会找一个小箱子来存放物品,一来显得不那么凌乱,二来方便之后找到。计算机也是这个道理,咱们须要先在内存中找一块区域,规定用它来存放整数,并起一个好记的名字,方便之后查找。这块区域就是“小箱子”,咱们能够把整数放进去了。
        C语言中这样在内存中找一块区域:    int a;

       int又是一个新单词,它是 Integer 的简写,意思是整数。a 是咱们给这块区域起的名字;固然也能够叫其余名字,例如 abc、mn123 等。int a;这个语句的意思是:在内存中找一块区域,命名为 a,用它来存放整数。

       注意 int 和 a 之间是有空格的,它们是两个词。也注意最后的分号,int a表达了完整的意思,是一个语句,要用分号来结束。

       不过int a;仅仅是在内存中找了一块能够保存整数的区域,那么如何将 12三、100、999 这样的数字放进去呢?C语言中这样向内存中放整数:a=123;

    =是一个新符号,它在数学中叫“等于号”,例如 1+2=3,但在C语言中,这个过程叫作赋值(Assign)。赋值是指把数据放到内存的过程。a 中的整数不是一成不变的,只要咱们须要,随时能够更改。更改的方式就是再次赋值

       由于 a 的值能够改变,因此咱们给它起了一个形象的名字,叫作变量(Variable)。

3.1.2 数据类型(Data Type)

           数据是放在内存中的,变量是给这块内存起的名字,有了变量就能够找到并使用这份数据。但问题是,该如何使用呢?

           咱们知道,诸如数字、文字、符号、图形、音频、视频等数据都是以二进制形式存储在内存中的,它们并无本质上的区别,那么,00010000 该理解为数字16呢,仍是图像中某个像素的颜色呢,仍是要发出某个声音呢?若是没有特别指明,咱们并不知道。

           也就是说,内存中的数据有多种解释方式,使用以前必需要肯定;上面的int a;就代表,这份数据是整数,不能理解为像素、声音等。int 有一个专业的称呼,叫作数据类型(Data Type)。

           顾名思义,数据类型用来讲明数据的类型,肯定了数据的解释方式,让计算机和程序员不会产生歧义。在C语言中,有多种数据类型,例如:

          

          这些是最基本的数据类型,是C语言自带的,若是咱们须要,还能够经过它们组成更加复杂的数据类型

3.1.3 数据的长度(Length) 

            为了让程序的书写更加简洁,C语言支持多个变量的连续定义,int a, b, c;

           所谓数据长度(Length),是指数据占用多少个字节。占用的字节越多,能存储的数据就越多,对于数字来讲,值就会更大,反之能存储的数据就有限。

         多个数据在内存中是连续存储的,彼此之间没有明显的界限,若是不明确指明数据的长度,计算机就不知道什么时候存取结束。例如咱们保存了一个整数 1000,它占用4个字节的内存,而读取时却认为它占用3个字节或5个字节,这显然是不正确的。

         因此,在定义变量时还要指明数据的长度。而这偏偏是数据类型的另一个做用。数据类型除了指明数据的解释方式,还指明了数据的长度。由于在C语言中,每一种数据类型所占用的字节数都是固定的,知道了数据类型,也就知道了数据的长度。

        在32位环境中,各类数据类型的长度通常以下:

        

        最后的总结

        数据是放在内存中的,在内存中存取数据要明确三件事情:数据存储在哪里、数据的长度以及数据的处理方式。
        变量名不只仅是为数据起了一个好记的名字,还告诉咱们数据存储在哪里,使用数据时,只要提供变量名便可;而数据类型则指明了数据的长度和处理方式。因此诸如int n;char c;float money;这样的形式就肯定了数据在内存中的全部要素。

       C语言提供的多种数据类型让程序更加灵活和高效,同时也增长了学习成本。而有些编程语言,例如PHP、JavaScript等,在定义变量时不须要指明数据类型,编译器会根据赋值状况自动推演出数据类型,更加智能。

       除了C语言,Java、C++、C#等在定义变量时也必须指明数据类型,这样的编程语言称为强类型语言。而PHP、JavaScript等在定义变量时没必要指明数据类型,编译系统会自动推演,这样的编程语言称为弱类型语言。
      强类型语言一旦肯定了数据类型,就不能再赋给其余类型的数据,除非对数据类型进行转换。弱类型语言没有这种限制,一个变量,能够先赋给一个整数,而后再赋给一个字符串。

     最后须要说明的是:数据类型只在定义变量时指明,并且必须指明;使用变量时无需再指明,由于此时的数据类型已经肯定了。

3.2   在屏幕上输出各类类型的数据

          printf 比 puts 更增强大,不只能够输出字符串,还能够输出整数、小数、单个字符等,而且输出格式也能够本身定义,例如:

  • 以十进制、八进制、十六进制形式输出;
  • 要求输出的数字占 n 个字符的位置;
  • 控制小数的位数。

        printf 是 print format 的缩写,意思是“格式化打印”。这里所谓的“打印”就是在屏幕上显示内容,与“输出”的含义相同,因此咱们通常称 printf 是用来格式化输出的。

        看%d,d 是 decimal 的缩写,意思是十进制数,%d 表示以十进制整数的形式输出。

       %d称为格式控制符,它指明了以何种形式输出数据。格式控制符均以%开头,后跟其余字符。%d 表示以十进制形式输出一个整数。除了 %d,printf 支持更多的格式控制,例如:

  • %c:输出一个字符。c 是 character 的简写。
  • %s:输出一个字符串。s 是 string 的简写。
  • %f:输出一个小数。f 是 float 的简写

         \n是一个总体,组合在一块儿表示一个换行字符。换行符是 ASCII 编码中的一个控制字符,没法在键盘上直接输入,只能用这种特殊的方法表示,被称为转义字符

3.3  C语言中的整数(short,int,long)

          整数是编程中经常使用的一种数据,C语言一般使用int来定义整数(int 是 integer 的简写)

          在现代操做系统中,int 通常占用 4 个字节(Byte)的内存,共计 32 位(Bit)。若是不考虑正负数,当全部的位都为 1 时它的值最大,为 2^32-1 = 4,294,967,295 ≈ 43亿,这是一个很大的数,实际开发中不多用到,而诸如 一、9九、12098 等较小的数使用频率反而较高。

          使用 4 个字节保存较小的整数绰绰有余,会空闲出两三个字节来,这些字节就白白浪费掉了,不能再被其余数据使用。如今我的电脑的内存都比较大了,配置低的也有 2G,浪费一些内存不会带来明显的损失;而在C语言被发明的早期,或者在单片机和嵌入式系统中,内存都是很是稀缺的资源,全部的程序都在尽力节省内存。

           反过来讲,43 亿虽然已经很大,但要表示全球人口数量仍是不够,必需要让整数占用更多的内存,才能表示更大的值,好比占用 6 个字节或者 8 个字节。

           让整数占用更少的内存能够在 int 前边加 short,让整数占用更多的内存能够在 int 前边加 long,例如:

            int 是基本的整数类型,short 和 long 是在 int 的基础上进行的扩展,short 能够节省内存,long 能够容纳更大的值。short、int、long 是C语言中常见的整数类型,其中 int 称为整型,short 称为短整型,long 称为长整型。       

           整型的长度

         细心的读者可能会发现,上面咱们在描述 short、int、long 类型的长度时,只对 short 使用确定的说法,而对 int、long 使用了“通常”或者“可能”等不肯定的说法。这种描述的言外之意是,只有 short 的长度是肯定的,是两个字节,而 int 和 long 的长度没法肯定,在不一样的环境下有不一样的表现。

          一种数据类型占用的字节数,称为该数据类型的长度。例如,short 占用 2 个字节的内存,那么它的长度就是 2。

          实际状况也确实如此,C语言并无严格规定 short、int、long 的长度,只作了宽泛的限制:

  • short 至少占用 2 个字节。
  • int 建议为一个机器字长。32 位环境下机器字长为 4 字节,64 位环境下机器字长为 8 字节。
  • short 的长度不能大于 int,long 的长度不能小于 int。

           总结起来,它们的长度(所占字节数)关系为:   2 ≤ short ≤ int ≤ long

            这就意味着,short 并不必定真的”短“,long 也并不必定真的”长“,它们有可能和 int 占用相同的字节数。
           在 16 位环境下,short 的长度为 2 个字节,int 也为 2 个字节,long 为 4 个字节。16 位环境多用于单片机和低级嵌入式系统,在PC和服务器上已经见不到了。

            对于 32 位的 Windows、Linux 和 Mac OS,short 的长度为 2 个字节,int 为 4 个字节,long 也为 4 个字节。PC和服务器上的 32 位系统占有率也在慢慢降低,嵌入式系统使用 32 位愈来愈多。

            在 64 位环境下,不一样的操做系统会有不一样的结果,以下所示:

           

             目前咱们使用较多的PC系统为 Win XP、Win 七、Win 八、Win 十、Mac OS、Linux,在这些系统中,short 和 int 的长度都是固定的,分别为 2 和 4,你们能够放心使用,只有 long 的长度在 Win64 和类 Unix 系统下会有所不一样,使用时要注意移植性。

             sizeof 操做符

            获取某个数据类型的长度能够使用 sizeof 操做符

             sizeof 用来获取某个数据类型或变量所占用的字节数,若是后面跟的是变量名称,那么能够省略( ),若是跟的是数据类型,就必须带上( )须要注意的是,sizeof 是C语言中的操做符,不是函数,因此能够不带( )

             不一样整型的输出

          使用不一样的格式控制符能够输出不一样类型的整数,它们分别是:

  • %hd用来输出 short int 类型,hd 是 short decimal 的简写;
  • %d用来输出 int 类型,d 是 decimal 的简写;
  • %ld用来输出 long int 类型,ld 是 long decimal 的简写。

            在编写代码的过程当中,我建议将格式控制符和数据类型严格对应起来,养成良好的编程习惯。固然,若是你不严格对应,通常也不会致使错误,例如,不少初学者都使用%d输出全部的整数类型

            当使用%d输出 short,或者使用%ld输出 short、int 时,无论值有多大,都不会发生错误,由于格式控制符足够容纳这些值。
            当使用%hd输出 int、long,或者使用%d输出 long 时,若是要输出的值比较小,通常也不会发生错误,若是要输出的值比较大,就颇有可能发生错误,

3.4   C语言中的二进制数、八进制数和十六进制数

             C语言中的整数除了能够使用十进制,还能够使用二进制、八进制和十六进制。      

             二进制数、八进制数和十六进制数的表示

             一个数字默认就是十进制的,表示一个十进制数字不须要任何特殊的格式。可是,表示一个二进制、八进制或者十六进制数字就不同了,为了和十进制数字区分开来,必须采用某种特殊的写法,具体来讲,就是在数字前面加上特定的字符,也就是加前缀。

             1) 二进制

              二进制由 0 和 1 两个数字组成,使用时必须以0b0B(不区分大小写)开头   

             2) 八进制

              八进制由 0~7 八个数字组成,使用时必须以0开头(注意是数字 0,不是字母 o)

             3) 十六进制

              十六进制由数字 0~九、字母 A~F 或 a~f(不区分大小写)组成,使用时必须以0x0X(不区分大小写)开头

             4) 十进制

              十进制由 0~9 十个数字组成,没有任何前缀,和咱们平时的书写格式同样

             二进制数、八进制数和十六进制数的输出

             C语言中经常使用的整数有 short、int 和 long 三种类型,经过 printf 函数,能够将它们以八进制、十进制和十六进制的形式输出。上节咱们讲解了如何以十进制的形式输出,这节咱们重点讲解如何以八进制和十六进制的形式输出,下表列出了不一样类型的整数、以不一样进制的形式输出时对应的格式控制符:

                

                十六进制数字的表示用到了英文字母,有大小写之分,要在格式控制符中体现出来:

  • %hx、%x 和 %lx 中的x小写,代表以小写字母的形式输出十六进制数;
  • %hX、%X 和 %lX 中的X大写,代表以大写字母的形式输出十六进制数。

             八进制数字和十进制数字不区分大小写,因此格式控制符都用小写形式。若是你比较叛逆,想使用大写形式,那么行为是未定义的,请你慎重:

  • 有些编译器支持大写形式,只不过行为和小写形式同样;
  • 有些编译器不支持大写形式,可能会报错,也可能会致使奇怪的输出。

               输出时加上前缀

  • 对于八进制数字,它无法和十进制、十六进制区分,由于八进制、十进制和十六进制都包含 0~7 这几个数字。
  • 对于十进制数字,它无法和十六进制区分,由于十六进制也包含 0~9 这几个数字。若是十进制数字中还不包含 8 和 9,那么也不能和八进制区分了。
  • 对于十六进制数字,若是没有包含 a~f 或者 A~F,那么就没法和十进制区分,若是还不包含 8 和 9,那么也不能和八进制区分了。

            区分不一样进制数字的一个简单办法就是,在输出时带上特定的前缀。在格式控制符中加上#便可输出前缀,例如 %#x、%#o、%#lX、%#ho 等,请看下面的代码:

            十进制数字没有前缀,因此不用加#。若是你加上了,那么它的行为是未定义的,有的编译器支持十进制加#,只不过输出结果和没有加#同样,有的编译器不支持加#,可能会报错,也可能会致使奇怪的输出;可是,大部分编译器都能正常输出,不至于当成一种错误。

3.5 C语言中的正负数及其输出

            在数学中,数字有正负之分。在C语言中也是同样,short、int、long 均可以带上正负号

            若是不带正负号,默认就是正数。

            符号也是数字的一部分,也要在内存中体现出来。符号只有正负两种状况,用1位(Bit)就足以表示;C语言规定,把内存的最高位做为符号位。以 int 为例,它占用 32 位的内存,0~30 位表示数值,31 位表示正负号

               

              C语言规定,在符号位中,用 0 表示正数,用 1 表示负数。例如 int 类型的 -10 和 +16 在内存中的表示以下:

               

               short、int 和 long 类型默认都是带符号位的,符号位之外的内存才是数值位。若是只考虑正数,那么各类类型能表示的数值范围(取值范围)就比原来小了一半

               可是在不少状况下,咱们很是肯定某个数字只能是正数,好比班级学生的人数、字符串的长度、内存地址等,这个时候符号位就是多余的了,就不如删掉符号位,把全部的位都用来存储数值,这样能表示的数值范围更大(大一倍)

               C语言容许咱们这样作,若是不但愿设置符号位,能够在数据类型前面加上 unsigned 关键字

               这也意味着,使用了 unsigned 后只能表示正数,不能再表示负数了。

               若是将一个数字分为符号和数值两部分,那么不加 unsigned 的数字称为有符号数,能表示正数和负数,加了 unsigned 的数字称为无符号数,只能表示正数。
               请读者注意一个小细节,若是是unsigned int类型,那么能够省略 int ,只写 unsigned,   

              无符号数的输出

               无符号数能够以八进制、十进制和十六进制的形式输出,它们对应的格式控制符分别为:

               

            上节咱们也讲到了不一样进制形式的输出,可是上节咱们尚未讲到正负数,因此也没有关心这一点,只是“笼统”地介绍了一遍。如今本节已经讲到了正负数,那咱们就再深刻地说一下。
            严格来讲,格式控制符和整数的符号是紧密相关的,具体就是:

  • %d 以十进制形式输出有符号数;
  • %u 以十进制形式输出无符号数;
  • %o 以八进制形式输出无符号数;
  • %x 以十六进制形式输出无符号数。

             那么,如何以八进制和十六进制形式输出有符号数呢?很遗憾,printf 并不支持,也没有对应的格式控制符。在实际开发中,也基本没有“输出负的八进制数或者十六进制数”这样的需求,我想可能正是由于这一点,printf 才没有提供对应的格式控制符。

               下表全面地总结了不一样类型的整数,以不一样进制的形式输出时对应的格式控制符(--表示没有对应的格式控制符)。

               

               有读者可能会问,上节咱们也使用 %o 和 %x 来输出有符号数了,为何没有发生错误呢?这是由于:

  • 当以有符号数的形式输出时,printf 会读取数字所占用的内存,并把最高位做为符号位,把剩下的内存做为数值位;
  • 当以无符号数的形式输出时,printf 也会读取数字所占用的内存,并把全部的内存都做为数值位对待。

             对于一个有符号的正数,它的符号位是 0,当按照无符号数的形式读取时,符号位就变成了数值位,可是该位刚好是 0 而不是 1,因此对数值不会产生影响,这就比如在一个数字前面加 0,有多少个 0 都不会影响数字的值。

3.6   C语言中的小数(float,double)

             小数分为整数部分和小数部分,它们由点号.分隔,例如 0.0、75.0、4.02三、0.2七、-937.198 -0.27 等都是合法的小数,这是最多见的小数形式,咱们将它称为十进制形式。
             此外,小数也能够采用指数形式,例如 7.25×10二、0.0368×10五、100.22×10-二、-27.36×10-3 等。任何小数均可以用指数形式来表示。
           C语言同时支持以上两种形式的小数。可是在书写时,C语言中的指数形式和数学中的指数形式有所差别。
           C语言中小数的指数形式为:aEn 或 aen

           a 为尾数部分,是一个十进制数;n 为指数部分,是一个十进制整数;Ee是固定的字符,用于分割尾数部分和指数部分。整个表达式等价于 a×10n。

指数形式的小数举例:

  • 2.1E5 = 2.1×105,其中 2.1 是尾数,5 是指数。
  • 3.7E-2 = 3.7×10-2,其中 3.7 是尾数,-2 是指数。
  • 0.5E7 = 0.5×107,其中 0.5 是尾数,7 是指数。

           C语言中经常使用的小数有两种类型,分别是 float 或 double;float 称为单精度浮点型,double 称为双精度浮点型。
           不像整数,小数没有那么多幺蛾子,小数的长度是固定的,float 始终占用4个字节,double 始终占用8个字节

           小数的输出

    小数也能够使用 printf 函数输出,包括十进制形式和指数形式,它们对应的格式控制符分别是:

  • %f 以十进制形式输出 float 类型;
  • %lf 以十进制形式输出 double 类型;
  • %e 以指数形式输出 float 类型,输出结果中的 e 小写;
  • %E 以指数形式输出 float 类型,输出结果中的 E 大写;
  • %le 以指数形式输出 double 类型,输出结果中的 e 小写;
  • %lE 以指数形式输出 double 类型,输出结果中的 E 大写。

               1) %f 和 %lf 默认保留六位小数,不足六位以 0 补齐,超过六位按四舍五入截断。
               2) 将整数赋值给 float 变量时会变成小数。
               3) 以指数形式输出小数时,输出结果为科学计数法;也就是说,尾数部分的取值为:0 ≤ 尾数 < 10。
               4) b 的输出结果让人费解,才三位小数,为何不能精确输出,而是输出一个近似值呢?这和小数在内存中的存储形式有关,不少简单的小数压根不能精确存储,因此也就不能精确输出,
               另外,小数还有一种更加智能的输出方式,就是使用%g。%g 会对比小数的十进制形式和指数形式,以最短的方式来输出小数,让输出结果更加简练。所谓最短,就是输出结果占用最少的字符。

               数字的后缀

               一个数字,是有默认类型的:对于整数,默认是 int 类型;对于小数,默认是 double 类型。

               若是不想让数字使用默认的类型,那么能够给数字加上后缀,手动指明类型:

  • 在整数后面紧跟 l 或者 L(不区分大小写)代表该数字是 long 类型;
  • 在小数后面紧跟 f 或者 F(不区分大小写)代表该数字是 float 类型。

               小数和整数相互赋值

在C语言中,整数和小数之间能够相互赋值:

  • 将一个整数赋值给小数类型,在小数点后面加 0 就能够,加几个都无所谓。
  • 将一个小数赋值给整数类型,就得把小数部分丢掉,只能取整数部分,这会改变数字原本的值。注意是直接丢掉小数部分,而不是按照四舍五入取近似值。

3.7   在C语言中使用英文字符

               前面咱们屡次提到了字符串,字符串是多个字符的集合,它们由" "包围,例如"http://c.biancheng.net""C语言中文网"。字符串中的字符在内存中按照次序、紧挨着排列,整个字符串占用一块连续的内存。
               固然,字符串也能够只包含一个字符,例如"A""6";不过为了操做方便,咱们通常使用专门的字符类型来处理。
               初学者常常用到的字符类型是 char,它的长度是 1,只能容纳 ASCII 码表中的字符,也就是英文字符。
               字符的表示:字符类型由单引号' '包围,字符串由双引号" "包围。

                说明:在字符集中,全角字符和半角字符对应的编号(或者说编码值)不一样,是两个字符;ASCII 编码只定义了半角字符,没有定义全角字符。

               字符的输出

              输出 char 类型的字符有两种方法,分别是:

  • 使用专门的字符输出函数 putchar;
  • 使用通用的格式化输出函数 printf,char 对应的格式控制符是%c

               字符与整数

               咱们知道,计算机在存储字符时并非真的要存储字符实体,而是存储该字符在字符集中的编号(也能够叫编码值)。对于 char 类型来讲,它实际上存储的就是字符的 ASCII 码。
              不管在哪一个字符集中,字符编号都是一个整数;从这个角度考虑,字符类型和整数类型本质上没有什么区别。
               咱们能够给字符类型赋值一个整数,或者以整数的形式输出字符类型。反过来,也能够给整数类型赋值一个字符,或者以字符的形式输出整数类型。

               能够说,是 ASCII 码表将英文字符和整数关联了起来。

               再谈字符串

               前面咱们讲到了字符串的概念,也讲到了字符串的输出,可是尚未讲如何用变量存储一个字符串。其实在C语言中没有专门的字符串类型,咱们只能使用数组或者指针来间接地存储字符串。

3.8 C语言转义字符

              字符集(Character Set)为每一个字符分配了惟一的编号,咱们不妨将它称为编码值。在C语言中,一个字符除了能够用它的实体(也就是真正的字符)表示,还能够用编码值表示。这种使用编码值来间接地表示字符的方式称为转义字符(Escape Character)。
             转义字符以\或者\x开头,以\开头表示后跟八进制形式的编码值,以\x开头表示后跟十六进制形式的编码值。对于转义字符来讲,只能使用八进制或者十六进制。

              转义字符既能够用于单个字符,也能够用于字符串,而且一个字符串中能够同时使用八进制形式和十六进制形式。

转义字符的初衷是用于 ASCII 编码,因此它的取值范围有限:

  • 八进制形式的转义字符最多后跟三个数字,也即\ddd,最大取值是\177
  • 十六进制形式的转义字符最多后跟两个数字,也即\xdd,最大取值是\7f

              超出范围的转义字符的行为是未定义的,有的编译器会将编码值直接输出,有的编译器会报错。

              对于 ASCII 编码,0~31(十进制)范围内的字符为控制字符,它们都是看不见的,不能在显示器上显示,甚至没法从键盘输入,只能用转义字符的形式来表示。不过,直接使用 ASCII 码记忆不方便,也不容易理解,因此,针对经常使用的控制字符,C语言又定义了简写方式,完整的列表以下:

               

               \n\t是最经常使用的两个转义字符:

  • \n用来换行,让文本从下一行的开头输出,前面的章节中已经屡次使用;
  • \t用来占位,通常至关于四个空格,或者 tab 键的功能。

               单引号、双引号、反斜杠是特殊的字符,不能直接表示:

  • 单引号是字符类型的开头和结尾,要使用\'表示,也即'\''
  • 双引号是字符串的开头和结尾,要使用\"表示,也即"abc\"123"
  • 反斜杠是转义字符的开头,要使用\\表示,也即'\\',或者"abc\\123"

3.9  C语言标识符、关键字、注释、表达式和语句

              标识符

            定义变量时,咱们使用了诸如 a、abc、mn123 这样的名字,它们都是程序员本身起的,通常可以表达出变量的做用,这叫作标识符(Identifier)。
            标识符就是程序员本身起的名字,除了变量名,后面还会讲到函数名、宏名、结构体名等,它们都是标识符。不过,名字也不能随便起,要遵照规范;C语言规定,标识符只能由字母(A~Z, a~z)、数字(0~9)和下划线(_)组成,而且第一个字符必须是字母或下划线,不能是数字。
         在使用标识符时还必须注意如下几点:

  • C语言虽然不限制标识符的长度,可是它受到不一样编译器的限制,同时也受到操做系统的限制。例如在某个编译器中规定标识符前128位有效,当两个标识符前128位相同时,则被认为是同一个标识符。
  • 在标识符中,大小写是有区别的,例如 BOOK 和 book 是两个不一样的标识符。
  • 标识符虽然可由程序员随意定义,但标识符是用于标识某个量的符号,所以,命名应尽可能有相应的意义,以便于阅读和理解,做到“顾名思义”。

                关键字

                关键字(Keywords)是由C语言规定的具备特定意义的字符串,一般也称为保留字,例如 int、char、long、float、unsigned 等。咱们定义的标识符不能与关键字相同,不然会出现错误。

你也能够将关键字理解为具备特殊含义的标识符,它们已经被系统使用,咱们不能再使用了。

              标准C语言中一共规定了32个关键字,你们能够参考C语言关键字及其解释[共32个],后续咱们会一一讲解。

              注释

               注释(Comments)能够出如今代码中的任何位置,用来向用户提示或解释代码的含义。程序编译时,会忽略注释,不作任何处理,就好像它不存在同样。
               C语言支持单行注释和多行注释:

  • 单行注释以//开头,直到本行末尾(不能换行);
  • 多行注释以/*开头,以*/结尾,注释内容能够有一行或多行。

                在调试程序的过程当中能够将暂时将不使用的语句注释掉,使编译器跳过不做处理,待调试结束后再去掉注释。须要注意的是,多行注释不能嵌套使用

                 表达式(Expression)和语句(Statement)

                表达式(Expression)和语句(Statement)的概念在C语言中并无明确的定义:

  • 表达式能够看作一个计算的公式,每每由数据、变量、运算符等组成,例如3*4+5a=c=d等,表达式的结果一定是一个值;
  • 语句的范围更加普遍,不必定是计算,不必定有值,能够是某个操做、某个函数、选择结构、循环等。

赶忙划重点:

  • 表达式必须有一个执行结果,这个结果必须是一个值,例如3*4+5的结果 17,a=c=d=10的结果是 10,printf("hello")的结果是 5(printf 的返回值是成功打印的字符的个数)。
  • 以分号;结束的每每称为语句,而不是表达式,例如3*4+5;a=c=d;等。

3.10  C语言加减乘除运算

                加减乘除是常见的数学运算,C语言固然支持,不过,C语言中的运算符号与数学中的略有不一样,请见下表。

                

                对除法的说明

              C语言中的除法运算有点奇怪,不一样类型的除数和被除数会致使不一样类型的运算结果:

  • 当除数和被除数都是整数时,运算结果也是整数;若是不能整除,那么就直接丢掉小数部分,只保留整数部分,这跟将小数赋值给整数类型是一个道理。
  • 一旦除数和被除数中有一个是小数,那么运算结果也是小数,而且是 double 类型的小数。

               另外须要注意的一点是除数不能为 0,由于任何一个数字除以 0 都没有意义。
              然而,编译器对这个错误通常无能为力,不少状况下,编译器在编译阶段根本没法计算出除数的值,不能进行有效预测,“除数为 0”这个错误只能等到程序运行后才能发现,而程序一旦在运行阶段出现任何错误,只能有一个结果,那就是崩溃,并被操做系统终止运行。

              对取余运算的说明

             取余,也就是求余数,使用的运算符是 %。C语言中的取余运算只能针对整数,也就是说,% 的两边都必须是整数,不能出现小数,不然编译器会报错。

另外,余数能够是正数也能够是负数,由 % 左边的整数决定:

  • 若是 % 左边是正数,那么余数也是正数;
  • 若是 % 左边是负数,那么余数也是负数。

3.11 C语言数据类型转换(自动类型转换+强制类型转换)

3.11.1 自动类型转换

             自动类型转换就是编译器默默地、隐式地、偷偷地进行的数据类型转换,这种转换不须要程序员干预,会自动发生。
             1) 将一种类型的数据赋值给另一种类型的变量时就会发生自动类型转换

              在赋值运算中,赋值号两边的数据类型不一样时,须要把右边表达式的类型转换为左边变量的类型,这可能会致使数据失真,或者精度下降;因此说,自动类型转换并不必定是安全的。对于不安全的类型转换,编译器通常会给出警告。

            2) 在不一样类型的混合运算中,编译器也会自动地转换数据类型,将参与运算的全部数据先转换为同一种类型,而后再进行计算。转换的规则以下:

 

  • 换按数据长度增长的方向进行,以保证数值不失真,或者精度不下降。例如,int 和 long 参与运算时,先把 int 类型的数据转成 long 类型后再进行运算。
  • 全部的浮点运算都是以双精度进行的,即便运算中只有 float 类型,也要先转换为 double 类型,才能进行运算。
  • char 和 short 参与运算时,必须先转换成 int 类型。

            

3.11.2 强制类型转换

             自动类型转换是编译器根据代码的上下文环境自行判断的结果,有时候并非那么“智能”,不能知足全部的需求。若是须要,程序员也能够本身在代码中明确地提出要进行类型转换,这称为强制类型转换。
             自动类型转换是编译器默默地、隐式地进行的一种类型转换,不须要在代码中体现出来;强制类型转换是程序员明确提出的、须要经过特定格式的代码来指明的一种类型转换。换句话说,自动类型转换不须要程序员干预,强制类型转换必须有程序员干预。

3.11.3  类型转换只是临时性的

             不管是自动类型转换仍是强制类型转换,都只是为了本次运算而进行的临时性转换,转换的结果也会保存到临时的内存空间,不会改变数据原本的类型或者值。

3.11.4   自动类型转换 VS 强制类型转换

            在C语言中,有些类型既能够自动转换,也能够强制转换,例如 int 到 double,float 到 int 等;而有些类型只能强制转换,不能自动转换,例如之后将要学到的 void * 到 int *,int 到 char * 等。
            能够自动转换的类型必定可以强制转换,可是,须要强制转换的类型不必定可以自动转换。如今咱们学到的数据类型,既能够自动转换,又能够强制转换,之后咱们还会学到一些只能强制转换而不能自动转换的类型。
            能够自动进行的类型转换通常风险较低,不会对程序带来严重的后果,例如,int 到 double 没有什么缺点,float 到 int 顶可能是数值失真。只能强制进行的类型转换通常风险较高,或者行为匪夷所思,例如,char * 到 int * 就是很奇怪的一种转换,这会致使取得的值也很奇怪,再如,int 到 char * 就是风险极高的一种转换,通常会致使程序崩溃。
             使用强制类型转换时,程序员本身要意识到潜在的风险。

四、C语言输入输出

             输入输出(Input and Output, IO)是用户和程序“交流”的过程。在控制台程序中,输出通常是指将数据(包括数字、字符等)显示在屏幕上,输入通常是指获取用户在键盘上输入的数据。

4.1  C语言数据输出大汇总以及轻量进阶

            在C语言中,有三个函数能够用来在显示器上输出数据,它们分别是:

  • puts():只能输出字符串,而且输出结束后会自动换行
  • putchar():只能输出单个字符
  • printf():能够输出各类类型的数据

              printf() 是最灵活、最复杂、最经常使用的输出函数,彻底能够替代 puts() 和 putchar()

               首先汇总一下前面学到的格式控制符:

            printf() 格式控制符的完整形式以下:     %[flag][width][.precision]type

[ ] 表示此处的内容无关紧要,是能够省略的。

1) type 表示输出类型,好比 %d、%f、%c、%lf,type 就分别对应 d、f、c、lf;再如,%-9d中 type 对应 d。type 这一项必须有,这意味着输出时必需要知道是什么类型。
2) width 表示最小输出宽度,也就是至少占用几个字符的位置;例如,%-9d中 width 对应 9,表示输出结果最少占用 9 个字符的宽度。当输出结果的宽度不足 width 时,以空格补齐(若是没有指定对齐方式,默认会在左边补齐空格);当输出结果的宽度超过 width 时,width 再也不起做用,按照数据自己的宽度来输出。

3) .precision 表示输出精度,也就是小数的位数。

  • 当小数部分的位数大于 precision 时,会按照四舍五入的原则丢掉多余的数字;
  • 当小数部分的位数小于 precision 时,会在后面补 0。

另外,.precision 也能够用于整数和字符串,可是功能倒是相反的:

  • 用于整数时,.precision 表示最小输出宽度。与 width 不一样的是,整数的宽度不足时会在左边补 0,而不是补空格。
  • 用于字符串时,.precision 表示最大输出宽度,或者说截取字符串。当字符串的长度大于 precision 时,会截掉多余的字符;当字符串的长度小于 precision 时,.precision 就再也不起做用。

  4) flag 是标志字符。例如,%#x中 flag 对应 #,%-9d中 flags 对应-。下表列出了 printf() 能够用的 flag:

                printf() 不能当即输出的问题

                 printf() 有一个尴尬的问题,就是有时候不能当即输出

                从本质上讲,printf() 执行结束之后数据并无直接输出到显示器上,而是放入了缓冲区,直到碰见换行符\n才将缓冲区中的数据输出到显示器上

4.2 C语言scanf:读取从键盘输入的数据(含输入格式汇总表)

               程序是人机交互的媒介,有输出必然也有输入,第三章咱们讲解了如何将数据输出到显示器上,本章咱们开始讲解如何从键盘输入数据。在C语言中,有多个函数能够从键盘得到用户输入:

  • scanf():和 printf() 相似,scanf() 能够输入多种类型的数据。
  • getchar()、getche()、getch():这三个函数都用于输入单个字符。
  • gets():获取一行数据,并做为字符串处理。

               scanf() 是最灵活、最复杂、最经常使用的输入函数,但它不能彻底取代其余函数,你们都要有所了解。
              本节咱们只讲解 scanf(),其它的输入函数将在下节讲解。

             scanf()函数

             scanf 是 scan format 的缩写,意思是格式化扫描,也就是从键盘得到用户输入,和 printf 的功能正好相反。

             其实 scanf 和 printf 很是类似,只是功能相反罢了:它们都有格式控制字符串,都有变量列表。不一样的是,scanf 的变量前要带一个&符号。&称为取地址符,也就是获取变量在内存中的地址。

            数据是以二进制的形式保存在内存中的,字节(Byte)是最小的可操做单位。为了便于管理,咱们给每一个字节分配了一个编号,使用该字节时,只要知道编号就能够,就像每一个学生都有学号,老师会随机抽取学号来让学生回答问题。字节的编号是有顺序的,从 0 开始,接下来是 一、二、3……

               

              这个编号,就叫作地址(Address)。int a;会在内存中分配四个字节的空间,咱们将第一个字节的地址称为变量 a 的地址,也就是&a的值。对于前面讲到的整数、浮点数、字符,都要使用 & 获取它们的地址,scanf 会根据地址把读取到的数据写入内存。

             scanf() 格式控制符汇总

五、C语言循环结构和选择结构

      C语言中有三大结构,分别是顺序结构、选择结构和循环结构(分支结构):

  • C语言顺序结构就是让程序按照从头至尾的顺序依次执行每一条C语言代码,不重复执行任何代码,也不跳过任何代码。
  • C语言选择结构也称分支结构,就是让程序“拐弯”,有选择性的执行代码;换句话说,能够跳过没用的代码,只执行有用的代码。
  • C语言循环结构就是让程序“杀个回马枪”,不断地重复执行同一段代码。

5.1 C语言if else语句详解

               if 和 else 是两个新的关键字,if 意为“若是”,else 意为“不然”,用来对条件进行判断,并根据判断结果执行不一样的语句。总结起来,if else 的结构为:

if(判断条件){
    语句块1
}else{
    语句块2
}

              所谓语句块(Statement Block),就是由{ }包围的一个或多个语句的集合。若是语句块中只有一个语句,也能够省略{ }

5.1.1 只使用if语句

             有的时候,咱们须要在知足某种条件时进行一些操做,而不知足条件时就不进行任何操做,这个时候咱们能够只使用 if 语句。也就是说,if else 没必要同时出现。

5.1.2  多个if else语句

if(判断条件1){
    语句块1
} else  if(判断条件2){
    语句块2
}else  if(判断条件3){
    语句块3
}else  if(判断条件m){
    语句块m
}else{
     语句块n
}

5.2  C语言关系运算符详解

           关系运算符在使用时,它的的两边都会有一个表达式,好比变量、数值、加减乘除运算等,关系运算符的做用就是判明这两个表达式的大小关系。注意,是判明大小关系,不是其余关系。

            

5.3 C语言逻辑运算符详解

               

5.4   C语言switch case语句详解

          switch 是另一种选择结构的语句,用来代替简单的、拥有多个分枝的 if else 语句,基本格式以下:

switch(表达式){
    case 整型数值1: 语句 1;
    case 整型数值2: 语句 2;
    ......
    case 整型数值n: 语句 n;
    default: 语句 n+1;
}

             它的执行过程是:
1) 首先计算“表达式”的值,假设为 m。
2) 从第一个 case 开始,比较“整型数值1”和 m,若是它们相等,就执行冒号后面的全部语句,也就是从“语句1”一直执行到“语句n+1”,而无论后面的 case 是否匹配成功。
3) 若是“整型数值1”和 m 不相等,就跳过冒号后面的“语句1”,继续比较第二个 case、第三个 case……一旦发现和某个整型数值相等了,就会执行后面全部的语句。假设 m 和“整型数值5”相等,那么就会从“语句5”一直执行到“语句n+1”。
4) 若是直到最后一个“整型数值n”都没有找到相等的值,那么就执行 default 后的“语句 n+1”。
须要重点强调的是,当和某个整型数值匹配成功后,会执行该分支以及后面全部分支的语句

              break 是C语言中的一个关键字,专门用于跳出 switch 语句。所谓“跳出”,是指一旦遇到 break,就再也不执行 switch 中的任何语句,包括当前分支中的语句和其余分支中的语句;也就是说,整个 switch 执行结束了,接着会执行整个 switch 后面的代码。

              因为 default 是最后一个分支,匹配后不会再执行其余分支,因此也能够不添加break;语句。
最后须要说明的两点是:
1) case 后面必须是一个整数,或者是结果为整数的表达式,但不能包含任何变量

2) default 不是必须的。当没有 default 时,若是全部 case 都匹配失败,那么就什么都不执行。

5.5   C语言条件运算符详解

            表达式1 ? 表达式2 : 表达式3

            条件运算符是C语言中惟一的一个三目运算符,其求值规则为:若是表达式1的值为真,则以表达式2 的值做为整个条件表达式的值,不然以表达式3的值做为整个条件表达式的值。条件表达式一般用于赋值语句之中。

5.6  C语言while循环和do while循环详解

5.6.1 while

while循环的通常形式为:

while(表达式){
    语句块
}

5.6.2   do-while循环

do{
    语句块
}while(表达式);

5.7  C语言for循环(for语句)详解

for(表达式1; 表达式2; 表达式3){
    语句块
}

5.8   C语言break和continue用法详解(跳出循环)

                 当 break 关键字用于 while、for 循环时,会终止循环而执行整个循环语句后面的代码。break 关键字一般和 if 语句一块儿使用,即知足条件时便跳出循环。

                 continue 语句的做用是跳过循环体中剩余的语句而强制进入下一次循环。continue语句只用在 while、for 循环中,常与 if 条件语句一块儿使用,判断条件是否成立。

6    c语言数组

6.1  什么是数组?C语言数组的基本概念

6.1.1 数组的概念和定义

              咱们知道,要想把数据放入内存,必须先要分配内存空间。放入4个整数,就得分配4个int类型的内存空间

              一组数据的集合称为数组(Array),它所包含的每个数据叫作数组元素(Element),所包含的数据的个数称为数组长度(Length)

              数组中的每一个元素都有一个序号,这个序号从0开始,而不是从咱们熟悉的1开始,称为下标(Index)。使用数组元素时,指明下标便可

              须要注意的是:
1) 数组中每一个元素的数据类型必须相同,对于int a[4];,每一个元素都必须为 int。
2) 数组长度 length 最好是整数或者常量表达式,例如 十、20*4 等,这样在全部编译器下都能运行经过;若是 length 中包含了变量,例如 n、4*m 等,在某些编译器下就会报错,咱们将在《C语言变长数组:使用变量指明数组的长度》一节专门讨论这点。
3) 访问数组元素时,下标的取值范围为 0 ≤ index < length,过大或太小都会越界,致使数组溢出,发生不可预测的状况,咱们将在《C语言数组的越界和溢出》一节重点讨论,请你们务必要引发注意。

6.1.2   数组内存是连续的

             数组是一个总体,它的内存是连续的;也就是说,数组元素之间是相互挨着的,彼此之间没有一点点缝隙。下图演示了int a[4];在内存中的存储情形:

            

6.1.3   数组的初始化

               数组元素的值由{ }包围,各个值之间以,分隔。

              对于数组的初始化须要注意如下几点:
              1) 能够只给部分元素赋值。当{ }中值的个数少于元素个数时,只给前面部分元素赋值。 当赋值的元素少于数组整体元素的时候,剩余的元素自动初始化为 0:

  • 对于short、int、long,就是整数 0;
  • 对于char,就是字符 '\0';
  • 对于float、double,就是小数 0.0。

              2) 只能给元素逐个赋值,不能给数组总体赋值。

             3) 如给所有元素赋值,那么在定义数组时能够不给出数组长度

6.2    C语言字符数组和字符串详解

             用来存放字符的数组称为字符数组

            字符数组其实是一系列字符的集合,也就是字符串(String)。在C语言中,没有专门的字符串变量,没有string类型,一般就用一个字符数组来存放一个字符串。

6.2.1  字符串结束标志(划重点)

           字符串是一系列连续的字符的组合,要想在内存中定位一个字符串,除了要知道它的开头,还要知道它的结尾。找到字符串的开头很容易,知道它的名字(字符数组名或者字符串名)就能够;然而,如何找到字符串的结尾呢?C语言的解决方案有点奇妙,或者说有点奇葩。
在C语言中,字符串老是以'\0'做为结尾,因此'\0'也被称为字符串结束标志,或者字符串结束符。

            '\0'是 ASCII 码表中的第 0 个字符,英文称为 NUL,中文称为“空字符”。该字符既不能显示,也没有控制功能,输出该字符不会有任何效果,它在C语言中惟一的做用就是做为字符串结束标志。

             C语言在处理字符串时,会从前日后逐个扫描字符,一旦遇到'\0'就认为到达了字符串的末尾,就结束处理。'\0'相当重要,没有'\0'就意味着永远也到达不了字符串的结尾。

            " "包围的字符串会自动在末尾添加'\0'。例如,"abc123"从表面看起来只包含了 6 个字符,其实否则,C语言会在最后隐式地添加一个'\0',这个过程是在后台默默地进行的,因此咱们感觉不到。

           须要注意的是,逐个字符地给数组赋值并不会自动添加'\0

           当用字符数组存储字符串时,要特别注意'\0',要为'\0'留个位置;这意味着,字符数组的长度至少要比字符串的长度大 1

           在不少编译器下,局部变量的初始值是随机的,是垃圾值,而不是咱们一般认为的“零”值。局部数组不少编译器并不会把局部数组的内存都初始化为“零”值,而是听任无论,爱是什么就是什么,因此它们的值也是没有意义的,也是垃圾

           在函数内部定义的变量、数组、结构体、共用体等都称为局部数据。在不少编译器下,局部数据的初始值都是随机的、无心义的,而不是咱们一般认为的“零”值。这一点很是重要,你们必定要谨记,不然后面会遇到不少奇葩的错误。

6.2.2   字符串长度

           所谓字符串长度,就是字符串包含了多少个字符(不包括最后的结束符'\0')。例如"abc"的长度是 3,而不是 4。
           在C语言中,咱们使用string.h头文件中的 strlen() 函数来求字符串的长度,它的用法为:

6.3   C语言字符串处理函数

           C语言提供了丰富的字符串处理函数,能够对字符串进行输入、输出、合并、修改、比较、转换、复制、搜索等操做,使用这些现成的函数能够大大减轻咱们的编程负担。
         用于输入输出的字符串函数,例如printfputsscanfgets等,使用时要包含头文件stdio.h,而使用其它字符串函数要包含头文件string.h

6.3.1  字符串链接函数 strcat()

         strcat 是 string catenate 的缩写,意思是把两个字符串拼接在一块儿,语法格式为:strcat(arrayName1, arrayName2);

         arrayName一、arrayName2 为须要拼接的字符串。
         strcat() 将把 arrayName2 链接到 arrayName1 后面,并删除原来 arrayName1 最后的结束标志'\0'。这意味着,arrayName1 必须足够长,要可以同时容纳 arrayName1 和 arrayName2,不然会越界(超出范围)。
        strcat() 的返回值为 arrayName1 的地址。

6.3.2  字符串复制函数 strcpy()

            strcpy 是 string copy 的缩写,意思是字符串复制,也即将字符串从一个地方复制到另一个地方,语法格式为:

           strcpy(arrayName1, arrayName2);

           strcpy() 会把 arrayName2 中的字符串拷贝到 arrayName1 中,字符串结束标志'\0'也一同拷贝

6.3.3    字符串比较函数 strcmp()

          strcmp 是 string compare 的缩写,意思是字符串比较,语法格式为:strcmp(arrayName1, arrayName2);

          arrayName1 和 arrayName2 是须要比较的两个字符串。
         字符自己没有大小之分,strcmp() 以各个字符对应的 ASCII 码值进行比较。strcmp() 从两个字符串的第 0 个字符开始比较,若是它们相等,就继续比较下一个字符,直到碰见不一样的字符,或者到字符串的末尾。
        返回值:若 arrayName1 和 arrayName2 相同,则返回0;若 arrayName1 大于 arrayName2,则返回大于 0 的值;若 arrayName1 小于 arrayName2,则返回小于0 的值。

6.4  对C语言数组的总结

 

              数组(Array)是一系列相同类型的数据的集合,能够是一维的、二维的、多维的;最经常使用的是一维数组和二维数组,多维数组较少用到。

6.4.1  对数组的总结

       1) 数组的定义格式为:type arrayName[length]

        type 为数据类型,arrayName 为数组名,length 为数组长度。 须要注意的是:

  • 数组长度 length 最好是常量表达式,例如 十、20*4 等,这样在全部编译器下都能运行经过;若是 length 中包含了变量,例如 n、4*m 等,在某些编译器下就会报错
  • 数组是一个总体,它的内存是连续的;也就是说,数组元素之间是相互挨着的,彼此之间没有一点点缝隙。
  • 通常状况下,数组名会转换为数组的地址,须要使用地址的地方,直接使用数组名便可。

七、 C语言函数详解(包括声明、定义、使用等)

7.1  什么是函数?C语言函数的概念

                从表面上看,函数在使用时必须带上括号,有必要的话还要传递参数,函数的执行结果也能够赋值给其它变量。例如,strcmp() 是一个用来比较字符串大小的函数

                C语言提供了一个功能,容许咱们将经常使用的代码以固定的格式封装(包装)成一个独立的模块,只要知道这个模块的名字就能够重复使用它,这个模块就叫作函数(Function)。函数的本质是一段能够重复使用的代码,这段代码被提早编写好了,放到了指定的文件中,使用时直接调取便可

                咱们本身编写的函数,放在了当前源文件中(函数封装和函数使用在同一个源文件中),因此不须要引入头文件;而C语言自带的 strcmp() 放在了其它的源文件中(函数封装和函数使用不在同一个源文件中),并在 string.h 头文件中告诉咱们如何使用,因此咱们必须引入 string.h 头文件。

7.1.1  C语言中的函数和数学中的函数

             美国人将函数称为“Function”。Function 除了有“函数”的意思,还有“功能”的意思,中国人将 Function 译为“函数”而不是“功能”,是由于C语言中的函数和数学中的函数在使用形式上有些相似,例如:

  • C语言中有 length = strlen(str)
  • 数学中有 y = f(x)

             不过从本质上看,将 Function 理解为“功能”或许更恰当,C语言中的函数每每是独立地实现了某项功能。一个程序由多个函数组成,能够理解为「一个程序由多个小的功能叠加而成」。

7.1.2  库函数和自定义函数

           C语言在发布时已经为咱们封装好了不少函数,它们被分门别类地放到了不一样的头文件中(暂时先这样认为),使用函数时引入对应的头文件便可。这些函数都是专家编写的,执行效率极高,而且考虑到了各类边界状况,各位读者请放心使用。
           C语言自带的函数称为库函数(Library Function)。库(Library)是编程中的一个基本概念,能够简单地认为它是一系列函数的集合,在磁盘上每每是一个文件夹。C语言自带的库称为标准库(Standard Library),其余公司或我的开发的库称为第三方库(Third-Party Library)。

7.1.3  参数

            函数的一个明显特征就是使用时带括号( ),有必要的话,括号中还要包含数据或变量,称为参数(Parameter)。参数是函数须要处理的数据,例如:

  • strlen(str1)用来计算字符串的长度,str1就是参数。
  • puts("C语言中文网")用来输出字符串,"C语言中文网"就是参数。

7.1.4 返回值

           既然函数能够处理数据,那就有必要将处理结果告诉咱们,因此不少函数都有返回值(Return Value)。所谓返回值,就是函数的执行结果

7.2  C语言函数定义(C语言自定义函数)

               函数是一段能够重复使用的代码,用来独立地完成某个功能,它能够接收用户传递的数据,也能够不接收。接收用户数据的函数在定义时要指明参数,不接收用户数据的不须要指明,根据这一点能够将函数分为有参函数和无参函数。
              将代码段封装成函数的过程叫作函数定义

7.2.1 C语言无参函数的定义

          若是函数不接收用户传递的数据,那么定义时能够不带参数。以下所示:

dataType  functionName(){
    //body
}

  • dataType 是返回值类型,它能够是C语言中的任意数据类型,例如 int、float、char 等。
  • functionName 是函数名,它是标识符的一种,命名规则和标识符相同。函数名后面的括号( )不能少。
  • body 是函数体,它是函数须要执行的代码,是函数的主体部分。即便只有一个语句,函数体也要由{ }包围。
  • 若是有返回值,在函数体中使用 return 语句返回。return 出来的数据的类型要和 dataType 同样

          无返回值函数:有的函数不须要返回值,或者返回值类型不肯定(不多见),那么能够用 void 表示

7.2.2  C语言有参函数的定义

          若是函数须要接收用户传递的数据,那么定义时就要带上参数。以下所示:

dataType  functionName( dataType1 param1, dataType2 param2 ... ){
    //body
}

       dataType1 param1, dataType2 param2 ...是参数列表。函数能够只有一个参数,也能够有多个,多个参数之间由,分隔。参数本质上也是变量,定义时要指明类型和名称。与无参函数的定义相比,有参函数的定义仅仅是多了一个参数列表。
          数据经过参数传递到函数内部进行处理,处理完成之后再经过返回值告知函数外部。

         函数定义时给出的参数称为形式参数,简称形参;函数调用时给出的参数(也就是传递的数据)称为实际参数,简称实参。函数调用时,将实参的值传递给形参,至关于一次赋值操做。 
         原则上讲,实参的类型和数目要与形参保持一致。若是可以进行自动类型转换,或者进行了强制类型转换,那么实参类型也能够不一样于形参类型,例如将 int 类型的实参传递给 float 类型的形参就会发生自动类型转换。

7.3  C语言形参和实参的区别(很是详细)

            若是把函数比喻成一台机器,那么参数就是原材料,返回值就是最终产品;从必定程度上讲,函数的做用就是根据不一样的参数产生不一样的返回值。

             C语言函数的参数会出如今两个地方,分别是函数定义处和函数调用处,这两个地方的参数是有区别的。

             形参(形式参数)

             在函数定义中出现的参数能够看作是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,因此称为形式参数,简称形参

            实参(实际参数)

           函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,因此称为实际参数,简称实参。形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。

7.3.1  形参和实参的区别和联系

1) 形参变量只有在函数被调用时才会分配内存,调用结束后,马上释放内存,因此形参变量只有在函数内部有效,不能在函数外部使用。
2) 实参能够是常量、变量、表达式、函数等,不管实参是何种类型的数据,在进行函数调用时,它们都必须有肯定的值,以便把这些值传送给形参,因此应该提早用赋值、输入等办法使实参得到肯定值。
3) 实参和形参在数量上、类型上、顺序上必须严格一致,不然会发生“类型不匹配”的错误。固然,若是可以进行自动类型转换,或者进行了强制类型转换,那么实参类型也能够不一样于形参类型。
4) 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,因此,在函数调用过程当中,形参的值发生改变并不会影响实参。                                                                5) 形参和实参虽然能够同名,但它们之间是相互独立的,互不影响,由于实参在函数外部有效,而形参在函数内部有效。

7.4   C语言return的用法详解,C语言函数返回值详解

            函数的返回值是指函数被调用以后,执行函数体中的代码所获得的结果,这个结果经过 return 语句返回。

            对C语言返回值的说明:

           1) 没有返回值的函数为空类型,用void表示,一旦函数的返回值类型被定义为 void,就不能再接收它的值了。例如,下面的语句是错误的:int a = func();       为了使程序有良好的可读性并减小出错, 凡不要求返回值的函数都应定义为 void 类型。

           2) return 语句能够有多个,能够出如今函数体的任意位置,可是每次调用函数只能有一个 return 语句被执行,因此只有一个返回值(少数的编程语言支持多个返回值,例如Go语言)。

           3) 函数一旦遇到 return 语句就当即返回,后面的全部语句都不会被执行。从这个角度看,return 语句还有强制结束函数执行的做用

          return 语句是提早结束函数的惟一办法。return 后面能够跟一份数据,表示将这份数据返回到函数外面;return 后面也能够不跟任何数据,表示什么也不返回,仅仅用来结束函数。

7.5  C语言函数调用详解(从中发现程序运行的秘密)

           所谓函数调用(Function Call),就是使用已经定义好的函数。函数调用的通常形式为:functionName(param1, param2, param3 ...);

functionName 是函数名称,param1, param2, param3 ...是实参列表。实参能够是常数、变量、表达式等,多个实参用逗号,分隔。

           函数不能嵌套定义,但能够嵌套调用,也就是在一个函数的定义或调用过程当中容许出现对另一个函数的调用。

           若是一个函数 A() 在定义或调用过程当中出现了对另一个函数 B() 的调用,那么咱们就称 A() 为主调函数或主函数,称 B() 为被调函数。当主调函数遇到被调函数时,主调函数会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回主调函数,主调函数根据刚才的状态继续往下执行。一个C语言程序的执行过程能够认为是多个函数之间的相互调用过程,它们造成了一个或简单或复杂的调用链条。这个链条的起点是 main(),终点也是 main()。当 main() 调用完了全部的函数,它会返回一个值(例如return 0;)来结束本身的生命,从而结束整个程序。
          函数是一个能够重复使用的代码块,CPU 会一条一条地挨着执行其中的代码,当遇到函数调用时,CPU 首先要记录下当前代码块中下一条代码的地址(假设地址为 0X1000),而后跳转到另一个代码块,执行完毕后再回来继续执行 0X1000 处的代码。整个过程至关于 CPU 开了一个小差,暂时放下手中的工做去作点别的事情,作完了再继续刚才的工做。

7.6   C语言函数声明以及函数原型

            C语言代码由上到下依次执行,原则上函数定义要出如今函数调用以前,不然就会报错。但在实际开发中,常常会在函数定义以前使用它们,这个时候就须要提早声明。
           所谓声明(Declaration),就是告诉编译器我要使用这个函数,你如今没有找到它的定义没关系,请不要报错,稍后我会把定义补上。函数声明的格式很是简单,至关于去掉函数定义中的函数体,并在最后加上分号;   dataType  functionName( dataType1 param1, dataType2 param2 ... );

           也能够不写形参,只写数据类型:      dataType  functionName( dataType1, dataType2 ... );

           函数声明给出了函数名、返回值类型、参数列表(重点是参数类型)等与该函数有关的信息,称为函数原型(Function Prototype)。函数原型的做用是告诉编译器与该函数有关的信息,让编译器知道函数的存在,以及存在的形式,即便函数暂时没有定义,编译器也知道如何使用它。
          有了函数声明,函数定义就能够出如今任何地方了,甚至是其余文件、静态连接库、动态连接库

7.7  C语言全局变量和局部变量

              形参变量要等到函数被调用时才分配内存,调用结束后当即释放内存。这说明形参变量的做用域很是有限,只能在函数内部使用,离开该函数就无效了。所谓做用域(Scope),就是变量的有效范围。
              不只对于形参变量,C语言中全部的变量都有本身的做用域。决定变量做用域的是变量的定义位置。

7.7.1   局部变量

             定义在函数内部的变量称为局部变量(Local Variable),它的做用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错

             几点说明:
             1) 在 main 函数中定义的变量也是局部变量,只能在 main 函数中使用;同时,main 函数中也不能使用其它函数中定义的变量。main 函数也是一个函数,与其它函数地位平等。
            2) 形参变量、在函数体内定义的变量都是局部变量。实参给形参传值的过程也就是给局部变量赋值的过程。
            3) 能够在不一样的函数中使用相同的变量名,它们表示不一样的数据,分配不一样的内存,互不干扰,也不会发生混淆。
            4) 在语句块中也可定义变量,它的做用域只限于当前语句块。

7.7.2    全局变量

             在全部函数外部定义的变量称为全局变量(Global Variable),它的做用域默认是整个程序,也就是全部的源文件,包括 .c 和 .h 文件。

             当全局变量和局部变量同名时,在局部范围内全局变量被“屏蔽”,再也不起做用。或者说,变量的使用遵循就近原则,若是在当前做用域中存在同名变量,就不会向更大的做用域中去寻找变量。

7.8  C语言变量的做用域,加深对全局变量和局部变量的理解

                 所谓做用域(Scope),就是变量的有效范围,就是变量能够在哪一个范围之内使用。有些变量能够在全部代码文件中使用,有些变量只能在当前的文件中使用,有些变量只能在函数内部使用,有些变量只能在 for 循环内部使用。

                 变量的做用域由变量的定义位置决定,在不一样位置定义的变量,它的做用域是不同的。本节咱们只讲解两种变量,一种是只能在函数内部使用的变量,另外一种是能够在全部代码文件中使用的变量。

7.8.1 在函数内部定义的变量(局部变量)

                在函数内部定义的变量,它的做用域也仅限于函数内部,出了函数就不能使用了,咱们将这样的变量称为局部变量(Local Variable)。函数的形参也是局部变量,也只能在函数内部使用

              对局部变量的两点说明:

  • main() 也是一个函数,在 main() 内部定义的变量也是局部变量,只能在 main() 函数内部使用。
  • 形参也是局部变量,将实参传递给形参的过程,就是用实参给局部变量赋值的过程,它和a=b; sum=m+n;这样的赋值没有什么区别。

7.8.2  在全部函数外部定义的变量(全局变量)

            C语言容许在全部函数的外部定义变量,这样的变量称为全局变量(Global Variable)。
           全局变量的默认做用域是整个程序,也就是全部的代码文件,包括源文件(.c文件)和头文件(.h文件)。若是给全局变量加上 static关键字,它的做用域就变成了当前文件,在其它文件中就无效了。咱们目前编写的代码都是在一个源文件中,因此暂时不用考虑 static 关键字

7.9  忽略语法细节,从总体上理解函数

               全部的函数中,main() 是入口函数,有且只能有一个,C语言程序就是从这里开始运行的。

               C语言不但提供了丰富的库函数,还容许用户定义本身的函数。每一个函数都是一个能够重复使用的模块,经过模块间的相互调用,有条不紊地实现复杂的功能。能够说,C程序的所有工做都是由各式各样的函数完成的,函数就比如一个一个的零件,组合在一块儿构成一台强大的机器。

               标准C语言(ANSI C)共定义了15 个头文件,称为“C标准库”,全部的编译器都必须支持,如何正确并熟练的使用这些标准库,能够反映出一个程序员的水平。

  • 合格程序员:<stdio.h>、<ctype.h>、<stdlib.h>、<string.h>
  • 熟练程序员:<assert.h>、<limits.h>、<stddef.h>、<time.h>
  • 优秀程序员:<float.h>、<math.h>、<error.h>、<locale.h>、<setjmp.h>、<signal.h>、<stdarg.h>

                以上各种函数不只数量众多,并且有的还须要硬件知识才能使用,初学者要想所有掌握得须要一个较长的学习过程。个人建议是先掌握一些最基本、最经常使用的函数,在实践过程当中再逐步深刻。因为课时关系,本教程只介绍了不多一部分库函数,其他部分读者可根据须要查阅C语言函数手册,网址是 http://www.cplusplus.com

            还应该指出的是,C语言中全部的函数定义,包括主函数 main() 在内,都是平行的。也就是说,在一个函数的函数体内,不能再定义另外一个函数,即不能嵌套定义。可是函数之间容许相互调用,也容许嵌套调用。习惯上把调用者称为主调函数,被调用者称为被调函数。函数还能够本身调用本身,称为递归调用。

            main() 函数是主函数,它能够调用其它函数,而不容许被其它函数调用。所以,C程序的执行老是从 main() 函数开始,完成对其它函数的调用后再返回到 main() 函数,最后由 main() 函数结束整个程序。

八、  C语言预处理命令(宏定义和条件编译)

                 在编译和连接以前,还须要对源文件进行一些文本方面的操做,好比文本替换、文件包含、删除部分代码等,这个过程叫作预处理,由预处理程序完成。
                较之其余编程语言,C/C++ 语言更依赖预处理器,因此在阅读或开发 C/C++ 程序过程当中,可能会接触大量的预处理指令,好比 #include、#define 等。

8.1  C语言预处理命令是什么?

             前面各章中,已经屡次使用过#include命令。使用库函数以前,应该用#include引入对应的头文件。这种以#号开头的命令称为预处理命令。
            C语言源文件要通过编译、连接才能生成可执行程序:
           1) 编译(Compile)会将源文件(.c文件)转换为目标文件。对于 VC/VS,目标文件后缀为.obj;对于GCC,目标文件后缀为.o

编译是针对单个源文件的,一次编译操做只能编译一个源文件,若是程序中有多个源文件,就须要屡次编译操做。

           2) 连接(Link)是针对多个文件的,它会将编译生成的多个目标文件以及系统中的库、组件等合并成一个可执行程序。

           在实际开发中,有时候在编译以前还须要对源文件进行简单的处理。例如,咱们但愿本身的程序在 Windows 和 Linux 下都可以运行,那么就要在 Windows 下使用 VS 编译一遍,而后在 Linux 下使用 GCC 编译一遍。可是如今有个问题,程序中要实现的某个功能在 VS 和 GCC 下使用的函数不一样(假设 VS 下使用 a(),GCC 下使用 b()),VS 下的函数在 GCC 下不能编译经过,GCC 下的函数在 VS 下也不能编译经过,怎么办呢?

           这就须要在编译以前先对源文件进行处理:若是检测到是 VS,就保留 a() 删除 b();若是检测到是 GCC,就保留 b() 删除 a()。

           这些在编译以前对源文件进行简单加工的过程,就称为预处理(即预先处理、提早处理)。

           预处理主要是处理以#开头的命令,例如#include <stdio.h>。预处理命令要放在全部函数以外,并且通常都放在源文件的前面。

           预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分做处理,处理完毕自动进入对源程序的编译。

           编译器会将预处理的结果保存到和源文件同名的.i文件中,例如 main.c 的预处理结果在 main.i 中。和.c同样,.i也是文本文件,能够用编辑器打开直接查看内容。

           C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等,合理地使用它们会使编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

            #if、#elif、#endif 就是预处理命令,它们都是在编译以前由预处理程序来执行的

8.2  C语言#include的用法详解(文件包含命令)

             #include叫作文件包含命令,用来引入对应的头文件(.h文件)。#include 也是C语言预处理命令的一种。

             #include 的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件链接成一个源文件,这与复制粘贴的效果相同。

            #include 的用法有两种,以下所示:

           #include <stdHeader.h>
           #include "myHeader.h"

           使用尖括号< >和双引号" "的区别在于头文件的搜索路径不一样:

  • 使用尖括号< >,编译器会到系统路径下查找头文件;
  • 而使用双引号" ",编译器首先在当前目录下查找头文件,若是没有找到,再到系统路径下查找。

         也就是说,使用双引号比使用尖括号多了一个查找路径,它的功能更为强大

          前面咱们一直使用尖括号来引入标准头文件,如今咱们也能够使用双引号了,以下所示:

  1. #include "stdio.h"
  2. #include "stdlib.h"

            stdio.h 和 stdlib.h 都是标准头文件,它们存放于系统路径下,因此使用尖括号和双引号都可以成功引入;而咱们本身编写的头文件,通常存放于当前项目的路径下,因此不能使用尖括号,只能使用双引号。      

           固然,你也能够把当前项目所在的目录添加到系统路径,这样就能够使用尖括号了,可是通常没人这么作,纯粹画蛇添足,费力不讨好

          在之后的编程中,你们既能够使用尖括号来引入标准头文件,也能够使用双引号来引入标准头文件;不过,我我的的习惯是使用尖括号来引入标准头文件,使用双引号来引入自定义头文件(本身编写的头文件),这样一眼就能看出头文件的区别。

          关于 #include 用法的注意事项:

  • 一个 #include 命令只能包含一个头文件,多个头文件须要多个 #include 命令。
  • 同一个头文件能够被屡次引入,屡次引入的效果和一次引入的效果相同,由于头文件在代码层面有防止重复引入的机制
  • 文件包含容许嵌套,也就是说在一个被包含的文件中又能够包含另外一个文件。

        「在头文件中定义定义函数和全局变量」这种认知是原则性的错误!无论是标准头文件,仍是自定义头文件,都只能包含变量和函数的声明,不能包含定义,不然在屡次引入时会引发重复定义错误

8.3  C语言#define的用法,C语言宏定义

              #define 叫作宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,若是在后面的代码中出现了该标识符,那么就所有替换成指定的字符串。

              宏定义的通常形式为:   #define  宏名  字符串

#表示这是一条预处理命令,全部的预处理命令都以 # 开头。宏名是标识符的一种,命名规则和变量相同。字符串能够是数字、表达式、if 语句、函数等。这里所说的字符串是通常意义上的字符序列,不要和C语言中的字符串等同,它不须要双引号。

              对 #define 用法的几点说明

1) 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中能够含任何字符,它能够是常数、表达式、if 语句、函数等,预处理程序对它不做任何检查,若有错误,只能在编译已被宏展开后的源程序时发现。
2) 宏定义不是说明或语句,在行末没必要加分号,如加上分号则连分号也一块儿替换。
3) 宏定义必须写在函数以外,其做用域为宏定义命令起到源程序结束。如要终止其做用域可以使用#undef命令。                                                4) 代码中的宏名若是被引号包围,那么预处理程序不对其做宏代替                                                                                                                      5) 宏定义容许嵌套,在宏定义的字符串中能够使用已经定义的宏名,在宏展开时由预处理程序层层代换                                                          6) 习惯上宏名用大写字母表示,以便于与变量区别。但也容许用小写字母。
7) 可用宏定义表示数据类型,使书写方便

            应注意用宏定义表示数据类型和用 typedef 定义数听说明符的区别。宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并非简单的字符串替换,而给原有的数据类型起一个新的名字,将它做为一种新的数据类型。

           由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟只是简单的字符串替换。在使用时要格外当心,以避出错。

8.4 、C语言带参数的宏定义

             C语言容许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些相似。

             对带参数的宏,在展开过程当中不只要进行字符串替换,还要用实参去替换形参。

            带参宏定义的通常形式为:          #define 宏名(形参列表) 字符串

            在字符串中能够含有各个形参。带参宏调用的通常形式为:   宏名(实参列表);

             例如:

#define M(y) y*y+3*y  //宏定义
// TODO:
k=M(5);  //宏调用

              对带参宏定义的说明:

             1) 带参宏定义中,形参之间能够出现空格,可是宏名和形参列表之间不能有空格出现

              例如把:#define MAX(a,b) (a>b)?a:b  写为:#define MAX (a,b) (a>b)?a:b    将被认为是无参宏定义,宏名 MAX 表明字符串(a,b) (a>b)?a:b。宏展开时,宏调用语句:max = MAX(x,y);将变为:max = (a,b)(a>b)?a:b(x,y);

             2) 在带参宏定义中,不会为形式参数分配内存,所以没必要指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,所以实参必需要指明数据类型。

              这一点和函数是不一样的:在函数中,形参和实参是两个不一样的变量,都有本身的做用域,调用时要把实参的值传递给形参;而在带参数的宏中,只是符号的替换,不存在值传递的问题。

             3) 在宏定义中,字符串内的形参一般要用括号括起来以免出错。例如上面的宏定义中 (y)*(y) 表达式的 y 都用括号括起来,所以结果是正确的。

8.5  C语言带参宏定义和函数的区别

               带参数的宏和函数很类似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译以前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段能够重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。

8.6  C语言#if、##ifdef、#ifndef的用法详解,C语言条件编译详解

             假如如今要开发一个C语言程序,让它输出红色的文字,而且要求跨平台,在 Windows 和 Linux 下都能运行,怎么办呢?这个程序的难点在于,不一样平台下控制文字颜色的代码不同,咱们必需要可以识别出不一样的平台。

            Windows 有专有的宏_WIN32,Linux 有专有的宏__linux__

#include <stdio.h>
int main(){
    if(_WIN32){
        system("color 0c");
        printf("http://c.biancheng.net\n");
    }else if(__linux__){
        printf("\033[22;31mhttp://c.biancheng.net\n\033[22;30m");
    }else{
        printf("http://c.biancheng.net\n");
    }
    return 0;
}

           这段代码是错误的,在 Windows 下提示 __linux__ 是未定义的标识符,在 Linux 下提示 _Win32 是未定义的标识符。对上面的代码进行改进:

#include <stdio.h>
int main(){
    #if _WIN32
        system("color 0c");
        printf("http://c.biancheng.net\n");
    #elif __linux__
        printf("\033[22;31mhttp://c.biancheng.net\n\033[22;30m");
    #else
        printf("http://c.biancheng.net\n");
    #endif
    return 0;
}

                 #if、#elif、#else 和 #endif 都是预处理命令,整段代码的意思是:若是宏 _WIN32 的值为真,就保留第 四、5 行代码,删除第 七、9 行代码;若是宏 __linux__ 的值为真,就保留第 7 行代码;若是全部的宏都为假,就保留第 9 行代码。
                这些操做都是在预处理阶段完成的,多余的代码以及全部的宏都不会参与编译,不只保证了代码的正确性,还减少了编译后文件的体积。
              这种可以根据不一样状况编译不一样代码、产生不一样目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能。
              条件编译须要多个预处理命令的支持,下面一一讲解。

8.6.1    #if 的用法

               #if 用法的通常格式为:

#if 整型常量表达式1
    程序段1
#elif 整型常量表达式2
    程序段2
#elif 整型常量表达式3
    程序段3
#else
    程序段4
#endif

               它的意思是:如常“表达式1”的值为真(非0),就对“程序段1”进行编译,不然就计算“表达式2”,结果为真的话就对“程序段2”进行编译,为假的话就继续往下匹配,直到遇到值为真的表达式,或者遇到 #else。这一点和 if else 很是相似。

               须要注意的是,#if 命令要求判断条件为“整型常量表达式”,也就是说,表达式中不能包含变量,并且结果必须是整数;而 if 后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别。

                #elif 和 #else 也能够省略,以下所示:

#include <stdio.h>
int main(){
#if _WIN32
printf("This is Windows!\n");
#else
printf("Unknown platform!\n");
#endif

#if __linux__
printf("This is Linux!\n");
#endif

return 0;
}

8.6.2  #ifdef 的用法

             #ifdef 用法的通常格式为:

#ifdef  宏名
    程序段1
#else
    程序段2
#endif

          它的意思是,若是当前的宏已被定义过,则对“程序段1”进行编译,不然对“程序段2”进行编译。也能够省略 #else:

#ifdef  宏名
    程序段
#endif

              VS/VC 有两种编译模式,Debug 和 Release。在学习过程当中,咱们一般使用 Debug 模式,这样便于程序的调试;而最终发布的程序,要使用 Release 模式,这样编译器会进行不少优化,提升程序运行效率,删除冗余信息

              为了可以清楚地看到当前程序的编译模式,咱们不妨在程序中增长提示,请看下面的代码:

#include <stdio.h>
#include <stdlib.h>
int main(){
#ifdef _DEBUG
printf("正在使用 Debug 模式编译程序...\n");
#else
printf("正在使用 Release 模式编译程序...\n");
#endif

system("pause");
return 0;
}

                   当以 Debug 模式编译程序时,宏 _DEBUG 会被定义,预处器会保留第 5 行代码,删除第 7 行代码。反之会删除第 5 行,保留第 7 行。

8.6.3 #ifndef 的用法

                   #ifndef 用法的通常格式为:

#ifndef 宏名
    程序段1 
#else 
    程序段2 
#endif

               与 #ifdef 相比,仅仅是将 #ifdef 改成了 #ifndef。它的意思是,若是当前的宏未被定义,则对“程序段1”进行编译,不然对“程序段2”进行编译,这与 #ifdef 的功能正好相反。

8.6.4 三者之间的区别

              最后须要注意的是,#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其余的。

              #ifdef 能够认为是 #if defined 的缩写。

8.7  C语言预处理命令总结

             预处理指令是以#号开头的代码行,# 号必须是该行除了任何空白字符外的第一个字符。# 后是指令关键字,在关键字和 # 号之间容许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编译以前对源代码作某些转换。
            下面是本章涉及到的部分预处理指令:

              

             预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的,程序员在程序中用预处理命令来调用这些功能。

             宏定义能够带有参数,宏调用时是以实参代换形参,而不是“值传送”。

             为了不宏代换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。

             文件包含是预处理的一个重要功能,它可用来把多个源文件链接成一个源文件进行编译,结果将生成一个目标文件。

             条件编译容许只编译源程序中知足条件的程序段,使生成的目标程序较短,从而减小了内存的开销并提升了程序的效率。

             使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。

九、C语言指针详解

9.1  C语言指针是什么?

          计算机中全部的数据都必须放在内存中,不一样类型的数据占用的字节数不同,例如 int 占用 4 个字节,char 占用 1 个字节。为了正确地访问这些数据,必须为每一个字节都编上号码,就像门牌号、身份证号同样,每一个字节的编号是惟一的,根据编号能够准确地找到某个字节 

           

             咱们将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从 0 开始依次增长,对于 32 位环境,程序可以使用的内存为 4GB,最小的地址为 0,最大的地址为 0XFFFFFFFF。

             一切都是地址

           C语言用变量来存储数据,用函数来定义一段能够重复使用的代码,它们最终都要放到内存中才能供 CPU 使用。

          数据和代码都以二进制的形式存储在内存中,计算机没法从格式上区分某块内存到底存储的是数据仍是代码。当程序被加载到内存后,操做系统会给不一样的内存块指定不一样的权限,拥有读取和执行权限的内存块就是代码,而拥有读取和写入权限(也可能只有读取权限)的内存块就是数据。

           CPU 只能经过地址来取得内存中的代码和数据,程序在执行过程当中会告知 CPU 要执行的代码以及要读写的数据的地址。若是程序不当心出错,或者开发者有意为之,在 CPU 要写入数据时给它一个代码区域的地址,就会发生内存访问错误。这种内存访问错误会被硬件和操做系统拦截,强制程序崩溃,程序员没有挽救的机会。

           CPU 访问内存时须要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和连接成可执行程序后,它们都会被替换成地址。编译和连接过程的一项重要任务就是找到这些名称所对应的地址。

            假设变量 a、b、c 在内存中的地址分别是 0X1000、0X2000、0X3000,那么加法运算c = a + b;将会被转换成相似下面的形式:

            0X3000 = (0X1000) + (0X2000);

( )表示取值操做,整个表达式的意思是,取出地址 0X1000 和 0X2000 上的值,将它们相加,把相加的结果赋值给地址为 0X3000 的内存
变量名和函数名为咱们提供了方便,让咱们在编写代码的过程当中能够使用易于阅读和理解的英文字符串,不用直接面对二进制地址,那场景简直让人崩溃。

             须要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是同样的,它们都是地址的助记符,但在编写代码的过程当中,咱们认为变量名表示的是数据自己,而函数名、字符串名和数组名表示的是代码块或数据块的首地址。

9.2 C语言指针变量的定义和使用(精华)

              数据在内存中的地址也称为指针,若是一个变量存储了一份数据的指针,咱们就称它为指针变量

              在C语言中,容许用一个变量来存放指针,这种变量称为指针变量。指针变量的值就是某份数据的地址,这样的一份数据能够是数组、字符串、函数,也能够是另外的一个普通变量或指针变量。

               如今假设有一个 char 类型的变量 c,它存储了字符 'K'(ASCII码为十进制数 75),并占用了地址为 0X11A 的内存(地址一般用十六进制表示)。另外有一个指针变量 p,它的值为 0X11A,正好等于变量 c 的地址,这种状况咱们就称 p 指向了 c,或者说 p 是指向变量 c 的指针。

                

9.2.1 定义指针变量

               定义指针变量与定义普通变量很是相似,不过要在变量名前面加星号*,格式为:datatype *name; 或者   datatype *name = value;

*表示这是一个指针变量,datatype表示该指针变量所指向的数据的类型 。例如: int *p1;  p1 是一个指向 int 类型数据的指针变量,至于 p1 究竟指向哪一份数据,应该由赋予它的值决定。再如:

  1. int a = 100;
  2. int *p_a = &a;

            在定义指针变量 p_a 的同时对它进行初始化,并将变量 a 的地址赋予它,此时 p_a 就指向了 a。值得注意的是,p_a 须要的一个地址,a 前面必需要加取地址符&,不然是不对的。

             和普通变量同样,指针变量也能够被屡次写入,只要你想,随时都可以改变指针变量的值,

             *是一个特殊符号,代表一个变量是指针变量,定义 p一、p2 时必须带*。而给 p一、p2 赋值时,由于已经知道了它是一个指针变量,就不必画蛇添足再带上*,后边能够像使用普通变量同样来使用指针变量。也就是说,定义指针变量时必须带*,给指针变量赋值时不能带*

9.2.2 经过指针变量取得数据

               指针变量存储了数据的地址,经过指针变量可以得到该地址上的数据,格式为:   *pointer;   这里的*称为指针运算符,用来取得某个地址上的数据

               上节咱们说过,CPU 读写数据必需要知道数据在内存中的地址,普通变量和指针变量都是地址的助记符,虽然经过 *p 和 a 获取到的数据同样,但它们的运行过程稍有不一样:a 只须要一次运算就可以取得数据,而 *p 要通过两次运算,多了一层“间接”。

                 假设变量 a、p 的地址分别为 0X1000、0XF0A0,它们的指向关系以下图所示:

                

                 程序被编译和连接后,a、p 被替换成相应的地址。使用 *p 的话,要先经过地址 0XF0A0 取得变量 p 自己的值,这个值是变量 a 的地址,而后再经过这个值取得变量 a 的数据,先后共有两次运算;而使用 a 的话,能够经过地址 0X1000 直接取得它的数据,只须要一步运算。

             也就是说,使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。指针除了能够获取内存上的数据,也能够修改内存上的数据,

               *在不一样的场景下有不一样的做用:*能够用在指针变量的定义中,代表这是一个指针变量,以和普通变量区分开;使用指针变量时在前面加*表示获取指针指向的数据,或者说表示的是指针指向的数据自己。

                也就是说,定义指针变量时的*和使用指针变量时的*意义彻底不一样

9.2.3 关于 * 和 & 的谜题

           假设有一个 int 类型的变量 a,pa 是指向它的指针,那么*&a&*pa分别是什么意思呢?
      *&a能够理解为*(&a)&a表示取变量 a 的地址(等价于 pa),*(&a)表示取这个地址上的数据(等价于 *pa),绕来绕去,又回到了原点,*&a仍然等价于 a。
      &*pa能够理解为&(*pa)*pa表示取得 pa 指向的数据(等价于 a),&(*pa)表示数据的地址(等价于 &a),因此&*pa等价于 pa。

9.2.4 对星号*的总结

在咱们目前所学到的语法中,星号*主要有三种用途:

  • 表示乘法,例如int a = 3, b = 5, c;  c = a * b;,这是最容易理解的。
  • 表示定义一个指针变量,以和普通变量区分开,例如int a = 100;  int *p = &a;
  • 表示获取指针指向的数据,是一种间接操做,例如int a, b, *p = &a;  *p = 100;  b = *p;

十、C语言结构体详解

              C语言结构体(Struct)从本质上讲是一种自定义的数据类型,只不过这种数据类型比较复杂,是由 int、char、float 等基本类型组成的。你能够认为结构体是一种聚合类型。

            在实际开发中,咱们能够将一组类型不一样的、可是用来描述同一件事物的变量放到结构体中。例如,在校学生有姓名、年龄、身高、成绩等属性,学告终构体后,咱们就不须要再定义多个变量了,将它们都放到结构体中便可。

            此外,本章还讲解了与位操做有关的知识点,好比位域、位运算等。

10.1 C语言结构体详解,C语言struct用法详解

               前面的教程中咱们讲解了数组(Array),它是一组具备相同类型的数据的集合。但在实际的编程过程当中,咱们每每还须要一组类型不一样的数据,例如对于学生信息登记表,姓名为字符串,学号为整数,年龄为整数,所在的学习小组为字符,成绩为小数,由于数据类型不一样,显然不能用一个数组来存放。

               在C语言中,能够使用结构体(Struct)来存放一组不一样类型的数据。结构体的定义形式为:

struct 结构体名{
    结构体所包含的变量或数组
};

                结构体是一种集合,它里面包含了多个变量或数组,它们的类型能够相同,也能够不一样,每一个这样的变量或数组都称为结构体的成员(Member);

                结构体也是一种数据类型,它由程序员本身定义,能够包含多个其余类型的数据。

                像 int、float、char 等是由C语言自己提供的数据类型,不能再进行分拆,咱们称之为基本数据类型;而结构体能够包含多个基本类型的数据,也能够包含其余的结构体,咱们将它称为复杂数据类型或构造数据类型。

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
};

               stu 为结构体名,它包含了 5 个成员,分别是 name、num、age、group、score。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。

10.1.1 结构体变量

                 既然结构体是一种数据类型,那么就能够用它来定义变量        struct stu stu1, stu2;       定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。注意关键字struct不能少。

                 stu 就像一个“模板”,定义出来的变量都具备相同的性质。也能够将结构体比做“图纸”,将结构体变量比做“零件”,根据同一张图纸生产出来的零件的特性都是同样的。

                 你也能够在定义结构体的同时定义结构体变量:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在学习小组
    float score;  //成绩
} stu1, stu2;

               将变量放在结构体定义的最后便可。

             若是只须要 stu一、stu2 两个变量,后面不须要再使用结构体名定义其余变量,那么在定义时也能够不给出结构体名,以下所示:

struct{ //没有写 stu
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
} stu1, stu2;

               这样作书写简单,可是由于没有结构体名,后面就无法用该结构体定义新的变量。

理论上讲结构体的各个成员在内存中是连续存储的,和数组很是相似,例如上面的结构体变量 stu一、stu2 的内存分布以下图所示,共占用 4+4+4+1+4 = 17 个字节

                   

                可是在编译器的具体实现中,各个成员之间可能会存在缝隙,对于 stu一、stu2,成员变量 group 和 score 之间就存在 3 个字节的空白填充(见下图)。这样算来,stu一、stu2 其实占用了 17 + 3 = 20 个字节。

                  

10.1.2  成员的获取和赋值

               结构体和数组相似,也是一组数据的集合,总体使用没有太大的意义。数组使用下标[ ]获取单个元素,结构体使用点号.获取单个成员。获取结构体成员的通常格式为:  结构体变量名.成员名;         经过这种方式能够获取成员的值,也能够给成员赋值:

               不过总体赋值仅限于定义结构体变量的时候,在使用过程当中只能对成员逐一赋值,这和数组的赋值很是相似。

10.2  C语言结构体数组详解

             所谓结构体数组,是指数组中的每一个元素都是一个结构体。在实际应用中,C语言结构体数组常被用来表示一个拥有相同数据结构的群体,好比一个班的学生、一个车间的职工等。

             在C语言中,定义结构体数组和定义结构体变量的方式相似,请看下面的例子:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组 
    float score;  //成绩
}class[5];

                表示一个班级有5个学生。

结构体数组在定义的同时也能够初始化,例如:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组 
    float score;  //成绩
}class[5] = {
    {"Li ping", 5, 18, 'C', 145.0},
    {"Zhang ping", 4, 19, 'A', 130.5},
    {"He fang", 1, 18, 'A', 148.5},
    {"Cheng ling", 2, 17, 'F', 139.0},
    {"Wang ming", 3, 17, 'B', 144.5}
};

                    当对数组中所有元素赋值时,也可不给出数组长度,例如:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组 
    float score;  //成绩
}class[] = {
    {"Li ping", 5, 18, 'C', 145.0},
    {"Zhang ping", 4, 19, 'A', 130.5},
    {"He fang", 1, 18, 'A', 148.5},
    {"Cheng ling", 2, 17, 'F', 139.0},
    {"Wang ming", 3, 17, 'B', 144.5}
};

10.3 C语言结构体指针(指向结构体的指针)详解

                  当一个指针变量指向结构体时,咱们就称它为结构体指针C语言结构体指针的定义形式通常为:  struct 结构体名 *变量名;

下面是一个定义结构体指针的实例:

//结构体
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
} stu1 = { "Tom", 12, 18, 'A', 136.5 };
//结构体指针
struct stu *pstu = &stu1;

                也能够在定义结构体的同时定义结构体指针:

struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    char group;  //所在小组
    float score;  //成绩
} stu1 = { "Tom", 12, 18, 'A', 136.5 }, *pstu = &stu1;

                  注意,结构体变量名和数组名不一样,数组名在表达式中会被转换为数组指针,而结构体变量名不会,不管在任何表达式中它表示的都是整个集合自己,要想取得结构体变量的地址,必须在前面加&

                 还应该注意,结构体和结构体变量是两个不一样的概念:结构体是一种数据类型,是一种建立变量的模板,编译器不会为它分配内存空间,就像 int、float、char 这些关键字自己不占用内存同样;结构体变量才包含实实在在的数据,才须要内存来存储。不可能去取一个结构体名的地址,也不能将它赋值给其余变量:

                 结构体指针做为函数参数

               结构体变量名表明的是整个集合自己,做为函数参数时传递的整个集合,也就是全部成员,而不是像数组同样被编译器转换成一个指针。若是结构体成员较多,尤为是成员为数组时,传送的时间和空间开销会很大,影响程序的运行效率。因此最好的办法就是使用结构体指针,这时由实参传向形参的只是一个地址,很是快速。

10.4  C语言枚举类型(C语言enum用法)详解

                在实际编程中,有些数据的取值每每是有限的,只能是很是少许的整数,而且最好为每一个值都取一个名字,以方便在后续代码中使用,好比一个星期只有七天,一年只有十二个月,一个班每周有六门课程等。

                C语言提供了一种枚举(Enum)类型,可以列出全部可能的取值,并给它们取一个名字。枚举类型的定义形式为:

enum typeName{ valueName1, valueName2, valueName3, ...... };

         enum是一个新的关键字,专门用来定义枚举类型,这也是它在C语言中的惟一用途;typeName是枚举类型的名字;valueName1, valueName2, valueName3, ......是每一个值对应的名字的列表。注意最后的;不能少。

               例如,列出一个星期有几天:enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Su

               能够看到,咱们仅仅给出了名字,却没有给出名字对应的值,这是由于枚举值默认从 0 开始,日后逐个加 1(递增);也就是说,week 中的 Mon、Tues ...... Sun 对应的值分别为 0、1 ...... 6。

               咱们也能够给每一个名字都指定一个值:enum week{ Mon = 1, Tues = 2, Wed = 3, Thurs = 4, Fri = 5, Sat = 6, Sun = 7 };

               更为简单的方法是只给第一个名字指定值:enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };   这样枚举值就从 1 开始递增,跟上面的写法是等效的。 枚举是一种类型,经过它能够定义枚举变量: enum week a, b, c;

              也能够在定义枚举类型的同时定义变量: enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a, b, c;

有了枚举变量,就能够把列表中的值赋给它:

  1. enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
  2. enum week a = Mon, b = Wed, c = Sat;
  3. enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a = Mon, b = Wed, c = Sat;

须要注意的两点是:

1) 枚举列表中的 Mon、Tues、Wed 这些标识符的做用范围是全局的(严格来讲是 main() 函数内部),不能再定义与它们名字相同的变量。2) Mon、Tues、Wed 等都是常量,不能对它们赋值,只能将它们的值赋给其余的变量。

          枚举和宏其实很是相似:宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值。咱们能够将枚举理解为编译阶段的宏

#include <stdio.h>
int main(){
    enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day;
    scanf("%d", &day);
    switch(day){
        case Mon: puts("Monday"); break;
        case Tues: puts("Tuesday"); break;
        case Wed: puts("Wednesday"); break;
        case Thurs: puts("Thursday"); break;
        case Fri: puts("Friday"); break;
        case Sat: puts("Saturday"); break;
        case Sun: puts("Sunday"); break;
        default: puts("Error!");
    }
    return 0;
}

                对于上面的代码,在编译的某个时刻会变成相似下面的样子:

#include <stdio.h>
int main(){
    enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day;
    scanf("%d", &day);
    switch(day){
        case 1: puts("Monday"); break;
        case 2: puts("Tuesday"); break;
        case 3: puts("Wednesday"); break;
        case 4: puts("Thursday"); break;
        case 5: puts("Friday"); break;
        case 6: puts("Saturday"); break;
        case 7: puts("Sunday"); break;
        default: puts("Error!");
    }
    return 0;
}

               Mon、Tues、Wed 这些名字都被替换成了对应的数字。这意味着,Mon、Tues、Wed 等都不是变量,它们不占用数据区(常量区、全局数据区、栈区和堆区)的内存,而是直接被编译到命令里面,放到代码区,因此不能用&取得它们的地址。这就是枚举的本质。

 

10.5  C语言共用体(C语言union用法)详解

            经过前面的讲解,咱们知道结构体(Struct)是一种构造类型或复杂类型,它能够包含多个类型不一样的成员。在C语言中,还有另一种和结构体很是相似的语法,叫作共用体(Union),它的定义格式为:

union 共用体名{
    成员列表
};

              共用体有时也被称为联合或者联合体,这也是 Union 这个单词的本意。

             结构体和共用体的区别在于:结构体的各个成员会占用不一样的内存,互相之间没有影响;而共用体的全部成员占用同一段内存,修改一个成员会影响其他全部成员

             结构体占用的内存大于等于全部成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,若是对新的成员赋值,就会把原来成员的值覆盖掉。

             共用体也是一种自定义类型,能够经过它来建立变量,例如:

union data{
    int n;
    char ch;
    double f;
};
union data a, b, c;

              上面是先定义共用体,再建立变量,也能够在定义共用体的同时建立变量:

union data{
    int n;
    char ch;
    double f;
} a, b, c;

             若是再也不定义新的变量,也能够将共用体的名字省略:

union{
    int n;
    char ch;
    double f;
} a, b, c;

                    共用体 data 中,成员 f 占用的内存最多,为 8 个字节,因此 data 类型的变量(也就是 a、b、c)也占用 8 个字节的内存,

union data{
    int n;
    char ch;
    short m;
};

以上面的 data 为例,各个成员在内存中的分布以下:

                 

                成员 n、ch、m 在内存中“对齐”到一头,对 ch 赋值修改的是前一个字节,对 m 赋值修改的是前两个字节,对 n 赋值修改的是所有字节。也就是说,ch、m 会影响到 n 的一部分数据,而 n 会影响到 ch、m 的所有数据。

10.6  C语言位域(位段)详解

                 有些数据在存储时并不须要占用一个完整的字节,只须要占用一个或几个二进制位便可。例如开关只有通电和断电两种状态,用 0 和 1 表示足以,也就是用一个二进位。正是基于这种考虑,C语言又提供了一种叫作位域的数据结构。

                 在结构体定义时,咱们能够指定某个成员变量所占用的二进制位数(Bit),这就是位域

struct bs{
    unsigned m;
    unsigned n: 4;
    unsigned char ch: 6;
}

               :后面的数字用来限定成员变量占用的位数。成员 m 没有限制,根据数据类型便可推算出它占用 4 个字节(Byte)的内存。成员 n、ch 被:后面的数字限制,不能再根据数据类型计算长度,它们分别占用 四、6 位(Bit)的内存。n、ch 的取值范围很是有限,数据稍微大些就会发生溢出

10.7  C语言位运算(按位与运算、或运算、异或运算、左移运算、右移运算)

                    所谓位运算,就是对一个比特(Bit)位进行操做。比特(Bit)是一个电子元器件,8个比特构成一个字节(Byte),它已是粒度最小的可操做单元了。

                   C语言提供了六种位运算符:

                   

10.7.1 按位与运算(&)

            一个比特(Bit)位只有 0 和 1 两个取值,只有参与&运算的两个位都为 1 时,结果才为 1,不然为 0。例如1&1为 1,0&0为 0,1&0也为 0,这和逻辑运算符&&很是相似。

             C语言中不能直接使用二进制,&两边的操做数能够是十进制、八进制、十六进制,它们在内存中最终都是以二进制形式存储,&就是对这些内存中的二进制位进行运算。其余的位运算符也是相同的道理。

             例如,9 & 5能够转换成以下的运算:

           

          也就是说,按位与运算会对参与运算的两个数的全部二进制位进行&运算,9 & 5的结果为 1

          又如,-9 & 5能够转换成以下的运算:

           

                再强调一遍,&是根据内存中的二进制位进行运算的,而不是数据的二进制形式;其余位运算符也同样。以-9&5为例,-9 的在内存中的存储和 -9 的二进制形式大相径庭:

                 1111 1111 -- 1111 1111 -- 1111 1111 -- 1111 0111  (-9 在内存中的存储)
               -0000 0000 -- 0000 0000 -- 0000 0000 -- 0000 1001  (-9 的二进制形式,前面多余的 0 能够抹掉)

10.7.2  按位或运算(|)

           参与|运算的两个二进制位有一个为 1 时,结果就为 1,两个都为 0 时结果才为 0。例如1|1为1,0|0为0,1|0为1,这和逻辑运算中的||很是相似

10.7.3  按位异或运算(^)

          参与^运算两个二进制位不一样时,结果为 1,相同时结果为 0。例如0^1为1,0^0为0,1^1为0。

10.7.4  取反运算(~)

          取反运算符~为单目运算符,右结合性,做用是对参与运算的二进制位取反。例如~1为0,~0为1,这和逻辑运算中的!很是相似。。

10.7.5  左移运算(<<)

          左移运算符<<用来把操做数的各个二进制位所有左移若干位,高位丢弃,低位补0。

10.7.6 右移运算(>>)

          右移运算符>>用来把操做数的各个二进制位所有右移若干位,低位丢弃,高位补 0 或 1。若是数据的最高位是 0,那么就补 0;若是最高位是 1,那么就补 1。

十一、 C语言重要知识点补充

11.1   C语言typedef的用法详解

                C语言容许为一个数据类型起一个新的别名,就像给人起“绰号”同样。起别名的目的不是为了提升程序运行效率,而是为了编码方便。例若有一个结构体的名字是 stu,要想定义一个结构体变量就得这样写:struct stu stu1;

                struct 看起来就是多余的,但不写又会报错。若是为 struct stu 起了一个别名 STU,书写起来就简单了:STU stu1;

                这种写法更加简练,意义也很是明确,无论是在标准头文件中仍是之后的编程实践中,都会大量使用这种别名。使用关键字 typedef 能够为类型起一个新的别名。typedef 的用法通常为:  typedef  oldName  newName;

11.2  C语言const的用法详解,C语言常量定义详解

               有时候咱们但愿定义这样一种变量,它的值不能被改变,在整个做用域中都保持固定。例如,用一个变量来表示班级的最大人数,或者表示缓冲区的大小。为了知足这一要求,能够使用const关键字对变量加以限定:

               咱们常常将 const 变量称为常量(Constant)。建立常量的格式一般为:     const type name = value;   const 和 type 都是用来修饰变量的,它们的位置能够互换,也就是将 type 放在 const 前面:但咱们一般采用第一种方式,不采用第二种方式。另外建议将常量名的首字母大写,以提醒程序员这是个常量。

               因为常量一旦被建立后其值就不能再改变,因此常量必须在定义的同时赋值(初始化),后面的任何赋值行为都将引起错误。

              const 和指针

              const 也能够和指针变量一块儿使用,这样能够限制指针变量自己,也能够限制指针指向的数据。const 和指针一块儿使用会有几种不一样的顺序,以下所示:

const int *p1;
int const *p2;
int * const p3;

              在最后一种状况下,指针是只读的,也就是 p3 自己的值不能被修改;在前面两种状况下,指针所指向的数据是只读的,也就是 p一、p2 自己的值能够修改(指向不一样的数据),但它们指向的数据不能被修改。

             固然,指针自己和它指向的数据都有多是只读的,下面的两种写法可以作到这一点:

const int * const p4;
int const * const p5;

                const 离变量名近就是用来修饰指针变量的,离变量名远就是用来修饰指针指向的数据,若是近的和远的都有,那么就同时修饰指针变量以及它指向的数据。

              const 和函数形参

            在C语言中,单独定义 const 变量没有明显的优点,彻底能够使用#define命令代替。const 一般用在函数形参中,若是形参是一个指针,为了防止在函数内部修改指针指向的数据,就能够用 const 来限制。

            在C语言标准库中,有不少函数的形参都被 const 限制了,下面是部分函数的原型:

size_t strlen ( const char * str );
int strcmp ( const char * str1, const char * str2 );
char * strcat ( char * destination, const char * source );
char * strcpy ( char * destination, const char * source );
int system (const char* command);
int puts ( const char * str );
int printf ( const char * format, ... );

              咱们本身在定义函数时也能够使用 const 对形参加以限制

              const 和非 const 类型转换

            当一个指针变量 str1 被 const 限制时,而且相似const char *str1这种形式,说明指针指向的数据不能被修改;若是将 str1 赋值给另一个未被 const 修饰的指针变量 str2,就有可能发生危险。由于经过 str1 不能修改数据,而赋值后经过 str2 可以修改数据了,意义发生了转变,因此编译器不提倡这种行为,会给出错误或警告。

           也就是说,const char *char *是不一样的类型,不能将const char *类型的数据赋值给char *类型的变量。但反过来是能够的,编译器容许将char *类型的数据赋值给const char *类型的变量。

            这种限制很容易理解,char *指向的数据有读取和写入权限,而const char *指向的数据只有读取权限,下降数据的权限不会带来任何问题,但提高数据的权限就有可能发生危险。

          C语言标准库中不少函数的参数都被 const 限制了,但咱们在之前的编码过程当中并无注意这个问题,常常将非 const 类型的数据传递给 const 类型的形参,这样作从未引起任何反作用,缘由就是上面讲到的,将非 const 类型转换为 const 类型是容许的

11.3  C语言随机数生成教程,C语言rand和srand用法详解

          在实际编程中,咱们常常须要生成随机数,例如,贪吃蛇游戏中在随机的位置出现食物,扑克牌游戏中随机发牌。在C语言中,咱们通常使用 <stdlib.h> 头文件中的 rand() 函数来生成随机数,它的用法为:     int rand (void);      void 表示不须要传递参数。

          C语言中还有一个 random() 函数能够获取随机数,可是 random() 不是标准函数,不能在 VC/VS 等编译器经过,因此比较少用。

          rand() 会随机生成一个位于 0 ~ RAND_MAX 之间的整数

          RAND_MAX 是 <stdlib.h> 头文件中的一个宏,它用来指明 rand() 所能返回的随机数的最大值。C语言标准并无规定 RAND_MAX 的具体数值,只是规定它的值至少为 32767。在实际编程中,咱们也不须要知道 RAND_MAX 的具体值,把它当作一个很大的数来对待便可。

          随机数的本质

#include <stdio.h>
#include <stdlib.h>
int main(){
    int a = rand();
    printf("%d\n",a);
    return 0;
}

                  屡次运行上面的代码,你会发现每次产生的随机数都同样,这是怎么回事呢?为何随机数并不随机呢?实际上,rand() 函数产生的随机数是伪随机数,是根据一个数值按照某个公式推算出来的,这个数值咱们称之为“种子”。种子和随机数之间的关系是一种正态分布,以下图所示:

                               

              种子在每次启动计算机时是随机的,可是一旦计算机启动之后它就再也不变化了;也就是说,每次启动计算机之后,种子就是定值了,因此根据公式推算出来的结果(也就是生成的随机数)就是固定的。

             从新播种

             咱们能够经过 srand() 函数来从新“播种”,这样种子就会发生改变。srand() 的用法为:   void srand (unsigned int seed);  它须要一个 unsigned int 类型的参数。在实际开发中,咱们能够用时间做为参数,只要每次播种的时间不一样,那么生成的种子就不一样,最终的随机数也就不一样。

           使用 <time.h> 头文件中的 time() 函数便可获得当前的时间(精确到秒),就像下面这样: srand((unsigned)time(NULL));

           生成必定范围内的随机数

           在实际开发中,咱们每每须要必定范围内的随机数,过大或者太小都不符合要求,那么,如何产生必定范围的随机数呢?咱们能够利用取模的方法:  int a = rand() % 10; //产生0~9的随机数,注意10会被整除

           若是要规定上下限:     int a = rand() % 51 + 13; //产生13~63的随机数

          分析:取模即取余,rand()%51+13咱们能够当作两部分:rand()%51是产生 0~50 的随机数,后面+13保证 a 最小只能是 13,最大就是 50+13=63。

           连续生成随机数

           有时候咱们须要一组随机数(多个随机数),该怎么生成呢?很容易想到的一种解决方案是使用循环,每次循环都从新播种,

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
    int a, i;
    //使用for循环生成10个随机数
    for (i = 0; i < 10; i++) {
        srand((unsigned)time(NULL));
        a = rand();
        printf("%d ", a);
    }
    return 0;
}

           运行结果很是奇怪,每次循环咱们都从新播种了呀,为何生成的随机数都同样呢?这是由于,for 循环运行速度很是快,在一秒以内就运行完成了,而 time() 函数获得的时间只能精确到秒,因此每次循环获得的时间都是同样的,这样一来,种子也就是同样的,随机数也就同样了。