C 语言笔记

  1. C语言 之 概述与入门
  2. C语言 之 变量和运算符
  3. C语言 之 控制结构
  4. C语言 之 函数
  5. C语言 之 测试与调试技术
  6. C语言 之 指针和数组
  7. C语言 之 递归
  8. C语言 之 C语言中的
  9. C语言 之 数据结构

概述及入门

1. 计算机语言

程序就是一组计算机能识别和执行的指令. 每一条指令使计算机指定行特定的操作.

计算机的本质是程序的机器. 程序和指令是计算机系统中最基本的概念.

计算机语言的发展阶段

  1. 机器语言

    0 和 1

    一条计算机指令的长度为 16, 即以 16 个二进制数(0或1) 组成一条指令.

  2. 符号语言(汇编语言或符号汇编语言)

    符号语言由英文字母和数字组成, 一般一条符号语言的指令对应转换为一条机器指令.
    人们通过汇编程序符号语言指令转换为机器指令.

    不同型号的计算机的机器语言和汇编语言是互不通用的.

    机器语言和汇编语言是完全依赖于具体机器特性的, 是面向机器的语言.

    符号语言 –汇编–> 机器指令

  3. 高级语言

    源程序(高级语言程序) – 编译 –> 目标程序(机器指令的程序)

    高级语言更接近于人们习惯使用的自然语言和数学语言. 且不依赖于具体机器.

    高级语言的一个语言往往对应多条机器指令.

    高级语言的发展阶段:

    • 非结构化语言

      编程风格比较随意, 只要符合语法规则即可, 没有严格的规范要求, 程序中的流程可以随意跳转.

    • 结构化语言

      程序必须具有良好特性的基本结构(顺序,分支,循环)构成, 程序中的流程不能随意跳转, 程序总是自上而下执行各个基本结构.

    • 面向对象语言

      处理规模较大问题.

2. C 语言的发展及其特点

37 个关键字, 9 种控制语句,

34 种运算符 : C语言把括号, 赋值和强制类型转换等都作为运算符处理.

数据类型: 整型, 浮点型, 字符型, 数组, 指针, 结构体, 共用体. C 99 又扩充了 复数浮点类型, 超长整型, 布尔类型. 指针类型能用来实现各种复杂的数据结构, 如链表, 树, 栈等.

C 语言允许直接访问物理地址, 能进行位(bit)操作, 能实现汇编语言的大部分功能, 可以直接对硬件进行操作.

3. C 语言程序结构

3.1 一个程序由一个或多个源程序文件组成

一个源文件包含如下三个部分.

  • 预处理指令

    C 编译系统在对源程序进行”翻译”之前, 先由一个预处理器(也成为预处理程序, 预编译器)对预处理指令进行预处理.

    对于 #include <stdio.h> 来说, 就是将stdio.h头文件的内容读出来, 放在 #include 行处, 取代 #include <stdio.h>.

    由预处理得到的结果与程序其他部分一起, 组成一个完整的, 可以用来编译的最后的源程序, 然后由编译程序对该源程序进行编译, 得到目标程序.

    #include 有两种格式:

    1. #include <stdin.h> : 表示从存放 C 编译系统的子目录中去找索要包含的文件, 称为标准方式
    2. $include "stdin.h" : 在编译时, 先在用户的当前目录寻找, 如果未找到, 则按标准方式查找. 如果头文件不在当前目录中, 可以使用#include "/path/to/stdin.h" 格式.
  • 全局声明: 即在函数外进行的数据声明.

    在函数声明的变量称为全局变量, 在整个源文件内有效.
    在函数声明的变量称为局部变量, 在整个函数内有效.

  • 函数定义

    每个函数用于完成一个特定的功能, 在调用这些函数时, 会完成函数中指定的功能.

3.2 函数是 C 语言的主要组成部分

C 程序的几乎全部工作都由各个函数分别完成, 函数是 C 程序的基本单位.

一个 C 程序由一个或多个函数组成, 其中必须包含一个且只能有一个 main 函数.

一个源程序文件就是一个程序模块.

在进行编译时, 是以源程序文件为对象进行的, 在分别对各个源程序文件进行编译并得到相应的目标程序后,再将这些目标程序连接成一个统一的二进制程序.

在程序中被调用的函数有 3 类:

  1. 系统提供的库函数
  2. 用户编写函数
  3. 编译系统提供的专门的函数.

    不同编译系统所提供的库函数个数和功能不完全相同.

函数组成部分:

  1. 函数首部

    函数第一行, 包括 函数名, 函数类型, 函数参数(形式参数)名, 参数类型, 函数属性.

    int      max   (int           x,        int y)
    函数类型 函数名 函数参数类型 函数参数名
    

    一个函数名后面必须跟一对圆括号, 括号内写参数名及其类型. 如果函数没有参数, 可以在括号内写 void 或者 直接使用空括号.

    int main(void)
    int main()
    
  2. 函数体

    函数首部花括号内的部分. 如果在一个函数中包括有多层花括号, 则最外层的一对花括号是函数体的范围.

    函数体一般包括如下两部分:

    • 声明部分

      1. 局部变量声明
      2. 调用函数声明
    • 执行部分

      由若干个语句组成, 指定在函数中所进行的操作.

    • 在某些情况下, 可以 没有声明部分, 甚至可以既没有声明部分, 也没有执行部分.

      // 如下为一个 空函数, 什么也不做, 但这是合法的.
      void dump()
      {}
      

3.3 程序总是从 main 函数开始执行, 而无论 main 函数在整个函数中的位置如何.

3.4 在每个数据声明和语句的最后必须有一个分号.

3.5 C 语言本身并不提供输入输出语句.

输入输出操作有库函数 scanf 和 printf 等函数来完成.

由于输入输出操作涉及具体的计算机设备, 把输入和输出操作用库函数操作实现, 可使得 C 语言本身短小精悍, 编译程序简单, 且程序具有可移植性.

3.6 程序应当包含注释, 以增强程序的可读性.

4. C 语言程序示例

示例1 : 打印语句

#include <stdio.h>          // 编译预处理指令
int main()                  // 定义主函数
{                           // 函数开始标志
    printf("This is a C program.\n");   // 输出信息
    return 0;                           // 函数执行完毕时, 返回函数值 0
}                           // 函数结束标志

每一个 C语言程序都必须有一个 main 函数, 函数体有花括号{}括起来.

main 是函数的名字, 表示主函数, main 前面的 int 表示此函数的类型是 int 类型, 在执行主函数后会得到一个值(即函数值), 此值纪委整型. 程序中, return 9; 的作用是: 当main函数执行结束前将整数 0 作为函数值, 返回到调用函数处.

每个语句都由一个最后都有一个分号;, 表示语句结束.

在使用函数库中的输入输出函数时, 编译系统要求程序提供有关此函数的信息(如对函数的声明和宏定义,全局量的定义等), 即#include <stdio.h>, 文件后缀.h 表示头文件(header file), 应为这些文件放在文件模块的开头.

注释: 注释可以用汉语或英语表示.

  • // : 单行注释,
  • /* ... ... */ : 多行注释.

在程序进行预编译处理时, 将每个注释替换为一个空格, 因此在编译时注释部分不产生目标代码, 注释对运行不起作用.

示例2 : 函数调用

程序第 5 行, 是对被调用函数(max)的声明, 因为 max 函数的定义在 main 函数之后, 在对程序进行编译时, 编译系统无法得知 max 为何物, 因而无法把它作为函数调用处理. 函数声明 即告诉编译系统 max 是什么, 及 max 的相关信息.

#include <stdio.h>
// 主函数
int main()
{
    int max(int x, int y); 
    int a, b, c;
    scanf("%d, %d", &a, &b);
    c=max(a, b);
    printf("max=%d\n", c);
    return 0;
}

// 求两个整数中较大者的 max 函数.
int max(int x, int y)
{
    int z;
    if(x>y)
        z=x;
    else 
        z=y;

    return(z);
}

scanf(A, B) 是 C 标准库中的输入函数的名字,用于接受用户输入.

  • A 参数指定输入格式,
  • B 参数指定输入的数据赋给那个变量.
  • & 是地址符, &a 表示 变量 a 的地址.

5. 运行 C 程序的步骤及方法

源程序 —[预编译/正式编译]—> 目标程序 —[连接]—> 库函数/其他目标程序 —> 可执行二进制程序

$ gcc test.c -o test
$ ./test

6. 程序 = 数据结构 + 算法

数据结构 : 对数据的描述, 在程序中用到了那些数据, 这些数据的类型和数据的组织形式.

算法 : 对操作的描述, 即要求计算机进行操作的步骤.

广义的说, 为解决一个问题而采取的方法和步骤就是算法.

计算机算法分为两类:

  1. 数值运算算法
    目的是求数值解.

    数值运算算法, 往往有现成的模型, 可以运用数值分析方法.

  2. 非数值运算算法
    包括非常广, 常见的是用于事务管理领域.
    目前, 计算机在非数值运算方面的应用远远超过 数值运算算法的运用.

    非数值运算算法种类繁多, 要求各异, 只有一些典型的非数值运算算法(如排序算法, 查找搜索算法等).

实际上, 一个过程化的程序除了数据结构和算法之外, 还应当采用结构化的程序设计方法进行程序设计, 并且用一种计算机语言表示.

7. 结构化程序设计方法

采用以下方法来保证得到结构化的程序:

  1. 自顶向下
  2. 逐步细化
  3. 模块化设计
  4. 结构化编码

变量,运算符,表达式和语句

1 数据

数据

1.1 常量

在程序运行过程中, 其值不能改变的量称为常量.

整型常量

如 1000, 123, 0 , -123 等.

实型常量

  • 十进制小数形式 : 由数字和小数点组成.
  • 指数形式: 以 e 或 E 表示以 10 为底的指数.

    e 或 E 之前必须有数字, 且 e 或 E 之后必须为整数.

字符常量

  • 普通字符

    单撇号括起来的一个字符. 如 ‘z’, ‘A’, ‘3’, ‘?’ 等. 单撇号只是界限符, 字符常量只能是一个字符, 不包括单撇号.

    字符常量存储在计算机存储单元中时, 并不是存储字符本身, 而是一起代码(一般为 ASCII)存储的.

  • 转义字符

    以字符 \ 开头的字符序列.

    转义字符及其作用

    | 转义字符 | 字符值 | 输出结果 |
    | — | — | — |
    | \' | 一个单撇号 | 具有此八进制码的字符 |
    | \" | 一个双撇号 | 输出此字符 |
    | \? | 一个问号 | 输出此字符 |
    | \\ | 一个反斜线 | 输出此字符 |
    | \a | 警告(alert) | 产生声音或视觉信号 |
    | \b | 退格(backspace) | 将当前位置后退一个字符 |
    | \f | 换页(form feed) | 将当前位置移到下一页的开头 |
    | \n | 换行 | 将当前位置移到下一行的开头 |
    | \r | 回车(carriage return) | 将当前位置移到本行的开头 |
    | \t | 水平制表符 | 将当前位置移到下一个 tab 位置 |
    | \v | 垂直制表符 | 将当前位置移到下一个垂直指标对齐点 |
    | \o\oo\ooo, 其中 o 代表一个八进制数字 | 与该八进制码对应的 ASCII 字符 | 与该八进制码对应的字符, 如 \101 代表字母 A. |
    | \xh[h...] 其中 h 代表一个十六进制数字 | 与该十六进制码对应的 ASCII 字符 | 与该十六进制码对应的字符, 如 \x41 代表字母 A |

字符串常量

双撇号把若干个字符括起来, 字符串常量是双撇号中的全部字符(但不包括双撇号本身).

单撇号只能包含一个字符, 双撇号可以包含一个字符串.

符号常量

#define指令, 指定用一个符号名称代表一个常量, 在源代码预编译后, 符号常量被全部替换为字面常量. 如下示例:

#define PI 3.1416   // 注意行末没有分号

要注意区别符号常量变量: 符号常量不占内存, 只是一个临时符号, 在预编译后这个符号就不存在了, 故不能对符号常量赋予新值.

为与变量名相区别, 习惯上符号常量用大写表示, 如 PI, PRICE 等.

1.2 变量

变量代表一个有名字的, 具有特定属性的一个存储单元. 他用来存放数据, 也就是存放变量的值. 在程序运行期间, 变量的值是可以改变的.

变量必须先定义, 后使用. 在定义时指定变量的名字和类型, 一个变量应该有一个名字, 以便被引用.

区分变量名与变量值:

- 变量名: 变量名实际上是一个名字代表的一个存储地址. 

    在对程序编译连接时, 由编译系统给每一个变量名分配对应的内存地址. 

    从变量中取值, 实际上时通过变量名找到相应的内存地址, 从该存储单元中读取数据.

- 变量值: 变量的实际值.

1.3. 常变量

C 99 允许使用常变量, 如:

const int a = 3;    // 表示 a 被定义为一个整型变量, 其指定值为 3, 且在变量存在期间, 其值不能改变.

常变量与常量的异同:

常变量具有常量的基本属性: 有类型, 占存储单元, 只是不允许改变其值.

常变量是有名字的不变量; 有名字方便在程序中调用.
常量是没有名字的不变量.

常变量与符号变量的异同:

- 符号常量: 

    定义符号常量用 '#define' 指令, 他是预编译指定, 他只是用符号常量代表一个字符串, 在预编译时仅是进行字符替换, 在预编译后, 符号常量就不存在了, 对符号常量的名字是不分配存储单元的.

- 常变量: 

    常变量占用存储单元, 有变量值, 只是改值不改变而已.

- 从使用的角度, 常变量具有符号变量的有点, 而且使用方便. 有了常变量之后, 可以不必多用符号常量.

1.4. 标识符

在计算机高级语言中, 用来对变量, 符号常量名, 函数, 数组, 类型等命名的有效字符序列统称为标识符. 标识符就是一个对象的名字.

// 标识符举例
变量名 : p1, p2, p3 
符号常量名 : PI, PRICE
函数名 : printf, scanf

C 语言规定标识符只能有字母, 数字, 下划线 3 种字符组成, 且第一个字符必须为字母或下划线. 标识符是大小写敏感的.

2 数据类型

数据类型

C 语言要求在定义所有的变量时都要指定变量的类型. 常量也是区分类型的.

数据类型产生的原因:

数学是一门研究抽象的学科, 数和数的运算都是抽象的. 而在计算机中, 数据是存放在存储单元中的, 他是具体存在. 而且, 存储单元是以优先的字节构成的, 每一个存储单元中存放数据的范围是有限的, 不可能存放无穷大的数, 也不能存放无限循环小数.

用计算机进行的计算不是抽象的计算, 而是用工程的方法实现的计算, 在许多情况下, 只能得到近似的结果.

所谓类型, 就是对数据分配存储单元的安排, 包括存储单元的长度(占用多少字节)以及数据的存储形式. 不同的类型分配不同的长度和存储形式.

C语言数据类型-带星号的为C99所新加

2.1 基本类型

整型类型

整型数据常见的存储空间和值得范围

  • 基本整型

    编译系统分配给 int 型数据 2 个或 4 个字节(由具体的C编译系统自定决定).

    存储方式: 用整数的补码形式存放.

    • 正数: 一个正数的补码是此数的二进制形式.
    • 复数: 先将此数的绝对值写成二进制形式, 然后对其后面所有各二进制按位取反, 再加1.

      在存放整数的存储单元中, 最左面一位是用来表示符号的, 如果该位为 0 , 表示数值为正; 如果该位为 1 , 表示数值为负.

  • 短整型

    类型名为 short intshort.

    一个短整型变量的值得范围是 -32768 ~ 32767

  • 长整型

    类型名为 long intlong.

    编译系统分配给 长整型 4 个字节.

  • 双长整型

    类型名为 long long intlong long. 一般分配 8 个字节.

  • 有符号整数与无符号整数

    为充分利用变量的值得范围, 可以将变量定义为无符号类型.
    在类型符号前面加上修饰符unsigned 表示指定该变量是无符号整数类型.
    在类型符号前面加上修饰符signed 表示该变量是有符号类型. 此类型为默认类型, 即当一个正数既没有指定为 signed 也没有指定为 unsigned, 则默认为 有符号类型.

因此, 以上四种数据类型可以扩展为 8 种数据类型:

| 名称 | 类型符号 |
| --- | --- |
| 有符号基本类型 | [signed] int |
| 无符号基本类型 | unsigned int |
| 有符号短整型   | [signed] short [int] |
| 无符号短整型   | unsigned short [int] |
| 有符号长整型   | [signed] long [int] |
| 无符号长整型   | unsigned long [int] |
| 有符号双长整型 | [signed] long long [int] |
| 无符号双长整型 | unsigned long long [int] |

**注意**:
1. 只有整型(包括字符型)数据可以加 signed 或 unsigned 修饰符, 实型数据不能加.
2. 对无符号整型数据用`%u` 格式输出, 表示无符号十进制数.
3. 将一个变量定义为无符号整形后, 不应向他赋予一个负值, 否则会得到一个错误的结果.
  • 字符型

    由于字符是按其代码(整数)形式存储的, 因此 C 99 把字符型数据作为整数类型的一种.

    各种字符集(包含 ASCII) 的基本集都包含 127 个字符. 在 C 中, 指定用一个字节存储一个字符, 此时字节的第一位置为 0 .

    字符变量使用 char 定义, 实质上是一个字节的整型变量. 可以把 0~127 之间的整数赋值给一个字符变量.

    char c='?'      // c 是字符变量, 
    printf("%d %c\n", c, c)     // 输出字符变量的值时, 可以十进制整数形式输出, 也可以字符形式输出.
    // 输出: 63 ?
    

    字符型数据的存储空间和值范围:

    | 类型 | 字节数 | 取值范围 |
    | — | — | — |
    | signed char | 1 | -128~127 |
    | unsigned char | 1 | 0~255 |

    如果把一个负整数赋给有符号字符型变量时合法的, 但他不代表一个字符, 而作为一个字节整型变量存储负整数.

    如果在定义变量时即不加 signed 也不加 unsigned, C 标准并未规定如何处理, 由各个编译系统自己决定, 可用如下方法做测试:

    char c=255;
    printf("%d\n", c)
    
  • 布尔型

浮点类型

浮点型数据用来表示具有小数点的实数. 在 C 语言中, 实数是以指数形式存放在存储单元中的.

规范化的指数形式: 把小数部分中小数点前的数字为 0, 小数点后第一位不为 0 的表示形式. 一个实数只有一个规范化的指数形式, 在程序以指数形式输出时, 必然以规范化的指数形式输出.

  • 单精度浮点型(float)

    编译系统为每一个 float 型变量分配 4 个字节, 数值以规范化的二进制指数形式存放在存储单元中.

    在存储时, 系统将实型数据分成小数部分和指数部分两部分, 以二进制形式分别存放.

    由于用二进制形式表示一个实数以及存储单元的长度是有限的, 因此不可能得到完全精确的值, 只能存储成优先的精确度:

    • 小数部分占的位(bit)数越多, 数的有效数字越多, 精度也就越高;
    • 指数部分占的位(bit)数越多, 表示的数值的范围就越大.

      float 可以得到 6 位有效数字.

  • 双精度浮点型(double)

    用 8 个字节存储一个 double 型数据, 可以得到 15 位有效数字.

    在 C 语言中进行浮点数的算术运算时, 将 float 型数据都自动转换为 double 型, 然后进行运算.

  • 长双精度浮点型(long double)

    不同的编译系统对 long double 型的处理方法不同, Turbo C 分配 16 字节, Visual C++ 6.0 分配 8 字节.

2.2 枚举类型

2.3 空类型

2.4 派生类型

指针类型

数组类型

结构体类型

共用体类型

函数类型

3 运算符

运算符
C 语言的运算符范围很宽, 把除了控制语句和输入输出意外的几乎所有操作都作为运算符处理.

3.1 算术运算符

基本的算术运算符

基本算术运算符

自增自减运算符

作用是使变量的值加 1 或 减 1.

++i, --i : 在使用之, 先使 i 的值加(减)1.
i++, i-- : 在使用之, 使 i 的值加(减)1.

自增运算符(++) 和 自减运算符(–) 只能用于变量, 不能用于常量或表达式.

自增(减)运算符常用与循环语句中, 使循环变量自动加 1; 也用于指针变量, 使指针指向下一个地址.

混合运算

如果一个运算符的两侧的数据类型不同, 则先自动进行类型转换, 使二者具有同一种类型, 然后进行运算.因此, 整型,实型, 字符型数据间可以进行混合运算. 规律如下:

  1. +,-,*,/ 运算的两个数中有一个数为 float 或 double 型, 结果是 double 型, 因为系统将所有 float 型数据都先转换为 double 型, 然后进行运算.

  2. 如果 int 型与 float 或 double 型数据进行运算, 先把 int 型 和 float 型数据转换为 double 型, 然后进行运算, 结果是 double 型.

  3. 字符型数据与整型数据进行运算, 就是把字符的 ASCII 代码与整型数据进行运算. 如果字符型数据与实型数据进行运算, 则将字符的 ASCII 代码转换为 double 型数据, 然后进行运算.

以上转换, 由编译系统自动完成, 用户无需干预.

强制类型转换.

可以利用强制类型转换运算符将一个表达式转换成所需类型.其格式为:

(类型名)(表达式)

在强制类型转换时, 得到一个所需类型的中间数据, 而原来的变量的类型保持不变.

(int)(x+y)  // 将 x+y 的结果转换为 int 类型
(int)x+y    // 将 x 转换为 int 类型, 然后与 y 做加法.

复合的赋值运算符

在赋值符=之前加上其他运算符, 可以构成复合的运算符.凡是二元运算符, 都可以与赋值符一起组合成复合赋值符.

a+=b    // 如果 b 为一个表达式, 则相当于他有括号
a=a+b 
x*=y+8  <--> x=x*(y+8)

赋值过程中的强制类型转换

如果赋值运算符两侧的类型不一致, 但都是算术类型时, 在赋值时要进行类型转换. 类型转换由系统自动完成, 规则如下: 赋值给谁, 以谁为准.

  • 将浮点型数据赋值给 整型变量时, 先对浮点数取整, 然后赋值给整型变量;
  • 将整型数据赋值给 浮点型数据时, 数值不变, 但以浮点数形式存储到变量中.
  • 将一个 double 型数据赋值给 float 型数据, 先将双精度转换为单精度, 存储到 float 变量的 4 个字节中. 要注意 双精度数值的大小不能超过 float 型变量的数值范围.
  • 字符型数据赋给整型变量时, 将字符的 ASCII 代码赋给整型变量.
  • 将一个占字节多的整型数据赋给一个占字节少的整型变量或字符变量时, 只将其低字节原封不同的赋值给变量, 即发生截断.

3.2 关系运算符(>,<,==,>=,<=,!=)

关系运算符与优先级:

运算符 含义 优先级
< 小于 优先级高
<= 小于等于 优先级高
> 大于 优先级高
>= 大于等于 优先级高
== 等于 优先级低
!= 不等于 优先级低

注意:

  1. 前四种(<,<=,>,>=)关系运算符的优先级别相同, 后两种(==,!=)也相同. 前四种优先级高于后两种..
  2. 关系运算符的优先级低于算术运算符.
  3. 关系运算符的优先级高于赋值运算符.

示例:

c > a + b  --> c > (a+b)
a > b == c --> (a>b)==c
a==b<c     --> a==(b<c)
a=b>c      --> a=(b>c)

3.3 逻辑运算符(!, &&, ||)

逻辑运算符与优先级

运算符 含义 举例 说明
&& 逻辑与 a && b 如果 a 和 b 都为真, 则结果为真; 否则为假
` ` 逻辑或 `a b` 如果 a 和 b 至少有一个为真, 则结果为真; 否则为假
! 逻辑非 !a 如果 a 为假, 则 !a 为真; 反之, 为假.

优先级:

!(非)  >  算术运算符 > 关系运算符 > && 和 || > 赋值运算符

C 语言编译系统在表示逻辑运算结果时, 以数值 1 表示 真, 数值 0 代表 假; 但在判断一个量是否为真时, 以 0 为假, 非0 为真.

实际上, 逻辑运算符两侧的运算对象不但可以是 0 和 1, 或者 0 或 非0 的整数, 也可以是字符型, 浮点型, 枚举型或指针型的纯量型数据. 系统最终以 0 或 非0 来判断他们属于 真或假.

逻辑表达式在判断时, 执行短路逻辑. (m=a>b) && (n=c>d)a>b 为假时, m=0, 则此时 && 表达式必定为假, 则 (n=c>d) 将不会执行, 即 n 的值保持不变.

3.4 位运算符(<<,>>,~,|,^,&)

3.6 赋值运算符(=, 及其扩展运算符)

3.7 条件运算符(?:) –> C 中唯一的 三目运算符

条件表达式的一般形式:

表达式_1 ? 表达式_2 : 表达式_3; 
// 如果 表达式_1 为真, 则执行 表达式_2, 否则执行 表达式_3.

示例:

max=(a>b) ? a:b;    // 求 a,b 的最大值.

// 输入一个字母, 判断是否为大写字母, 如果是, 将其转换为小写字母, 如果不是, 不转换.

#include <stdio.h>

int main()
{
    char ch;
    scanf("%c", &ch);

    ch=(ch>='A' && ch<='Z') ? (ch + 32) : ch;
    printf("%c\n", ch);
    return 0;
}    

3.8 逗号运算符(,)

3.9 指针运算符(*, &)

3.10 求字节数运算符(sizeof)

3.11 强制类型转换运算符((TYPE))

3.12 成员运算符(.,-,>)

3.13 下标运算符([])

4. C 语句和表达式

表达式和语句

4.1 控制语句

条件

  • if() ... else ...
  • switch

循环

  • for() ...
  • while() ...
  • do ... while ()
  • continue
  • break

return

goto

4.2 函数调用语句

由一个函数调用 + 一个分号组成.

printf("Just for test.");

4.3 表达式语句

由一个表达式 + 一个分号组成.

a=3     // 表达式
a=3;    // 表达式语句
i++;    // 表达式语句
x+y;    // 表达式语句, 只是没有赋值, 没有实际意义

其实函数调用语句, 也是一种表达式语句, 因为函数调用也是表达式的一种.

赋值语句

输入输出语句

putchar(输出字符)

输出单个字符:

putchar(c)  // c 可以是字符常量, 整型常量, 字符变量或整型变量(在ASCII代码范围内)

示例:

putchar(c)  // 输出变量 c
putchar('\101')     // 输出字符 A
putchar('\'')       // 输出单撇号
putchar('\o15')     // 八进制15, 输出回车
getchar(输入字符)

向计算机输入一个字符:

getchar()   // 没有参数, 只能接受一个字符.
printf(格式输出)

printf格式字符

printf 函数的一般格式为:

printf(格式控制, 输出列表)

格式控制 = 格式声明 + 普通字符串
格式声明 = "%" + 格式字符

输出列表是程序需要输出的一些数据, 可以是变量, 常量 或 表达式.

格式控制和输出列表实质上是 printf 函数的参数.

示例:

printf("%5d\n%5d\n", 12, -345);
   12  // 12 前面 3 个空格
 -345  // -345 前面 1 个空格


printf("%ld")   // 表示 long 型数据
printf("%lld")   // 表示 long long 型数据

printf("%5c", "a") 
    a  // a 前面 4 个空格
printf("%5c", 121)  // 会自动执行 ASCII 转换.

printf("%s", "China")   // 输出字符串

printf("%f")     // 输出实数
printf("%m.nf")  // m 数据宽度, n 小数位数.
printf("%-m.nf") // 数据输出左对齐

printf("%e")    // 指数格式输出
printf("%E")    // 指数格式输出

printf("%o")    // 输出八进制格式
printf("%x")    // 输出十六进制格式

printf("%%")    // 输出百分号(%)
scanf(格式输入)

scanf格式字符

一般格式:

scanf(格式控制, 地址表列)

地址表列是由若干个地址组成的表列, 可以是变量的地址, 或字符串的首地址.

注意问题:

  1. scanf 函数中的地址表列, 应当是变量地址, 而不是变量名.

    scanf(“%f%f%f”, a, b, c) // 错误
    scanf(“%f%f%f”, &a, &b, &c) // 正确

  2. 如果在格式控制字符串中, 除了格式声明之外, 还有其他字符, 则在输入数据时, 在对应的位置上应输入与这些字符相同的字符.

    scanf(“a=%f, b=%f”, &a, &b) // 应当输入 “a=1, b=3”

  3. 在使用 %c 格式声明输入字符时, 空格字符和转义字符中的字符都作为有效字符输入.

  4. 在输入数值数据时, 如输入空格, 回车, Tab 键, 或遇非法字符(不属于数值的字符), 认为该数据结束.
puts(输出字符串)
gets(输入字符串)

4.4 空语句

空语句什么也不做, 主要用来作为流程的转向点(流程从程序其他地方转到此语句处), 也可以作为循环语句中的循环体(循环体是空语句, 表示循环体什么也不做).

;   // 空语句

4.5 复合语句

{}把一些语句和声明括起来称为复合语句(又称语句块). 复合语句常用在 if 语句或循环中.

可以在复合语句中包含声明部分, 声明部分可以放在复合语句中的任何位置, 但习惯上把它放在语句块开头的位置.

复合语句中最后一个语句中最后的分号不能省略.

{
    float pi=3.14159, r=2.5m, area;
    area = pi * r * r;
    printf("area=%f", area);
}

函数

控制结构

1. 顺序

语句自上而下依次执行.

2. 选择

2.1 if 选择

if 语句的一般形式:

// 一般形式
if(表达式) 语句_1
    [ else 语句_2 ]

// 常见形式
if(表达式) 语句_1

if(表达式)
    语句_1
else
    语句_2

if(表达式_1) 语句_1
else if(表达式_2) 语句_2
else if(表达式_3) 语句_3
...
else 语句_n

注意:

  1. if语句中的表达式可以为关系表达式, 逻辑表达式, 甚至是数值表达式.
  2. 语句_1, 语句_2 … 是 if 语句的内嵌语句, 每个内嵌语句末尾都应当有分号结尾.
  3. 语句_1, 语句_2 … 可以是单个语句, 也可以是复合语句, 复合语句应当用花括号括起来.

实例代码:

#include <stdio.h>
// 输入三个数字, 按从小到大输出.
int main()
{
    float a,b,c,t;

    scanf("%f,%f,%f", &a, &b, &c);

    if(a>b)
    {
        t=a;
        a=b;
        b=t;
    }

    if(a>c)
    {
        t=a;
        a=c;
        c=t;
    }

    if(b>c){
        t=b;
        b=c;
        c=t;
    }

    printf("%5.2f,%5.2f,%5.2f\n", a,b,c);
    return 0;
}

2.2 switch 选择

switch 语句是多分支语句. 其一般表达式如下:

switch(表达式)
{
    case 常量_1 : 语句_1 ; break;
    case 常量_1 : 语句_1 ; break;
    ...     ...
    default: 语句_n;
}

switch 后面括号内的”表达式”, 其值的类型应为整数类型(包括字符型);

default 标号是可选的, 如果没有雨 switch 表达式相匹配的 case 常量, 则不执行任何语句;

各个 case 标号(包含 default)出现的次序不影响执行结果.

一个 switch 选择分支结构中, 最多只能执行一个标号语句;

多个 case 标号, 可以共用一组执行语句, 如:

case 'a':
case 'A': action(a); break;
...

每个 case 后面的的语句中, 最后都有一个 break 语句, 他的作用是使流程转到 switch 语句的末尾.

#include <stdio.h>

int main()
{
    char grade;
    scanf("%c", &grade);
    printf("Your score:");

    switch(grade)
    {
        case 'A': 
            printf("85 ~ 100\n"); 
            break;
        case 'B': 
            printf("70 ~ 84\n");
            break;
        case 'C': 
            printf("60 ~ 69\n");
            break;
        case 'D': 
            printf("< 60\n");
            break;
        default: printf("enter data error!\n");
    }

    return 0;
}

3. 循环

3.1 for 循环

一般形式:

for(循环变量赋初值; 循环条件; 循环变量增值)
    语句

说明:

  1. 循环变量赋初值, 只执行一次, 可以为零个, 一个或多个变量设置初值.
  2. 循环条件表达式, 用来判断是否继续循环, 在每次执行循环体之前, 先执行此表达式做判断.
  3. 作为循环的调整, 是在每次执行完循环体之后执行.
  4. for 语句形式与下列 while 语句无条件等价

    循环变量赋初值;
    while 循环条件
    {
        语句;
        循环变量增值;
    }
    
  5. 循环变量赋初值可以省略, 但其后的分号不能省略.

    for(;i<100;i++)

  6. 循环条件也可以省略, 即不设置循环条件, 此时循环无终止进行, 即认为循环条件始终为真.

    for(i=1; ; i++)

  7. 循环变量增值, 也可省略, 但此时应另外设法保证循环能正常结束, 否则, 循环体无止境执行.

    for(i=1;i<100;)

  8. 循环变量赋初值,循环条件,循环变量增值 三个可同时省略, 此时, 循环无终止运行.

    for(;;)

  9. 循环变量赋初值,循环变量增值 可以是一个简单的表达式, 也可以是一个逗号表达式, 即包含一个以上的简单表达式, 中间用逗号分隔. 在逗号表达式内, 按自左至右顺序求解, 整个逗号表达式的值为最右边的表达式的值.

    for(i=1;i<=100;i++,i++)

  10. C99 允许在 for 语句的循环变量赋初值中定义变量并赋初值. 但所定义变量的有效范围仅限于for循环中, 在循环外不能使用此变量.

    for(int i=1;i<100;i++)

3.2 while 循环

while 语句的一般形式: 只要循环条件表达式为真, 就执行循环体.

// 循环条件表达式控制循环体, 他的值只能为真或假.
while(循环条件表达式) 循环体

程序示例: 求 1 ~ 100 之和

#include <stdio.h>

int main()
{
    int i=1, sum=0;
    while(i<=100)
    {
        sum = sum + i;
        i++ ;
    }

    printf("sum=%d\n", sum);
    return 0;
}

3.3 do … while 循环

一般形式: 先无条件执行循环体, 然后判断循环条件是否成立. 即对于 do ... while 循环来说, 至少执行一次循环体.

do
    循环体
while(循环条件)

示例: 求 1 ~ 100 之和

#include <stdio.h>

int main()
{
    int i=1, sum=0;

    do
    {
        sum = sum + i;
        i++;
    }while(i<=100);

    printf("sum=%d\n", sum);
    return 0;
}

whiledo ... while 比较:

  1. 当 while 表达式的第一次的值为真时, 两个循环得到的结果相同, 否则不同.
  2. whiledo ... while 都需要在循环体中实现循环条件控制变量的增减.

3.4 continue & break

  1. break 用来从循环体中跳出循环体, 即提前结束整个循环, 接着执行循环后面的语句.

    break只能用于 循环语句 和 switch 语句之中, 而不能单独使用.

  2. continue 提前结束本次循环 , 而接着执行下次循环.

3.5 实例:

斐波那契数列前 40 个数

方法一 :
#include <stdio.h>

int main()
{
    int f1=1, f2=1, f3;
    int i;
    printf("\t%12d\n\t%12d\n", f1, f2);
    for(i=1;i<=38;i++)      // 40 = 38 + 2
    {
        f3 = f1 + f2;
        printf("%d\t%12d\n", i, f3);
        f1 = f2;
        f2 = f3;
    }

    return 0;
}
方法二:
#include <stdio.h>

int main()
{
    int f1=1, f2=1;
    int i;
    for(i=1;i<=20;i++)  // 每次输入两个数, 只需 20 次循环
    {
        printf("%12d%12d",f1,f2 );
        if(i%2==0) printf("\n");
        f1 = f1 + f2;      // 此时 f1 为 第三个数
        f2 = f2 + f1;      // f2 是 第二个数(f2) + 第三个数(f1) 的和, 即第四个数.
    }
}

输入一个大于 3 的数, 判断是否为素数(质数)

方法一:
#include <stdio.h>

int main()
{
    int f, i;
    int fl=0;
    scanf("%d", &f);
    for(i=2; i<f; i++)
    {
        if(f%i==0) 
        {
            fl=1;
            break;
        }
    }
    if(fl==0) printf("%d is prime\n", f);
    else printf("%d is not prime\n", f);

    return 0;
}
方法二:
#include <stdio.h>

int main()
{
    int n,i;
    printf("Plz input a integer number, n\n");
    scanf("%d", &n);

    for(i=2;i<=n;i++)
    {
        if(n%i==0) break;   // 当 n 为非质数, 会提前退出.

        // 当 n 为质数, 会一直循环到 n-1 而不退出.
        if(i==n-1) printf("%d is a prime\n", n);
    }
    return 0;
}
方法三: 输入任意数字, 求 小于该数的所有质数
#include <stdio.h>

int main()
{
    int f;
    int i;
    int num;
    int fl=0;

    scanf("%d", &num);

    printf("Begin:\n");
    for(i=3;i<=num;i++)
    {

        for(f=2;f<i;f++)
        {
            if(i%f==0)
            {
                fl=1;
                break;
            }
        }
        if(fl==0) printf("\t%d\n", i);
        fl=0;
    }

    return 0;
}

指针和数组

一. 指针

二. 数组

1. 数组含义

  1. 数组是一组有序数据的集合, 数组中的各数据的排列是有顺序的, 下标代表数据在数组中的序号.

  2. 用一个数组名下标来唯一的确定数组中的元素. 在 C 语言中, 下标使用方括号中的数字表示.

  3. 数组中的每一个元素都属于同一种数据类型, 不能把不同类型的数据放在同一个数组中.

2. 一维数组

2.1 定义数组

可变长数组

2.2 引用数组

2.3 一维数组的初始化

2.4 示例

  1. 示例: 10个元素赋值为 0-9, 并倒序输出

    #include <stdio.h>
    int main()
    {
        int i,a[10];
        for(i=0; i<=9;i++)
            a[i]=i;
    
        for(i=9;i>=0;i--)
        {
            printf("%d\t", a[i]);
        }
        printf("\n");
        return 0;
    }
    
  2. 示例: 使用数组,输出斐波那契数列的前 20 个数

    #include <stdio.h>
    
    int main()
    {
        int i;
        int f[20]={1,1};
    
        for(i=2;i<=20;i++)
            f[i] = f[i-2] + f[i-1];
    
        for(i=0;i<20;i++)
        {
    
            if(i%5==0) printf("\n");
            printf("%12d", f[i]);
        }
    
        printf("\n");
        return 0;
    }
    
  3. 示例: 随机输入10个整数, 使用冒泡排序按从小到大的顺序输出.

    冒泡排序: 如果有 n 个数, 则进行 n-1 趟比较, 第 1 趟比较中有 n - 1 此比较, 第 2 趟有 n-2 比较, 第 j 趟中有 n - j 趟比较. 在每趟比较中, 从第 1 个数字开始, 两两比较, 并将两者中的大者排到小者的后面, 以此类推. 第 1 趟比较中, 最大的数字将排到末尾, 第 2 趟比较中, 次大的数字将排到次末尾, 依次类推.

    #include <stdio.h>
    
    int main()
    {
        int a[10];
        int i,j,t;
        // 获取输入
        printf("input 10 numbers: \n");
        for(i=0;i<10;i++)
            scanf("%d", &a[i]);
        printf("\n");
    
        // 冒泡排序
        for(j=0;j<9;j++)
            for(i=0;i<9-j;i++)
                if(a[i]>a[i+1])
                {
                    t=a[i]; 
                    a[i]=a[i+1];
                    a[i+1]=t;
                }
    
        // 输出
        printf("The Sorted Numbers: \n");
        for(i=0; i<10;i++)
            printf("%d ", a[i]);
        printf("\n");
        return 0;
    }
    

3. 二维数组

二维数组常常成为矩阵. 用矩阵表示二维数组, 只是逻辑上的概念, 能形象的表示出行和列的关系. 但是在内存中, 各元素是连续存放的, 不是二维的, 是线性的.

除了 二维数组之外, C 语言还允许使用多维数组, 类似二维数组, 如 三维数组定义如下 float a[2][3][4].

数组存放示意图

3.1 定义二维数组

3.2 引用二维数组

3.3 二维数组的初始化

3.4 示例

  1. 示例1 : 将一个二维数组行和列的元素互换, 存到另一个数组中. 如将一个两行三列的数据转换为三行两列的数据.

    核心算法: 将 a 数组中的元素 a[i][j] 存到 b 数组中的 b[j][i].

    #include <stdio.h>
    int main()
    {
        int a[2][3] = {{1,2,3}, {4,5,6}};
        int b[3][2], i,j;
        printf("array a: \n");
        for(i=0;i<=1;i++)
        {
            for(j=0;j<=2;j++)
            {
                printf("%5d", a[i][j]);
                b[j][i] = a[i][j];
            }
            printf("\n");
        }
    
        printf("array b: \n");
        for(i=0;i<=2;i++)
        {
            for(j=0;j<=1;j++)
                printf("%5d", b[i][j]);
            printf("\n");
        }
        return 0;
    }
    
    // 输出如下:
    array a: 
        1    2    3
        4    5    6
    array b: 
        1    4
        2    5
        3    6
    
  2. 示例2 : 有一个 3x4 的矩阵, 求出其中最大的元素的值及其下标.

    打擂台算法: 有 a[3][4] 数组, 从 a[0][0] 开始, 存到 max 中, a[3][4] 中的每个数依次与 max 比较大小, 大的 赋值并保存到 max 中, 最后, max 为最大值.

    #include <stdio.h>
    
    int main()
    {
        int i,j, row=0,colum=0,max;
        int a[3][4] = {{1,2,3,4},{9,8,7,6},{-10,10,-5,2}};
    
        max=a[0][0];
        for(i=0;i<=2;i++)
            for(j=0;j<=3;j++)
                if(a[i][j]>max){
                    max = a[i][j];
                    row = i;
                    colum = j;
                }
    
        printf("Max=%d\nrow=%d\ncolum=%d\n",max,row,colum );
        return 0;        
    }
    

4. 字符数组

C 语言中没有字符串类型, 字符串是存放在字符型数组中的.

4.1 字符数组定义

4.2 字符数组初始化

4.3 字符数组引用

4.4 字符数组处理函数

include <string.h>
puts – 输出字符串的函数
gets – 输入字符串的函数
strcat – 字符串连接函数
strcpy strncpy – 字符串复制函数
strcmp – 字符串比较函数
strlen – 测字符串长度的函数
strlwr – 转换为小写的函数
strupr – 转换为大写的函数

4.5 示例

C语言中的 IO

递归

数据结构

1. 结构体

2. 结构体数组

3. 链表


一. 概述

一般而言, 不同 CPU 制造商使用的指令系统和编码格式不同. 但是, 高级语言以非常抽象的方式描述行为, 不受限于特定 CPU 或 指令集.

编译器就是把高级语言程序翻译成计算机可以理解的机器指令集的程序.

程序员进行高级思维活动, 而编译器则负责处理冗长乏味的细节工作.

1. C 语言标准

  • C89/C90/ANSI C

    1989年, 美国国家标准协会(ANSI) 发布 C89 标准, 定义了 C 语言和 C 标准库.

    1990年, 国际标准化组织采用了 C89 标准, 也成为 C90.

  • C99

    1994年, ANSI/ISO 联合委员会, 开始修订 C 标准, 并最终发布 C99 标准. 主要在着眼于 国际化, 弥补缺陷 和 提高计算的实用性三个方面, 在其他方面则比较保守, 并且尽量与 C90, C++ 兼容.

  • C11

    2011年, 标准委员会发布 C11 标准.

2. 使用 C 语言的 7 个步骤

编程并非一个线性的过程, 有时需要在不同的步骤之间重复. 但是一般都会遵循以下的最佳实现:

  1. 定义程序目标

    要有清晰的思路, 想要程序去做什么, 程序需要哪些信息, 进行哪些计算和控制, 以及程序应该报告哪些信息.

    该步骤不涉及具体的计算机语言, 应该用一般术语来描述问题.

  2. 设计程序

    如何用程序来实现. 还要决定在程序中如何表示数据, 用什么方法处理数据.

    应该用一般术语来描述问题, 而不是具体的代码. 但是, 某些决策可能取决于语言的特性.

  3. 编写代码

    吧设计的程序翻译成 C 语言.

  4. 编译

    编译源代码. 编译的细节取决于编程的环境.

    编译器是吧源代码转换成可执行代码的程序. 可执行代码是用计算机的机器语言表示的代码. 这种语言由数字码表示的指令组成.

    C 编译器负责吧 C 代码编译成特定的机器语言. C 编译器还将源代码与 C 库的 代码合并成(更精确的说, 是由一个被称为链接器的程序来链接库函数, 在大多数系统中, 编译器运行链接器.)最终的程序.其结果是, 生成一个用户可以运行的可执行文件, 其中包含计算机能理解的代码.

    编译器还会检查 C 语言程序是否有效. 如果发现错误, 就报错退出.

  5. 运行程序

  6. 测试和调试程序

  7. 维护和修改程序

3. 编程机制

编译和链接原理图

编译器源代码转换为中间代码, 链接器中间代码和其他代码(库, 头文件等)合并, 生成可执行文件.

C 使用分而治之的方法对程序进行模块化, 可以独立编译单独的模块, 稍后再用链接器合并以编译的模块. 另外, 链接器还将编写的程序和预编译的库代码合并.

中间代码文件有多种形式. 虽然其中存储的是编译器翻译的源码, 但还不是一个完整的程序, 缺少启动代码库函数.

启动代码充当这程序和操作系统之间的接口.
库代码 目标文件并不包含库函数的代码, 链接器的工作就是把程序中要用到的库函数代码提取出来.

链接器的作用, 是把编写的目标代码, 系统的标准启动代码库代码 三部分合并成一个文件, 即可执行文件.

源文件: 包含 C 代码的文本文件. 习惯上以 .c 结尾.
目标代码文件: 由编译器生成, 内容为机器语言指令. 但只包含 自己写的源代码翻译的机器语言代码.
可执行文件: 除了 源代码翻译的机器语言代码之外, 还包含 源代码使用的库函数, 和启动代码的机器代码.
: 标准库.

3.1 Linux/Unix

Linux 下主要使用 gcc(GUN 编译器集合) 和 LLVM 编译器.

1
2
3
4
5
6
7
-- 运行不同的 C 标准编译.

$ gcc -std=c99 name.c

$ gcc -std=c1x name.c

$ gcc -std=c11 name.c

UNIX 环境下 C 编译与链接

name.o 为源码生成的目标代码文件, 一旦链接器生成了完整的可执行程序, 就会将其删除, 所以, 可能在当前目录下, 找不到 目标代码文件.

3.2 Mac

Xcode

数据类型

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

运算符, 表达式和语句

1. 运算符

2. 表达式

3. 语句

3.1 标号语句

3.2 复合语句

3.3 表达式语句

3.4 选择语句

3.5 迭代语句

3.6 跳转语句

C 控制语句

1. 循环

2. 分支

3. 跳转

字符输入/输出和输入验证

函数

数组和指针

字符串和字符串函数

存储类别, 链接和内存管理

文件输入/输出

结构和其他数据形式

位操作

C 预处理 和 C 库

高级数据表示


指针和指针变量

1. 指针

指针是类型相关的.

指针变量是存储单元, 存储的是地址. 长度 为 4字节(32位), 或 8字节(64位). 提供了一种间接访问变量的方式.

1
2
3
指针变量 --> 地址 --> 内容

普通变量 --> 内容

格式声明

1
2
3
4
5
6
7
8
9
10
数据类型 *指针变量名;

float *p;
float *p=NULL;

float num=3.1415, *p, *q=NULL;

p = &num; // 赋值

print("%f\n", *p); // 访问指针指向的内存

访问内存单元的方式:

  1. 直接访问, 如 局部变量或全局变量
  2. 间接访问, 如 指针.

    可以实现访问指定的内存单元.
    传递大数据, 变量时, 无需赋值数据, 只修改指针即可, 更加高效.

指针使用之前, 要判断

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(void)
{
float *q=NULL;
if (NULL!=q){
printf("%f\n", *q);
}
return 0;
}

2. 指针访问数组

2.1 一维数组

指针的 +1, *(p+1) 并非数值上的 +1, 而是访问单元 +1

指针和数组:

  • 声明: age[N] –> *p
  • 访问: age[i] –> p[i]*(p+i)
  1. 解引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <stdio.h>

    int main(void)
    {
    short age[5] = {10, 20, 30, 40, 50};
    short *p=NULL;

    p = age; // p 指向 数组age 的首地址

    printf("%hd %hd\n", *p, *(p+1)) // 打印数组的第一个和第二个元素

    // 循环打印所有值
    int i=0;
    for(i=0; i<5; i++){
    printf("%hd ", *(p+i));
    }

    return 0;
    }
  2. 数组方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <stdio.h>

    int main(void)
    {
    short age[5] = {10, 20, 30, 40, 50};
    short *p=NULL;

    p = age; // p 指向 数组age 的首地址

    printf("%hd %hd\n", p[0], p[1]) // 打印数组的第一个和第二个元素

    // 循环打印所有值
    int i=0;
    for(i=0; i<5; i++){
    printf("%hd ", p[i]);
    }
    return 0;
    }

2.2 二维数组

二维数组本质上是一维数组.

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(void)
{
short age[2][4] = {1,2,3,4,5,6,7,8};

// 打印每个数组元素的地址.
int i=0, j=0;
for(i=0; i<2; i++){
for(j=0; j<4; j++){
printf("age[%d][%d] addr %p\n", i, j, &(age[i][j]));
}
}

}

指针访问 二维数组的 核心思想是, 把二维数组地址转换为 一维数组.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(void)
{
short age[2][4] = {1,2,3,4,5,6,7,8};
short *p = &(age[0][0]); // 声明指针

// 打印元素
int i=0, j=0;
for(i=0; i<2; i++){
for(j=0; j<4; j++){
printf("age[%d][%d] addr %p\n", i, j, *(p+i*4+j));
}
}

}

数组指针:使用二维数组方式访问二维数组

  • 声明: 数组指针, age[M][N] –> (*p)[N]
  • 访问: age[i][j] –>
    数组方式: p[i][j]
    解引用方式: *(*(p+i)+j) –> 可以推广至多数组, 实际上是递归的使用 一维数组解引用.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(void)
{
short age[2][4] = {1,2,3,4,5,6,7,8};
short (*p)[4] = age // 声明指针

// 打印元素
int i=0, j=0;
for(i=0; i<2; i++){
for(j=0; j<4; j++){
printf("age[%d][%d] addr %p\n", i, j, p[i][j]); // 数组访问方式
printf("age[%d][%d] addr %p\n", i, j, *(*(p+i)+j)); // 解引用方式, 递归方式.
}
}

}