https://www.runoob.com/cprogramming/c-tutorial.html

1. C
语言教程

C
语言一种通用面向程式计算机程序设计语言。1972 为了移植开发 UNIX 操作系统丹尼斯·贝尔电话实验室设计开发 C 语言
C
语言一种广泛使用计算机语言 Java 编程语言一样普及二者现代软件程序员之间得到广泛使用
当前最新 C 语言标准 C18 ,之前 C 语言标准 C17、C11...C99

现在开始学习 C 编程

C
在线工具
适合阅读教程
教程专门需要从零开始了解 C 语言软件程序员打造教程 C 语言足够认识从而提升自己专业知识水平
阅读教程需要了解知识
开始学习教程之前需要计算机编程术语基本了解任何一种编程语言基本了解有助于理解 C 语言编程概念有助于加快学习进度
编译/执行 C 程序
实例
#include <stdio.h>

int main()
{
/*
第一 C 程序 */
printf("Hello, World! \n");

return 0;
}

运行实例 »
实例解析

所有 C 语言程序需要包含 main() 函数代码 main() 函数开始执行
/* ... */
用于注释说明
printf()
用于格式化输出屏幕。printf() 函数 "stdio.h" 文件声明
stdio.h
个头文件 (标准输入输出文件) , #include 预处理命令用来引入文件编译器遇到 printf() 函数如果没有找到 stdio.h 文件发生编译错误
return 0;
语句用于表示退出程序


2. C
简介

C
语言一种通用高级语言最初丹尼斯·贝尔实验室开发 UNIX 操作系统设计。C 语言开始 1972 DEC PDP-11 计算机首次实现

1978 布莱恩·柯林(Brian Kernighan)丹尼斯·(Dennis Ritchie)制作 C 第一公开描述现在称为 K&R 标准

UNIX
操作系统,C编译器几乎所有 UNIX 应用程序 C 语言编写由于各种原因,C 语言现在已经成为一种广泛使用专业语言

易于学习
结构语言
产生高效率程序
可以处理底层活动
可以多种计算机平台编译
关于 C
C
语言为了编写 UNIX 操作系统发明
C
语言是以 B 语言基础,B 语言大概 1970 引进
C
语言标准 1988 美国国家标准协会(ANSI,全称 American National Standard Institute)制定
截至 1973 ,UNIX 操作系统完全使用 C 语言编写
目前,C 语言广泛使用系统程序设计语言
大多数先进软件使用 C 语言实现
当今流行 Linux 操作系统 RDBMS(Relational Database Management System:关系数据库管理系统) MySQL 使用 C 语言编写
为什么使用 C?
C
语言最初用于系统开发工作特别组成操作系统程序由于 C 语言产生代码运行速度汇编语言编写代码运行速度几乎一样所以采用 C 语言作为系统开发语言下面列举几个使用 C 实例

操作系统
语言编译器
汇编
文本编辑器
打印机
网络驱动器
现代程序
数据库
语言解释器
实体工具
C
程序
C 语言程序可以 3 可以数百万可以多个扩展名为 ".c" 文本文件例如,hello.c。可以使用 "vi"、"vim" 任何其他文本编辑器编写 C 语言程序

教程假定已经知道如何编辑文本文件以及如何程序文件编写源代码

C11
C11(
称为C1X)ISO标准ISO/IEC 9899:2011。之前C语言标准C99。

特性
对齐处理(Alignment)标准化包括_Alignas标志,alignof运算,aligned_alloc函数以及<stdalign.h>文件)。

_Noreturn
函数标记类似 gcc __attribute__((noreturn))。

_Generic
关键字

线程(Multithreading)支持包括
_Thread_local
存储类型标识,<threads.h>文件里面包含线程创建管理函数
_Atomic
类型修饰<stdatomic.h>文件

增强Unicode支持基于C Unicode技术报告ISO/IEC TR 19769:2004,增强Unicode支持包括UTF-16/UTF-32编码增加char16_tchar32_t数据类型提供包含unicode字符串转换函数文件<uchar.h>。

除了 gets() 函数使用安全函数gets_s()替代

增加边界检查函数接口定义安全函数例如 fopen_s(),strcat_s() 等等

增加浮点处理()。

匿名结构/联合支持这个gcc早已存在,C11引入标准

静态断言(Static assertions),_Static_assert(),解释 #if #error 之后处理

fopen() 模式,("…x")。类似 POSIX 中的 O_CREAT|O_EXCL,文件比较常用

新增 quick_exit() 函数作为终止程序方式 exit()失败可以最少清理工作


3. C
环境设置

如果想要设置 C 语言环境需要确保电脑以下软件文本编辑器 C 编译器

文本编辑器
通过编辑器创建文件通常称为文件文件包含程序源代码

C
程序文件通常使用扩展名 .c。

开始编程之前确保文本编辑器足够经验编写计算机程序然后存在文件编译执行

Visual Studio Code:
虽然通用文本编辑器插件支持 C/C++ 开发使成为流行选择通过安装 C/C++ 插件调整设置可以使成为 C 语言开发环境

安装教程:https://www.runoob.com/w3cnote/vscode-tutorial.html

下载地址:https://code.visualstudio.com/

Sublime Text:Sublime Text
轻量级快速高度定制文本编辑器插件支持 C 语言开发具有强大代码编辑功能快捷键使得编码更加高效

下载地址:https://www.sublimetext.com/

Atom:Atom
开源文本编辑器 GitHub 开发插件主题可以定制适合 C 语言开发环境

下载地址:https://atom-editor.cc/

Vim
Emacs:传统文本编辑器它们有着强大编辑功能高度定制对于熟练用户非常强大插件配置可以支持C语言开发

Eclipse:Eclipse
另一功能强大集成开发环境虽然最初 Java 开发设计通过安装 C/C++ 插件可以使支持 C 语言开发

C
编译器
文件中的源代码人类可读需要"编译",机器语言这样 CPU 可以给定指令执行程序

C
语言编译器用于源代码编译最终可执行程序这里假设已经编程语言编译器基本了解

常用免费编译器 GNU C/C++ 编译器如果使用 HP Solaris,可以使用各自操作系统编译器

以下部分指导如何不同操作系统安装 GNU C/C++ 编译器这里同时提到 C/C++,主要因为 GNU gcc 编译器适合 C C++ 编程语言

UNIX/Linux
安装
如果使用 Linux UNIX,命令行使用下面命令检查系统是否安装 GCC:

$ gcc -v
如果计算机已经安装 GNU 编译器显示如下消息

Using built-in specs.
Target: i386-redhat-linux
Configured with: ../configure --prefix=/usr .......
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)
如果安装 GCC,那么按照 http://gcc.gnu.org/install/ 详细说明安装 GCC。

教程基于 Linux 编写所有给定实例 Cent OS Linux 系统编译

Mac OS
安装
如果使用 Mac OS X,快捷获取 GCC 方法苹果网站上下 Xcode 开发环境按照安装说明进行安装一旦安装 Xcode,使用 GNU 编译器

Xcode
目前 developer.apple.com/technologies/tools/ 上下

Windows
安装
Cygwin
Cygwin
Windows 操作系统模拟 Unix/Linux 环境软件包允许用户 Windows 使用 Unix 工具应用程序

Cygwin
通过提供 DLL(动态链接库),这些 DLL 充当 Unix 系统调用 Windows 内核之间桥梁使得 Unix 程序能够 Windows 系统运行

Cygwin
官网:https://www.cygwin.com/。

官网下载安装
下载完成双击下载文件
接下来可以一直点击下一步(Next):
这里我们可以添加网易开源镜像阿里镜像 https://mirrors.aliyun.com/cygwin/:
安装完成桌面生成图标
双击图标进入命令行界面输入 cygcheck -c cygwin命令可以查看当前 cygwin 版本信息

接下来我们安装 gcc/g++ 编译环境命令行进入 setup-x86_64.exe 目录执行

setup-x86_64.exe -q -P wget -P gcc-g++ -P make -P diffutils -P libmpfr-devel -P libgmp-devel -P libmpc-devel
安装完成进入 Cygwin64 终端输入 gcc --version 命令可以查看版本信息

MinGW-w64
为了 Windows 安装 GCC,需要安装 MinGW-w64。

MinGW-w64
开源项目 Windows 系统提供完整 GCC 工具链支持编译生成 32 64 Windows 应用程序

访问 MinGW-w64 主页 mingw-w64.org,进入 MinGW 下载页面 https://www.mingw-w64.org/downloads/,下载最新版本 MinGW-w64 安装程序

MinGW-w64
下载详情页面包含 MinGW-w64 特定工具整合


4. C VScode

章节我们已经介绍 C 基本概念实例章节我们介绍 C 代码编辑器 -- VScode。

VSCode(
全称:Visual Studio Code)微软开发跨平台免费源代码编辑器,VSCode 开发环境非常简单

VSCode
支持 C/C++ 微软提供 Visual Studio Code 扩展使得 Windows、Linux macOS 进行跨平台 C C++ 开发成为可能

创建 *.c *.cpp文件扩展添加一些功能比如语法高亮着色)、智能悬停以及错误检查

安装 VS Code
VSCode
安装简单打开官网 https://code.visualstudio.com/,下载软件包步步安装即可安装过程注意安装路径设置环境变量默认自动添加系统勾选以下所有选项

VSCode
完整安装教程参考:https://www.runoob.com/vscode/vscode-tutorial.html

安装 C/C++ 扩展
1、
打开 VS Code。
点击左侧菜单栏选择扩展图标使用键盘快捷键 (⇧⌘X) 打开扩展界面
搜索C++。
选择以下扩展点击 Install。

创建 C 代码文件
打开 VScode,然后点击新建文件

点击选择语言
搜索输入 c,创建 test.c 文件
输入代码保存 test.c 文件代码文件集成终端执行以下命令

实例
#include <stdio.h>

int main()
{
/*
第一 C 程序 */
printf("Hello, World! \n");

return 0;
}


5. C AI
编程助手

AI 发展迅猛作为开发人员我们总是追求高效工作方式,AI 出现可以改变编程方式

AI
我们就是可靠编程助手我们提供实时建议和解方案无论快速修复错误提升代码质量或者查找关键文档资源,AI 作为编程助手事半功倍

今天大家推荐适配 Visual Studio(本文使用),VS Code(本文使用),JetBrains系列以及Vim多种编译器环境插件Fitten Code,Fitten Code模型驱动 AI 编程助手可以自动生成代码提升开发效率调试 Bug,节省时间另外可以对话聊天解决编程碰到问题

Fitten Code
免费支持 80 多种语言:Python、C++、Javascript、Typescript、Java

目前对于 C 语言,Fitten Code 支持多种文本编辑器 IDE 使用接下来我们详细看看 VS Code Visual Studio IDE 安装使用

、VS Code 版本

1.
安装
2.
智能
3. AI
问答
4.
生成代码
5.
代码翻译
6.
生成注释
7.
解释代码
8.
生成测试
9.
检查 BUG
10.
编辑代码
11.
常见问题
、Visual Studio 版本

1.
安装
2.
智能
3. AI
问答
4.
生成代码
5.
代码翻译
6.
生成注释
7.
解释代码
8.
生成测试
9.
检查 BUG
10.
编辑代码
、VS Code版本
1、
安装
如果已经安装 VS Code 版本大于等于1.68.0,直接跳过步骤否则点击下载前往官网下载安装 VS Code。

打开 VS Code,点击左侧 Extensions(扩展按钮
搜索搜索关键字 Fitten Code:
搜索结果中点Install:
登录注册即可开始使用
打开代码文件输入代码,Fitten Code 自动代码
按下 Tab 接受所有建议
按下 Ctrl+→ (mac系统Command+→)接收单个建议
3、AI
问答
用户通过点击左上工具栏中的Fitten Code – 开始对话或者使用快捷键Ctrl+Alt+C(mac系统Control+Option+C)打开对话窗口进行对话
用户选中代码段进行对话,Fitten Code 自动引用用户选中代码段此时直接针对代码段进行操作
4、
生成代码
左侧 Fitten Code 工具栏选择 "Fitten Code - 生成代码" 或者使用快捷键 Ctrl+Alt+G (mac系统Control+Option+G),如下
然后输入输入指令即可生成代码
利用对话功能生成代码
5、
代码翻译
编辑代码功能可以实现不同语言之间转换Python语法转换C++语法选中需要进行编辑代码段选择 "Fitten Code – 编辑代码" 点击左侧工具栏中的 "Fitten Code – 编辑代码" 或者使用快捷键 Ctrl+Alt+E (mac系统Control+Option+E),如下
然后输入输入需求(如此要求FittenPython代码C++代码):
可以Chat界面实现选中需要进行编辑代码段选择 "Fitten Code – 开始聊天" 点击左侧工具栏中的 "Fitten Code – 开始聊天" 或者使用快捷键 Ctrl+Alt+C, 如下
6、
生成注释
Fitten Code
能够根据代码自动生成相关注释通过分析代码逻辑结构代码提供清晰易懂解释文档不仅提高代码可读性方便其他开发人员理解使用代码选中需要生成注释代码段然后选择 "Fitten Code – 生成注释":
即可生成对应注释如下点击"Apply"即可应用
7、
解释代码
Fitten Code
可以代码进行解释可以通过选中代码段然后选择 "Fitten Code – 解释代码" 进行解释如下
此外可以进一步回答用户关于代码疑问如下
8、
生成测试
Fitten Code
拥有自动生成单元测试功能可以根据代码自动产生相应测试提高代码质量可靠性通过选中代码段选择 "Fitten Code – 生成单元测试" 实现如下
9、
检查 BUG
Fitten Code
可以代码检查可能 bug,修复建议选中对应代码段然后选择 "Fitten Code查找Bug",如下
10、
编辑代码
Fitten Code
根据用户指示选定代码进行编辑用户点击 "Apply" 即可应用变更通过选中代码段选择 "Fitten Code – 编辑代码" 左上工具栏点击 "Fitten Code – 编辑代码",如下
随后用户输入输入指示,Fitten Code 新建窗口对比显示更改更改内容用户通过点击 "Apply" 应用更改如下
11、
常见问题
如果 VS Code 远程服务器 remote 无法连接外网点击左下按钮点击设置
然后设置页面点击右上 "打开设置(JSON)":
最后弹出 settings.json 文件添加以下内容即可:
"remote.extensionKind": { "FittenTech.Fitten-Code": ["ui"] }
内容参考官网:https://code.fittentech.com/

支持以下 4 编辑器开发环境

VS Code:
本文详细介绍
JetBrains IDE
系列包括PyCharm)
Visual Studio:
本文详细介绍
Vim
、Visual Studio版本
1、
安装
点击上方工具栏拓展选项选择管理拓展选项
接着联机页面搜索"Fitten Code",点击下载下载完成重启Visual Studio
扩展选项中选Fitten,选择Open Chat Window进入登录界面完成注册登录
2、
智能
打开代码文件输入代码,Fitten Code 自动代码
按下 Tab 接受所有建议
按下 Ctrl+→ 接收单个建议
3、AI
问答
用户通过点击左上工具栏中的"Fitten Code – 开始对话"打开对话窗口进行对话
4、
生成代码
Fitten Code工具栏选择"Fitten Code - 生成代码",然后输入输入指令即可生成代码
利用注释自动功能生成代码
可以利用对话功能生成代码
5、
代码翻译
Fitten Code
可以实现代码语义翻译支持多种编程语言之间互译以下方法可以实现

(1)
选中需要进行翻译代码段选择"Fitten Code – 重构选择代码",然后输入输入需求即可完成转换
(2)
选中需要进行翻译代码段点击左侧工具栏中的"Fitten Code – 开始对话"。然后输入输入需求即可完成转换
6、
生成注释
Fitten Code
能够根据代码自动生成相关注释通过分析代码逻辑结构代码提供清晰易懂解释文档不仅提高代码可读性方便其他开发人员理解使用代码选中需要生成注释代码段然后选择 "Fitten Code – 生成注释":
可以通过对话功能实现
7、
解释代码
Fitten Code
可以代码进行解释可以通过选中代码段然后选择 "Fitten Code – 解释代码" 进行解释如下
可以通过对话功能实现
8、
生成测试
Fitten Code
拥有自动生成单元测试功能可以根据代码自动产生相应测试提高代码质量可靠性通过选中代码段选择 "Fitten Code – 生成函数单元测试" 实现如下
可以通过对话功能实现
9、
检查 BUG
开始对话窗口Fitten Code提问代码bug查找,Fitten Code可以智能完成debug工作
10、
编辑代码
开始对话窗口FittenCode提供需要编辑代码段输入需求,Fitten可以完成代码编辑工作
内容参考官网:https://code.fittentech.com/

支持以下 4 编辑器开发环境

VS Code:
本文详细介绍
JetBrains IDE
系列包括PyCharm)
Visual Studio:
本文详细介绍
Vim


6. C
程序结构

我们学习 C 语言基本构建之前我们看看小的 C 程序结构接下来章节可以以此作为参考

C Hello World
实例
C
程序主要包括以下部分

处理器指令
函数
变量
语句 & 表达式
注释
我们简单代码可以输出单词 "Hello World":

实例
#include <stdio.h>

int main()
{
/*
第一 C 程序 */
printf("Hello, World! \n");

return 0;
}
接下来我们讲解一下上面程序

程序第一 #include <stdio.h> 处理器指令告诉 C 编译器实际编译之前包含 stdio.h 文件
一行 int main() 函数程序这里开始执行
一行 /*...*/ 将会编译器忽略这里放置程序注释内容它们称为程序注释
一行 printf(...) C 另一函数屏幕显示消息 "Hello, World!"。
一行 return 0; 终止 main() 函数返回 0。
编译 & 执行 C 程序
接下来我们看看如何源代码存在文件以及如何编译运行下面简单步骤

打开文本编辑器添加上述代码
保存文件 hello.c。
打开命令提示进入保存文件所在目录
键入 gcc hello.c,输入回车编译代码
如果代码没有错误命令提示一行生成 a.out(Windows 生成 a.exe) 可执行文件
现在键入 a.out 执行程序
可以看到屏幕显示 "Hello World"。
$ gcc hello.c
$ ./a.out
Hello, World!
确保路径包含 gcc 编译器确保包含文件 hello.c 目录运行

如果多个 c 代码源码文件编译方法如下

$ gcc test1.c test2.c -o main.out
$ ./main.out
test1.c
test2.c 源代码文件

7. C
基础语法
C
语言一种通用编程语言广泛应用系统编程嵌入开发高性能计算领域

C
语言具有高效灵活可移植性特点许多其他编程语言基础

C 语言令牌(Token)程序基本组成单位编译器通过源代码进行词法分析代码分解一个个令牌

C
语言令牌主要包括以下种类

关键字(Keywords)
标识(Identifiers)
常量(Constants)
字符串字面(String Literals)
运算(Operators)
分隔(Separators)
C
程序基本结构
简单 C 语言程序可以输出 "Hello, World!":

实例
#include <stdio.h>

int main() {
printf("Hello, World!\n");
return 0;
}
以上代码组成结构如下

处理器指令 #include #define。
函数 C 程序 main() 函数
变量声明声明程序使用变量
函数定义定义程序使用函数
复杂一点 C 程序结构说明后面章节知识展开说明):

实例
#include <stdio.h> //
文件包含

#define PI 3.14159 //
定义

//
函数声明
int add(int a, int b);

int main() { //
函数
//
变量声明
int num1, num2, sum;

//
用户输入
printf("Enter two integers: ");
scanf("%d %d", &num1, &num2);

//
函数调用
sum = add(num1, num2);

//
输出结果
printf("Sum: %d\n", sum);

return 0; //
返回 0 表示程序成功执行
}

//
函数定义
int add(int a, int b) {
return a + b;
}
文件包含

文件通常程序开头使用 #include 指令包含文件提供函数声明标准输入输出 <stdio.h>、标准 <stdlib.h> 它们定义函数常量使程序能够使用预定义函数
示例:#include <stdio.h>
定义

通过 #define 指令定义符号常量代码片段编译处理器替换定义内容常用定义常量或者复杂代码
示例:#define PI 3.14159
函数声明

C 语言函数声明必须函数定义调用之前声明提供函数返回类型函数参数列表以便编译器知道如何调用函数
示例:int add(int a, int b);
函数

main()
函数 C 程序入口 C 程序必须包含 main() 函数程序 main() 开始执行返回通常 0 表示程序成功执行
示例:int main() { ... }
变量声明

C 程序所有变量必须使用声明变量可以 main() 函数声明可以其他函数全局声明
示例
printf("Enter two integers: ");
sum = add(num1, num2);
语句表达式

语句 C 程序基本执行单元通常函数调用赋值控制语句 if、for 表达式表达式变量操作符常量组成代码片段
示例
printf("Enter two integers: ");
sum = add(num1, num2);
控制语句

控制语句用于控制程序执行顺序包括 if、for、while、do-while 循环条件分支语句
示例
if (num1 > num2) {
printf("num1 is greater than num2");
}
函数定义

函数定义包含实际函数描述函数具体实现函数通常包含参数局部变量返回
示例
int add(int a, int b) {
return a + b;
}
返回语句

return
语句用于终止函数执行控制权还给调用函数。main() 函数返回通常 0 表示正常执行
示例:return 0;
分隔
分隔用于分隔语句表达式常见分隔包括

逗号 , :用于分隔变量声明函数参数
分号 ; :用于结束语句
括号
圆括号 () 用于分组表达式函数调用
花括号 {} 用于定义代码
方括号 [] 用于数组下标
C 程序分号 ; 语句结束也就是说语句必须分号结束表明逻辑实体结束

例如下面不同语句

printf("Hello, World! \n");
return 0;
单独分号可以作为语句表示什么例如

;
注释
C
语言注释方式

//
单行注释
// 开始单行注释这种注释可以单独一行

/*
单行注释 */
/*
注释
注释
注释
*/
/* */
这种格式注释可以单行

实例
//
单行注释

/*
注释
可以跨越
*/

int main() {
//
打印消息
printf("Hello, World!\n");
return 0;
}
不能注释嵌套注释注释不能出现字符串字符

标识
标识程序变量函数数组名字标识字母大写小写)、数字下划线组成第一字符必须字母下划线不能数字

标识字母 A-Z a-z 下划线 _ 开始后跟多个字母下划线数字(0-9)。

C
标识允许出现标点字符比如 @、$ %。C 区分大小写编程语言因此 C ,Manpower manpower 不同标识下面列出几个有效标识

mohd zara abc move_name a_123
myname50 _temp j a23b9 retVal
常量
常量固定程序执行期间不会改变

常量可以整型常量浮点型常量字符常量枚举常量

const int MAX = 100; //
整型常量
const float PI = 3.14; //
浮点型常量
const char NEWLINE = '\n'; //
字符常量
字符串字面
字符串字面双引号起来字符序列

字符串末尾自动添加字符 \0。

char greeting[] = "Hello, World!";
运算(Operators)
运算用于执行各种操作算术运算逻辑运算比较运算

C
语言中的运算种类繁多常见包括

算术运算:+, -, *, /, %
关系运算:==, !=, >, <, >=, <=
逻辑运算:&&, ||, !
运算:&, |, ^, ~, <<, >>
赋值运算:=, +=, -=, *=, /=, %=
其他运算:sizeof, ?:, &, *, ->, .
int a = 5, b = 10;
int sum = a + b; //
使用算术运算 +
int isEqual = (a == b); //
使用关系运算 ==
关键字
列出 C 中的保留这些保留不能作为常量变量其他标识名称

关键字 说明
auto
声明自动变量
break
跳出当前循环
case
开关语句分支
char
声明字符变量函数返回类型
const
定义常量如果变量 const 修饰那么不能改变
continue
结束当前循环开始一轮循环
default
开关语句中的"其它"分支
do
循环语句循环
double
声明精度浮点型变量函数返回类型
else
条件语句否定分支 if 连用
enum
声明枚举类型
extern
声明变量函数其它文件文件其他位置定义
float
声明浮点型变量函数返回类型
for
一种循环语句
goto
无条件跳转语句
if
条件语句
int
声明整型变量函数
long
声明整型变量函数返回类型
register
声明寄存器变量
return
子程序返回语句可以参数可不参数
short
声明整型变量函数
signed
声明符号类型变量函数
sizeof
计算数据类型变量长度字节数
static
声明静态变量
struct
声明结构类型
switch
用于开关语句
typedef
用以数据类型别名
unsigned
声明无符号类型变量函数
union
声明共用类型
void
声明函数返回参数声明类型指针
volatile
说明变量程序执行隐含改变
while
循环语句循环条件
C99
新增关键字
_Bool _Complex _Imaginary inline restrict
C11
新增关键字
_Alignas _Alignof _Atomic _Generic _Noreturn
_Static_assert _Thread_local

C
中的空格
包含空格称为空白可能带有注释,C 编译器完全忽略

C 空格用于描述空白制表换行注释空格分隔语句各个部分编译器识别语句中的元素比如 int)哪里结束下一个元素哪里开始因此在下面的语句

int age;
这里,int age 之间必须至少空格字符通常空白),这样编译器才能区分它们另一方面在下面的语句

fruit = apples + oranges; //
获取水果总数
fruit
=,或者 = apples 之间空格字符不是必需但是为了增强可读性可以根据需要适当增加一些空格


8. C
数据类型
C 语言数据类型用于声明不同类型变量函数广泛系统变量类型决定变量存储占用空间以及如何解释存储模式

C
中的类型分为以下

序号 类型描述
1
基本数据类型
它们算术类型包括整型(int)、字符(char)、浮点型(float)精度浮点型(double)。
2
枚举类型
它们算术类型用来定义程序只能赋予一定离散数值变量
3 void
类型
类型说明 void 表示没有数据类型通常用于函数返回
4
派生类型
包括数组类型指针类型结构类型

数组类型结构类型统称为聚合类型函数类型函数返回类型章节接下来部分我们介绍基本类型其他类型后边几个章节进行讲解

整数类型
列出关于标准整数类型存储大小范围细节

类型 存储大小 范围
char 1
字节 -128 127 0 255
unsigned char 1
字节 0 255
signed char 1
字节 -128 127
int 2
4 字节 -32,768 32,767 -2,147,483,648 2,147,483,647
unsigned int 2
4 字节 0 65,535 0 4,294,967,295
short 2
字节 -32,768 32,767
unsigned short 2
字节 0 65,535
long 4
字节 -2,147,483,648 2,147,483,647
unsigned long 4
字节 0 4,294,967,295
注意各种类型存储大小系统有关目前通用64系统为主

以下列出32系统64系统存储大小差别(windows 相同):



为了得到类型变量特定平台准确大小可以使用 sizeof 运算表达式 sizeof(type) 得到对象类型存储字节大小下面实例演示获取 int 类型大小

实例
#include <stdio.h>
#include <limits.h>

int main()
{
printf("int
存储大小 : %lu \n", sizeof(int));

return 0;
}
%lu
32 无符号整数详细说明查看 C 函数 - printf()。

Linux 编译执行上面程序产生下列结果

int
存储大小 : 4
浮点类型
列出关于标准浮点类型存储大小范围精度细节

类型 存储大小 范围 精度
float 4
字节 1.2E-38 3.4E+38 6 有效
double 8
字节 2.3E-308 1.7E+308 15 有效
long double 16
字节 3.4E-4932 1.1E+4932 19 有效
文件 float.h 定义程序可以使用这些其他有关实数二进制表示细节下面实例输出浮点类型占用存储空间以及范围

实例
#include <stdio.h>
#include <float.h>

int main()
{
printf("float
存储字节数 : %lu \n", sizeof(float));
printf("float
最小值: %E\n", FLT_MIN );
printf("float
: %E\n", FLT_MAX );
printf("
精度: %d\n", FLT_DIG );

return 0;
}
%E
指数形式输出精度实数详细说明查看 C 函数 - printf()。

Linux 编译执行上面程序产生下列结果

float
存储字节数 : 4
float
最小值: 1.175494E-38
float
: 3.402823E+38
精度: 6
void
类型
void
类型指定没有通常用于以下情况

序号 类型描述
1
函数返回
C
各种函数返回或者可以它们返回返回函数返回类型例如 void exit (int status);
2
函数参数
C
各种函数接受任何参数不带参数函数可以接受 void。例如 int rand(void);
3
指针指向 void
类型 void * 指针代表对象地址不是类型例如内存分配函数 void *malloc( size_t size ); 返回指向 void 指针可以转换任何数据类型
如果现在还是无法完全理解 void 类型不用担心后续章节我们将会详细讲解这些概念

类型转换
类型转换数据类型转换一种数据类型

C
语言种类转换

类型转换类型转换表达式自动发生无需进行任何明确指令函数调用通常一种小的类型自动转换较大类型例如int类型转换long类型float类型转换double类型类型转换可能导致数据精度丢失数据截断

类型转换类型转换需要使用强制类型转换运算(type casting operator),可以数据类型强制转换一种数据类型强制类型转换可以使程序员必要数据类型进行精确控制可能导致数据丢失截断

类型转换实例

实例
int i = 10;
float f = 3.14;
double d = i + f; //
int类型转换double类型
类型转换实例

实例
double d = 3.14159;
int i = (int)d; //
double类型转换int类型


9. C
变量
变量其实只不过程序操作存储名称。C 变量特定类型类型决定变量存储大小布局范围可以存储在内运算应用变量

变量名称可以字母数字下划线字符组成必须字母下划线开头大写字母小写字母不同因为 C 大小写敏感基于讲解基本类型以下基本变量类型

类型 描述
char
通常字节), 整数类型
int
整型,4 字节范围 -2147483648 2147483647。
float
精度浮点精度这样格式,1符号,8指数,23小数
double
精度浮点精度1符号,11指数,52小数
void
表示类型缺失

C
语言允许定义各种其他类型变量比如枚举指针数组结构共用等等将会后续章节进行讲解章节我们讲解基本变量类型

C
中的变量定义
变量定义就是告诉编译器何处创建变量存储以及如何创建变量存储变量定义指定数据类型包含类型多个变量列表如下

type variable_list;
type
表示变量数据类型可以整型浮点型字符指针可以用户自定义对象

variable_list
可以多个变量名称组成多个变量之间逗号,分隔变量字母数字下划线组成字母下划线开头

下面列出几个有效声明

定义整型变量

int age;
以上代码,age 定义整型变量

定义浮点型变量

float salary;
以上代码,salary 定义浮点型变量

定义字符变量

char grade;
以上代码,grade 定义字符变量

定义指针变量

int *ptr;
以上代码,ptr 定义整型指针变量

定义多个变量

int i, j, k;
int i, j, k;
声明定义变量 i、j k,指示编译器创建类型 int 名为 i、j、k 变量

变量初始化
C 语言变量初始化定义变量同时赋予初始变量初始化可以定义进行可以后续代码进行

初始化等号后跟常量表达式组成如下

type variable_name = value;
其中,type 表示变量数据类型,variable_name 变量名称,value 变量初始

下面列举几个实例

int x = 10; //
整型变量 x 初始化 10
float pi = 3.14; //
浮点型变量 pi 初始化 3.14
char ch = 'A'; //
字符变量 ch 初始化字符 'A'
int d = 3, f = 5; //
定义初始化 d f
byte z = 22; //
定义初始化 z

//
声明外部变量
extern int d;
extern int f;
后续初始化变量

变量定义代码可以使用赋值运算 = 变量赋予

type variable_name; //
变量定义
variable_name = new_value; //
变量初始化
实例如下

int x; //
整型变量x定义
x = 20; //
变量x初始化20
float pi; //
浮点型变量pi定义
pi = 3.14159; //
变量pi初始化3.14159
char ch; //
字符变量ch定义
ch = 'B'; //
变量ch初始化字符'B'
需要注意变量使用之前应该初始化初始化变量定义可能包含任意垃圾因此为了避免确定行为错误建议使用变量之前进行初始化

变量初始化
C 语言如果变量没有初始化那么默认取决于变量类型所在作用

对于全局变量静态变量函数内部定义静态变量函数外部定义全局变量),它们默认初始

以下不同类型变量没有初始化默认

整型变量(int、short、long):默认0。
浮点型变量(float、double):默认0.0。
字符变量(char):默认'\0',字符
指针变量默认NULL,表示指针指向任何有效内存地址
数组结构联合复合类型变量它们元素成员按照相应规则进行默认初始化可能包括元素递归应用默认规则
需要注意局部变量函数内部定义静态变量不会自动初始化默认它们初始定义包含垃圾)。因此使用局部变量之前应该赋予初始

总结起来,C 语言变量默认取决于类型作用全局变量静态变量默认 0,字符变量默认 \0,指针变量默认 NULL,局部变量没有默认初始定义

C
中的变量声明
变量声明编译器保证变量指定类型名称存在这样编译器需要知道变量完整细节情况继续进一步编译变量声明编译意义程序连接编译器需要实际变量声明

变量声明情况

1、
一种需要建立存储空间例如:int a 声明时候已经建立存储空间
2、
一种需要建立存储空间通过使用extern关键字声明变量定义例如:extern int a 其中变量 a 可以别的文件定义
除非 extern 关键字否则变量定义

extern int i; //
声明不是定义
int i; //
声明定义
extern
说明参阅:C extern 关键字

实例
尝试下面实例其中变量头部已经声明但是定义初始化函数

实例
#include <stdio.h>

//
函数定义变量 x y
int x;
int y;
int addtwonum()
{
//
函数声明变量 x y 外部变量
extern int x;
extern int y;
//
外部变量全局变量)x y 赋值
x = 1;
y = 2;
return x+y;
}

int main()
{
int result;
//
调用函数 addtwonum
result = addtwonum();

printf("result
: %d",result);
return 0;
}
当上面的代码编译执行产生下列结果

result
: 3
如果需要文件引用另外文件定义变量我们引用文件中将变量加上 extern 关键字声明即可

addtwonum.c
文件代码
#include <stdio.h>
/*
外部变量声明*/
extern int x ;
extern int y ;
int addtwonum()
{
return x+y;
}
test.c
文件代码
#include <stdio.h>

/*
定义全局变量*/
int x=1;
int y=2;
int addtwonum();
int main(void)
{
int result;
result = addtwonum();
printf("result
: %d\n",result);
return 0;
}
当上面的代码编译执行产生下列结果

$ gcc addtwonum.c test.c -o main
$ ./main
result
: 3
C
中的(Lvalues)(Rvalues)
C
种类表达式

(lvalue):指向内存位置表达式称为(lvalue)表达式可以出现赋值左边右边
(rvalue):术语(rvalue)存储在内某些地址数值不能进行赋值表达式也就是说可以出现赋值右边不能出现赋值左边
变量因此可以出现赋值左边数值面值因此不能赋值不能出现赋值左边下面有效语句

int g = 20;
但是下面这个不是有效语句生成编译错误

10 = 20;


10. C
常量
常量固定程序执行期间不会改变这些固定叫做字面

常量可以任何基本数据类型比如整数常量浮点常量字符常量字符串面值枚举常量

常量常规变量只不过常量定义不能进行修改

常量可以直接代码使用可以通过定义常量使用

整数常量
整数常量可以十进制八进制十六进制常量前缀指定基数:0x 0X 表示十六进制,0 表示八进制不带前缀默认表示十进制

整数常量可以后缀后缀 U L 组合,U 表示无符号整数(unsigned),L 表示整数(long)。后缀可以大写可以小写,U L 顺序任意

下面列举几个整数常量实例

212 /*
合法 */
215u /*
合法 */
0xFeeL /*
合法 */
078 /*
非法:8 不是八进制数字 */
032UU /*
非法不能重复后缀 */
以下各种类型整数常量实例

85 /*
十进制 */
0213 /*
八进制 */
0x4b /*
十六进制 */
30 /*
整数 */
30u /*
无符号整数 */
30l /*
整数 */
30ul /*
无符号整数 */
整数常量可以带有后缀表示数据类型例如

实例
int myInt = 10;
long myLong = 100000L;
unsigned int myUnsignedInt = 10U;
浮点常量
浮点常量整数部分小数点小数部分指数部分组成可以使用小数形式或者指数形式表示浮点常量

使用小数形式表示必须包含整数部分小数部分同时包含两者使用指数形式表示必须包含小数点指数同时包含两者符号指数 e E 引入

下面列举几个浮点常量实例

3.14159 /*
合法 */
314159E-5L /*
合法 */
510E /*
非法完整指数 */
210f /*
非法没有小数指数 */
.e55 /*
非法缺少整数分数 */
浮点数常量可以带有后缀表示数据类型例如

实例
float myFloat = 3.14f;
double myDouble = 3.14159;
字符常量
字符常量单引号例如,'x' 可以存储 char 类型简单变量

字符常量可以普通字符例如 'x')、转义序列例如 '\t'),通用字符例如 '\u02C0')。

C 有一些特定字符它们前面反斜杠它们具有特殊含义用来表示换行(\n)制表(\t)列出一些这样转义序列

转义序列 含义
\\ \
字符
\' '
字符
\" "
字符
\? ?
字符
\a
警报铃声
\b
退格键
\f

\n
换行
\r
回车
\t
水平制表
\v
垂直制表
\ooo
八进制
\xhh . . .
多个数字十六进制
下面实例显示一些转义序列字符

实例
#include <stdio.h>

int main()
{
printf("Hello\tWorld\n\n");

return 0;
}
当上面的代码编译执行产生下列结果

Hello World

字符常量 ASCII 可以通过强制类型转换转换数值

实例
char myChar = 'a';
int myAsciiValue = (int) myChar; //
myChar 转换 ASCII 97
字符串常量
字符串面值常量双引号 " " 中的字符串包含类似字符常量字符普通字符转义序列通用字符

可以使用空格分隔字符串常量进行分行

下面实例显示一些字符串常量下面形式显示字符串相同

"hello, dear"

"hello, \

dear"

"hello, " "d" "ear"
字符串常量在内 null 终止 \0 结尾例如

char myString[] = "Hello, world!"; //
系统字符串常量自动 '\0'
定义常量
C 简单定义常量方式

使用 #define 处理器: #define 可以程序定义常量编译替换对应
使用 const 关键字:const 关键字用于声明只读变量变量不能程序运行时修改
#define
处理器
下面使用 #define 处理器定义常量形式

#define
常量 常量
下面代码定义名为 PI 常量

#define PI 3.14159
程序使用常量编译器所有 PI 替换 3.14159。
具体请看下面实例

实例
#include <stdio.h>

#define LENGTH 10
#define WIDTH 5
#define NEWLINE '\n'

int main()
{

int area;

area = LENGTH * WIDTH;
printf("value of area : %d", area);
printf("%c", NEWLINE);

return 0;
}
当上面的代码编译执行产生下列结果

value of area : 50
const
关键字
可以使用 const 前缀声明指定类型常量如下

const
数据类型 常量 = 常量;
下面代码定义名为MAX_VALUE常量

const int MAX_VALUE = 100;
程序使用常量始终100,并且不能修改

const
声明常量语句完成

具体请看下面实例

实例
#include <stdio.h>

int main()
{
const int LENGTH = 10;
const int WIDTH = 5;
const char NEWLINE = '\n';
int area;

area = LENGTH * WIDTH;
printf("value of area : %d", area);
printf("%c", NEWLINE);

return 0;
}
当上面的代码编译执行产生下列结果

value of area : 50
注意常量定义大写字母形式编程习惯

#define
const 区别
#define
const 方式可以用来定义常量选择方式取决于具体需求编程习惯通常情况建议使用 const 关键字定义常量因为具有类型检查作用优势 #define 进行简单文本替换可能导致一些意外问题

#define
预处理指令 const 关键字定义常量有一些区别

替换机制:#define 进行简单文本替换 const 声明具有类型常量。#define 定义常量编译直接替换对应 const 定义常量程序运行时分配内存并且具有类型信息

类型检查:#define 进行类型检查因为只是进行简单文本替换 const 定义常量具有类型信息编译器可以进行类型检查可以帮助捕获一些潜在类型错误

作用:#define 定义常量没有作用限制定义之后整个代码中都有效 const 定义常量具有作用定义所在作用有效

调试符号使用 #define 定义常量符号不会相应条目因为只是进行文本替换使用 const 定义常量符号相应条目有助于调试可读性


11. C
存储
存储定义 C 程序变量/函数存储位置生命周期作用

这些说明放置它们修饰类型之前

下面列出 C 程序存储

auto
register
static
extern
auto
存储
auto
存储所有局部变量默认存储

定义函数中的变量认为 auto 存储意味着它们函数开始创建函数结束销毁

{
int mount;
auto int month;
}
上面实例定义带有相同存储变量,auto 只能函数 auto 只能修饰局部变量

register
存储
register
存储用于定义存储寄存器不是 RAM 中的局部变量意味着变量尺寸等于寄存器大小通常),不能应用一元 '&' 运算因为没有内存位置)。

register
存储定义存储寄存器所以变量访问速度但是不能直接地址因为不是存储 RAM 中的需要频繁访问变量使用 register 存储可以提高程序运行速度

{
register int miles;
}
寄存器用于需要快速访问变量比如计数器注意定义 'register' 并不意味着变量存储寄存器意味着变量可能存储寄存器取决于硬件实现限制

static
存储
static
存储指示编译器程序生命周期保持局部变量存在需要每次进入离开作用进行创建销毁因此使用 static 修饰局部变量可以函数调用之间保持局部变量

static
修饰可以应用全局变量 static 修饰全局变量使变量作用限制声明文件

全局声明 static 变量方法可以任何函数方法调用只要这些方法出现 static 变量方法同一文件

静态变量程序初始化一次即使函数调用多次变量不会重置

以下实例演示 static 修饰全局变量局部变量应用

实例
#include <stdio.h>

/*
函数声明 */
void func1(void);

static int count=10; /*
全局变量 - static 默认 */

int main()
{
while (count--) {
func1();
}
return 0;
}

void func1(void)
{
/* 'thingy'
'func1' 局部变量 - 初始化一次
*
每次调用函数 'func1' 'thingy' 不会重置
*/
static int thingy=5;
thingy++;
printf(" thingy
%d , count %d\n", thingy, count);
}
实例 count 作为全局变量可以函数使用,thingy 使用 static 修饰不会每次调用重置

可能现在无法理解这个实例因为已经使用函数全局变量概念目前为止进行讲解即使现在不能完全理解没有关系后续章节我们详细讲解当上面的代码编译执行产生下列结果

thingy
6 , count 9
thingy
7 , count 8
thingy
8 , count 7
thingy
9 , count 6
thingy
10 , count 5
thingy
11 , count 4
thingy
12 , count 3
thingy
13 , count 2
thingy
14 , count 1
thingy
15 , count 0
extern
存储
extern
存储用于定义其他文件声明全局变量函数使用 extern 关键字不会变量分配任何存储空间只是指示编译器变量其他文件定义

extern
存储用于提供全局变量引用全局变量所有程序文件可见使用 extern 对于无法初始化变量变量指向之前定义存储位置

多个文件定义可以其他文件使用全局变量函数可以其他文件使用 extern 得到定义变量函数引用可以这么理解,extern 用来另一文件声明全局变量函数

extern
修饰通常用于多个文件共享相同全局变量函数时候如下

第一文件:main.c

实例
#include <stdio.h>

int count ;
extern void write_extern();

int main()
{
count = 5;
write_extern();
}
第二文件:support.c

实例
#include <stdio.h>

extern int count;

void write_extern(void)
{
printf("count is %d\n", count);
}
这里第二文件中的 extern 关键字用于声明已经第一文件 main.c 定义 count。现在编译文件如下

$ gcc main.c support.c
产生 a.out 可执行程序程序执行产生下列结果

count is 5


12. C
运算
运算一种告诉编译器执行特定数学逻辑操作符号。C 语言内置丰富运算提供以下类型运算

算术运算
关系运算
逻辑运算
运算
赋值运算
杂项运算
逐一介绍算术运算关系运算逻辑运算运算赋值运算其他运算

算术运算
显示 C 语言支持所有算术运算假设变量 A 10,变量 B 20,

运算 描述 实例
+
操作数相加 A + B 得到 30
-
第一操作数减去第二操作数 A - B 得到 -10
*
操作数相乘 A * B 得到 200
/
分子除以分母 B / A 得到 2
%
取模运算整除余数 B % A 得到 0
++
运算数值增加 1 A++ 得到 11
--
运算数值减少 1 A-- 得到 9
实例
请看下面实例了解 C 语言所有算术运算

实例
#include <stdio.h>

int main()
{
int a = 21;
int b = 10;
int c ;

c = a + b;
printf("Line 1 - c
%d\n", c );
c = a - b;
printf("Line 2 - c
%d\n", c );
c = a * b;
printf("Line 3 - c
%d\n", c );
c = a / b;
printf("Line 4 - c
%d\n", c );
c = a % b;
printf("Line 5 - c
%d\n", c );
c = a++; //
赋值 1 ,c 21,a 22
printf("Line 6 - c
%d\n", c );
c = a--; //
赋值 1 ,c 22 ,a 21
printf("Line 7 - c
%d\n", c );

}
当上面的代码编译执行产生下列结果

Line 1 - c
31
Line 2 - c
11
Line 3 - c
210
Line 4 - c
2
Line 5 - c
1
Line 6 - c
21
Line 7 - c
22
以下实例演示 a++ ++a 区别

实例
#include <stdio.h>

int main()
{
int c;
int a = 10;
c = a++;
printf("
赋值运算:\n");
printf("Line 1 - c
%d\n", c );
printf("Line 2 - a
%d\n", a );
a = 10;
c = a--;
printf("Line 3 - c
%d\n", c );
printf("Line 4 - a
%d\n", a );

printf("
运算赋值:\n");
a = 10;
c = ++a;
printf("Line 5 - c
%d\n", c );
printf("Line 6 - a
%d\n", a );
a = 10;
c = --a;
printf("Line 7 - c
%d\n", c );
printf("Line 8 - a
%d\n", a );

}
以上程序执行输出结果

赋值运算
Line 1 - c
10
Line 2 - a
11
Line 3 - c
10
Line 4 - a
9
运算赋值
Line 5 - c
11
Line 6 - a
11
Line 7 - c
9
Line 8 - a
9
关系运算
显示 C 语言支持所有关系运算假设变量 A 10,变量 B 20,

运算 描述 实例
==
检查操作数是否相等如果相等条件。 (A == B)
!=
检查操作数是否相等如果相等条件。 (A != B)
>
检查操作数是否大于操作数如果条件。 (A > B)
<
检查操作数是否小于操作数如果条件。 (A < B)
>=
检查操作数是否大于等于操作数如果条件。 (A >= B)
<=
检查操作数是否小于等于操作数如果条件。 (A <= B)
实例
请看下面实例了解 C 语言所有关系运算

实例
#include <stdio.h>

int main()
{
int a = 21;
int b = 10;
int c ;

if( a == b )
{
printf("Line 1 - a
等于 b\n" );
}
else
{
printf("Line 1 - a
等于 b\n" );
}
if ( a < b )
{
printf("Line 2 - a
小于 b\n" );
}
else
{
printf("Line 2 - a
小于 b\n" );
}
if ( a > b )
{
printf("Line 3 - a
大于 b\n" );
}
else
{
printf("Line 3 - a
不大 b\n" );
}
/*
改变 a b */
a = 5;
b = 20;
if ( a <= b )
{
printf("Line 4 - a
小于等于 b\n" );
}
if ( b >= a )
{
printf("Line 5 - b
大于等于 a\n" );
}
}
当上面的代码编译执行产生下列结果

Line 1 - a
等于 b
Line 2 - a
小于 b
Line 3 - a
大于 b
Line 4 - a
小于等于 b
Line 5 - b
大于等于 a
逻辑运算
显示 C 语言支持所有关系逻辑运算假设变量 A 1,变量 B 0,

运算 描述 实例
&&
称为逻辑运算如果操作数非零条件。 (A && B)
||
称为逻辑运算如果操作数任意非零条件。 (A || B)
!
称为逻辑运算用来逆转操作数逻辑状态如果条件逻辑运算使。 !(A && B)
实例
请看下面实例了解 C 语言所有逻辑运算

实例
#include <stdio.h>

int main()
{
int a = 5;
int b = 20;
int c ;

if ( a && b )
{
printf("Line 1 -
条件\n" );
}
if ( a || b )
{
printf("Line 2 -
条件\n" );
}
/*
改变 a b */
a = 0;
b = 10;
if ( a && b )
{
printf("Line 3 -
条件\n" );
}
else
{
printf("Line 3 -
条件\n" );
}
if ( !(a && b) )
{
printf("Line 4 -
条件\n" );
}
}
当上面的代码编译执行产生下列结果

Line 1 -
条件
Line 2 -
条件
Line 3 -
条件
Line 4 -
条件
运算
运算作用执行操作。&、 | ^ 真值表如下

p q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1
假设如果 A = 60, B = 13,现在二进制格式表示它们如下

A = 0011 1100

B = 0000 1101

-----------------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A = 1100 0011

显示 C 语言支持运算假设变量 A 60,变量 B 13,

运算 描述 实例
&
操作数执行逻辑操作如果相应 1,结果 1,否则 0。

操作二进制进行""运算运算规则

0&0=0;
0&1=0;
1&0=0;
1&1=1;
(A & B)
得到 12,即为 0000 1100
|
操作数执行逻辑操作如果相应 0,结果 0,否则 1。

运算二进制进行""运算运算规则

0|0=0;
0|1=1;
1|0=1;
1|1=1;
(A | B)
得到 61,即为 0011 1101
^
操作数执行逻辑操作如果相应相同结果 0,否则 1。

运算二进制进行""运算运算规则

0^0=0;
0^1=1;
1^0=1;
1^1=0;
(A ^ B)
得到 49,即为 0011 0001
~
操作数执行逻辑操作即将 0 变为 1,1 变为 0。

运算二进制进行""运算运算规则

~1=-2;
~0=-1;
(~A )
得到 -61,即为 1100 0011,符号二进制补码形式
<<
操作数所有移动指定 n 相当于乘以 2 n 次方

二进制运算运算对象二进制全部若干左边二进制丢弃右边0)。

A << 2
得到 240,即为 1111 0000
>>
操作数所有移动指定n相当于除以 2 n 次方

二进制运算个数二进制全部若干正数 0,负数 1,右边丢弃

A >> 2
得到 15,即为 0000 1111
实例
请看下面实例了解 C 语言所有运算

实例
#include <stdio.h>

int main()
{

unsigned int a = 60; /* 60 = 0011 1100 */
unsigned int b = 13; /* 13 = 0000 1101 */
int c = 0;

c = a & b; /* 12 = 0000 1100 */
printf("Line 1 - c
%d\n", c );

c = a | b; /* 61 = 0011 1101 */
printf("Line 2 - c
%d\n", c );

c = a ^ b; /* 49 = 0011 0001 */
printf("Line 3 - c
%d\n", c );

c = ~a; /*-61 = 1100 0011 */
printf("Line 4 - c
%d\n", c );

c = a << 2; /* 240 = 1111 0000 */
printf("Line 5 - c
%d\n", c );

c = a >> 2; /* 15 = 0000 1111 */
printf("Line 6 - c
%d\n", c );
}
当上面的代码编译执行产生下列结果

Line 1 - c
12
Line 2 - c
61
Line 3 - c
49
Line 4 - c
-61
Line 5 - c
240
Line 6 - c
15
赋值运算
列出 C 语言支持赋值运算

运算 描述 实例
=
简单赋值运算右边操作数左边操作数 C = A + B A + B C
+=
赋值运算右边操作数加上左边操作数结果赋值左边操作数 C += A 相当于 C = C + A
-=
赋值运算左边操作数减去右边操作数结果赋值左边操作数 C -= A 相当于 C = C - A
*=
赋值运算右边操作数乘以左边操作数结果赋值左边操作数 C *= A 相当于 C = C * A
/=
赋值运算左边操作数除以右边操作数结果赋值左边操作数 C /= A 相当于 C = C / A
%=
赋值运算操作数赋值左边操作数 C %= A 相当于 C = C % A
<<=
赋值运算 C <<= 2 等同 C = C << 2
>>=
赋值运算 C >>= 2 等同 C = C >> 2
&=
赋值运算 C &= 2 等同 C = C & 2
^=
赋值运算 C ^= 2 等同 C = C ^ 2
|=
赋值运算 C |= 2 等同 C = C | 2
实例
请看下面实例了解 C 语言所有赋值运算

实例
#include <stdio.h>

int main()
{
int a = 21;
int c ;

c = a;
printf("Line 1 - =
运算实例,c = %d\n", c );

c += a;
printf("Line 2 - +=
运算实例,c = %d\n", c );

c -= a;
printf("Line 3 - -=
运算实例,c = %d\n", c );

c *= a;
printf("Line 4 - *=
运算实例,c = %d\n", c );

c /= a;
printf("Line 5 - /=
运算实例,c = %d\n", c );

c = 200;
c %= a;
printf("Line 6 - %%=
运算实例,c = %d\n", c );

c <<= 2;
printf("Line 7 - <<=
运算实例,c = %d\n", c );

c >>= 2;
printf("Line 8 - >>=
运算实例,c = %d\n", c );

c &= 2;
printf("Line 9 - &=
运算实例,c = %d\n", c );

c ^= 2;
printf("Line 10 - ^=
运算实例,c = %d\n", c );

c |= 2;
printf("Line 11 - |=
运算实例,c = %d\n", c );

}
当上面的代码编译执行产生下列结果

Line 1 - =
运算实例,c = 21
Line 2 - +=
运算实例,c = 42
Line 3 - -=
运算实例,c = 21
Line 4 - *=
运算实例,c = 441
Line 5 - /=
运算实例,c = 21
Line 6 - %=
运算实例,c = 11
Line 7 - <<=
运算实例,c = 44
Line 8 - >>=
运算实例,c = 11
Line 9 - &=
运算实例,c = 2
Line 10 - ^=
运算实例,c = 0
Line 11 - |=
运算实例,c = 2
杂项运算 ↦ sizeof & 三元
列出 C 语言支持其他一些重要运算包括 sizeof ? :。

运算 描述 实例
sizeof()
返回变量大小。 sizeof(a) 返回 4,其中 a 整数
&
返回变量地址。 &a; 变量实际地址
*
指向变量。 *a; 指向变量
? :
条件表达式 如果条件 ? X : 否则 Y
实例
请看下面实例了解 C 语言所有杂项运算

实例
#include <stdio.h>

int main()
{
int a = 4;
short b;
double c;
int* ptr;

/* sizeof
运算实例 */
printf("Line 1 -
变量 a 大小 = %lu\n", sizeof(a) );
printf("Line 2 -
变量 b 大小 = %lu\n", sizeof(b) );
printf("Line 3 -
变量 c 大小 = %lu\n", sizeof(c) );

/* &
* 运算实例 */
ptr = &a; /* 'ptr'
现在包含 'a' 地址 */
printf("a
%d\n", a);
printf("*ptr
%d\n", *ptr);

/*
三元运算实例 */
a = 10;
b = (a == 1) ? 20: 30;
printf( "b
%d\n", b );

b = (a == 10) ? 20: 30;
printf( "b
%d\n", b );
}
当上面的代码编译执行产生下列结果

Line 1 -
变量 a 大小 = 4
Line 2 -
变量 b 大小 = 2
Line 3 -
变量 c 大小 = 8
a
4
*ptr
4
b
30
b
20
C
中的运算优先级
运算优先级确定表达式组合影响表达式如何计算某些运算其他运算优先级例如乘除运算具有运算优先级

例如 x = 7 + 3 * 2,这里,x 赋值 13,不是 20,因为运算 * 具有 + 优先级所以首先计算乘法 3*2,然后加上 7。

运算优先级列出各个运算具有优先级运算出现表格上面具有优先级运算出现表格下面表达式优先级运算优先计算

类别 运算 结合
后缀 () [] -> . ++ - -
一元 + - ! ~ ++ - - (type)* & sizeof
乘除 * / %
+ -
移位 << >>
关系 < <= > >=
相等 == !=
AND &
XOR ^
OR |
逻辑 AND &&
逻辑 OR ||
条件 ?:
赋值 = += -= *= /= %=>>= <<= &= ^= |=
逗号 ,
实例
请看下面实例了解 C 语言运算优先级

实例
#include <stdio.h>

int main()
{
int a = 20;
int b = 10;
int c = 15;
int d = 5;
int e;

e = (a + b) * c / d; // ( 30 * 15 ) / 5
printf("(a + b) * c / d
%d\n", e );

e = ((a + b) * c) / d; // (30 * 15 ) / 5
printf("((a + b) * c) / d
%d\n" , e );

e = (a + b) * (c / d); // (30) * (15/5)
printf("(a + b) * (c / d)
%d\n", e );

e = a + (b * c) / d; // 20 + (150/5)
printf("a + (b * c) / d
%d\n" , e );

return 0;
}
当上面的代码编译执行产生下列结果

(a + b) * c / d
90
((a + b) * c) / d
90
(a + b) * (c / d)
90
a + (b * c) / d
50


13. C
判断
判断结构要求程序员指定多个评估测试条件以及条件执行语句必需条件执行语句可选)。

C
语言任何零和非空假定 true, null 假定 false。

下面大多数编程语言典型判断结构一般形式

C
中的判断语句
判断语句
C
语言提供以下类型判断语句点击链接查看语句细节

语句 描述
if
语句 if 语句 布尔表达式后跟多个语句组成
if...else
语句 if 语句 可选 else 语句,else 语句布尔表达式执行
嵌套 if 语句 可以 if else if 语句使用另一 if else if 语句
switch
语句 switch 语句允许测试变量等于多个情况
嵌套 switch 语句 可以 switch 语句使用另一 switch 语句

? :
运算(三元运算)
我们已经在前面的章节讲解 条件运算 ? :,可以用来替代 if...else 语句一般形式如下

Exp1 ? Exp2 : Exp3;
其中,Exp1、Exp2 Exp3 表达式注意冒号使用位置

?
表达式 Exp1 决定如果 Exp1 计算 Exp2 结果即为整个表达式如果 Exp1 计算 Exp3 结果即为整个表达式



实例
以下实例通过输入数字判断是否奇数偶数

实例
#include<stdio.h>

int main()
{
int num;

printf("
输入数字 : ");
scanf("%d",&num);

(num%2==0)?printf("
偶数"):printf("奇数");
}


14. C
循环
有的时候我们可能需要多次执行同一代码一般情况语句顺序执行函数中的第一语句执行接着第二语句类推

编程语言提供更为复杂执行路径多种控制结构

循环语句允许我们多次执行语句语句下面大多数编程语言循环语句流程图

循环结构
循环类型
C
语言提供以下循环类型点击链接查看类型细节

循环类型 描述
while
循环 给定条件重复语句语句执行循环主体之前测试条件
for
循环 多次执行语句序列简化管理循环变量代码
do...while
循环 除了循环主体结尾测试条件其他 while 语句类似
嵌套循环 可以 while、for do..while 循环使用多个循环
内容:C while do while 区别


循环控制语句
循环控制语句改变代码执行顺序通过可以实现代码跳转

C
提供下列循环控制语句点击链接查看语句细节

控制语句 描述
break
语句 终止循环 switch 语句程序继续执行紧接着循环 switch 语句
continue
语句 告诉循环立刻停止循环迭代重新开始下次循环迭代
goto
语句 控制转移标记语句但是建议程序使用 goto 语句

无限循环
如果条件永远循环变成无限循环。for 循环传统意义用于实现无限循环由于构成循环表达式任何不是必需可以某些条件表达式留空构成无限循环

实例
#include <stdio.h>

int main ()
{
for( ; ; )
{
printf("
循环永远执行下去!\n");
}
return 0;
}
条件表达式存在假设可以设置初始增量表达式但是一般情况,C 程序员偏向使用 for(;;) 结构表示无限循环

注意可以 Ctrl + C 终止无限循环


15. C
函数
函数一起执行任务语句 C 程序至少函数函数 main() ,所有简单程序可以定义其他额外函数

可以代码划分不同函数如何划分代码不同函数决定逻辑划分通常根据函数执行特定任务进行

函数声明告诉编译器函数名称返回类型参数函数定义提供函数实际主体

C
标准提供大量程序可以调用内置函数例如函数 strcat() 用来连接字符串函数 memcpy() 用来复制内存另一位置

函数还有叫法比如方法程序等等

定义函数
C
语言中的函数定义一般形式如下

return_type function_name( parameter list )
{
body of the function
}
C 语言函数函数函数主体组成下面列出函数所有组成部分

返回类型函数可以返回。return_type 函数返回数据类型有些函数执行所需操作返回这种情况,return_type 关键字 void。
函数名称函数实际名称函数参数列表一起成了函数签名
参数参数占位符函数调用参数传递这个称为实际参数参数列表包括函数参数类型顺序数量参数可选也就是说函数可能包含参数
函数主体函数主体包含定义函数执行任务语句
实例
以下 max() 函数源代码函数参数 num1 num2,返回个数较大那个

/*
函数返回个数较大那个 */
int max(int num1, int num2)
{
/*
局部变量声明 */
int result;

if (num1 > num2) {
result = num1;
} else {
result = num2;
}
return result;
}
函数声明
函数声明告诉编译器函数名称如何调用函数函数实际主体可以单独定义

函数声明包括以下几个部分

return_type function_name( parameter list );
针对上面定义函数 max(),以下函数声明

int max(int num1, int num2);
函数声明参数名称并不重要只有参数类型必需因此下面有效声明

int max(int, int);
文件定义函数另一文件调用函数函数声明必需这种情况应该调用函数文件顶部声明函数

调用函数
创建 C 函数定义函数什么然后通过调用函数完成定义任务

程序调用函数程序控制权转移调用函数调用函数执行定义任务函数返回语句执行到达函数结束括号程序控制权还给程序

调用函数传递所需参数如果函数返回可以存储返回例如

实例
#include <stdio.h>

/*
函数声明 */
int max(int num1, int num2);

int main ()
{
/*
局部变量定义 */
int a = 100;
int b = 200;
int ret;

/*
调用函数获取 */
ret = max(a, b);

printf( "Max value is : %d\n", ret );

return 0;
}

/*
函数返回个数较大那个 */
int max(int num1, int num2)
{
/*
局部变量声明 */
int result;

if (num1 > num2)
result = num1;
else
result = num2;

return result;
}
max() 函数 main() 函数一块编译源代码运行最后可执行文件产生下列结果

Max value is : 200
函数参数
如果函数使用参数必须声明接受参数变量这些变量称为函数形式参数

形式参数函数其他局部变量进入函数创建退出函数销毁

调用函数函数传递参数方式

调用类型 描述
调用 方法参数实际复制函数形式参数这种情况修改函数形式参数不会影响实际参数
引用调用 通过指针传递方式指向实参地址指针指向操作相当于实参本身进行操作
默认情况,C 使用用来传递参数一般来说意味着函数代码不能改变用于调用函数实际参数


16. C
作用规则
任何一种编程作用程序定义变量存在区域超过区域变量不能访问。C 语言地方可以声明变量

函数内部局部变量
所有函数外部全局变量
形式参数函数参数定义
我们看看什么局部变量全局变量形式参数

局部变量
函数内部声明变量称为局部变量它们只能函数代码内部语句使用局部变量函数外部不可下面使用局部变量实例这里所有变量 a、b c main() 函数局部变量

实例
#include <stdio.h>

int main ()
{
/*
局部变量声明 */
int a, b;
int c;

/*
实际初始化 */
a = 10;
b = 20;
c = a + b;

printf ("value of a = %d, b = %d and c = %d\n", a, b, c);

return 0;
}
全局变量
全局变量定义函数外部通常程序顶部全局变量整个程序生命周期有效任意函数内部访问全局变量

全局变量可以任何函数访问也就是说全局变量声明整个程序中都下面使用全局变量局部变量实例

实例
#include <stdio.h>

/*
全局变量声明 */
int g;

int main ()
{
/*
局部变量声明 */
int a, b;

/*
实际初始化 */
a = 10;
b = 20;
g = a + b;

printf ("value of a = %d, b = %d and g = %d\n", a, b, g);

return 0;
}
程序局部变量全局变量名称可以相同但是函数如果名字相同使用局部变量全局变量不会使用下面实例

程序局部变量全局变量
实例
#include <stdio.h>

/*
全局变量声明 */
int g = 20;

int main ()
{
/*
局部变量声明 */
int g = 10;

printf ("value of g = %d\n", g);

return 0;
}
当上面的代码编译执行产生下列结果

value of g = 10
形式参数
函数参数形式参数当作函数局部变量如果全局变量同名它们优先使用下面实例

实例
#include <stdio.h>

/*
全局变量声明 */
int a = 20;

int main ()
{
/*
函数中的局部变量声明 */
int a = 10;
int b = 20;
int c = 0;
int sum(int, int);

printf ("value of a in main() = %d\n", a);
c = sum( a, b);
printf ("value of c in main() = %d\n", c);

return 0;
}

/*
添加整数函数 */
int sum(int a, int b)
{
printf ("value of a in sum() = %d\n", a);
printf ("value of b in sum() = %d\n", b);

return a + b;
}
当上面的代码编译执行产生下列结果

value of a in main() = 10
value of a in sum() = 10
value of b in sum() = 20
value of c in main() = 30
全局变量局部变量在内中的区别

全局变量保存在内全局存储占用静态存储单元
局部变量存在只有所在函数调用动态变量分配存储单元
内容参考:C/C++ static 用法全局变量局部变量

初始化局部变量全局变量
当局变量定义系统不会初始化必须自行初始化定义全局变量系统自动初始化如下

数据类型 初始化默认
int 0
char '\0'
float 0
double 0
pointer NULL
正确初始化变量良好编程习惯否则有时候程序可能产生意想不到结果因为初始化变量导致一些在内位置已经垃圾


17. C
数组
C
语言支持数组数据结构可以存储固定大小相同类型元素顺序集合数组用来存储一系列数据往往认为一系列相同类型变量

数组声明不是声明一个个单独变量比如 runoob0、runoob1、...、runoob99,而是声明个数变量比如 runoob,然后使用 runoob[0]、runoob[1]、...、runoob[99] 代表一个个单独变量

所有数组连续内存位置组成地址对应第一元素最高地址对应最后元素

C
中的数组

数组中的特定元素可以通过索引访问第一索引 0。

C
语言允许我们使用指针处理数组使得对数操作更加灵活高效



声明数组
C 声明个数需要指定元素类型元素数量如下

type arrayName [ arraySize ];
叫做一维数组。arraySize 必须大于零的整数常量,type 可以任意有效 C 数据类型例如声明类型 double 包含 10 元素数组 balance,声明语句如下

double balance[10];
现在 balance 数组可以容纳 10 类型 double 数字

初始化数组
C 可以逐个初始化数组可以使用初始化语句如下

double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
大括号 { } 之间数目不能大于我们数组声明方括号 [ ] 指定元素数目

如果省略数组大小数组大小初始化元素个数因此如果

double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};
创建个数实例创建数组完全相同下面为数元素赋值实例

balance[4] = 50.0;
上述语句数组第五元素 50.0。所有数组是以 0 作为它们第一元素索引称为索引数组最后索引数组大小减去 1。以下上面讨论数组图形表示

数组表示

长度 10 数组第一元素索引 0,元素 runoob 索引 8:


访问数组元素
数组元素可以通过数组名称索引进行访问元素索引方括号数组名称后边例如

double salary = balance[9];
上面语句数组 10 元素 salary 变量下面实例使用上述概念声明数组数组赋值访问数组

实例
#include <stdio.h>

int main ()
{
int n[ 10 ]; /* n
包含 10 整数数组 */
int i,j;

/*
初始化数组元素 */
for ( i = 0; i < 10; i++ )
{
n[ i ] = i + 100; /*
设置元素 i i + 100 */
}

/*
输出数组元素 */
for (j = 0; j < 10; j++ )
{
printf("Element[%d] = %d\n", j, n[j] );
}

return 0;
}
当上面的代码编译执行产生下列结果

Element[0] = 100
Element[1] = 101
Element[2] = 102
Element[3] = 103
Element[4] = 104
Element[5] = 105
Element[6] = 106
Element[7] = 107
Element[8] = 108
Element[9] = 109
获取数组长度
数组长度可以使用 sizeof 运算获取数组长度例如

int numbers[] = {1, 2, 3, 4, 5};
int length = sizeof(numbers) / sizeof(numbers[0]);
实例
#include <stdio.h>

int main() {
int array[] = {1, 2, 3, 4, 5};
int length = sizeof(array) / sizeof(array[0]);

printf("
数组长度: %d\n", length);

return 0;
}
使用定义

实例
#include <stdio.h>

#define LENGTH(array) (sizeof(array) / sizeof(array[0]))

int main() {
int array[] = {1, 2, 3, 4, 5};
int length = LENGTH(array);

printf("
数组长度: %d\n", length);

return 0;
}
以上实例输出结果

数组长度: 5
数组
C 语言数组表示数组地址数组元素地址我们声明定义个数数组代表数组地址

例如以下代码

int myArray[5] = {10, 20, 30, 40, 50};
这里,myArray 数组表示整数类型数组包含 5 元素。myArray 代表着数地址第一元素地址

数组本身常量指针意味着不能改变一旦确定不能指向其他地方

我们可以使用&运算获取数组地址如下

int myArray[5] = {10, 20, 30, 40, 50};
int *ptr = &myArray[0]; //
或者直接写作 int *ptr = myArray;
上面例子,ptr 指针变量初始化 myArray 地址数组第一元素地址

需要注意虽然数组表示数组地址大多数情况数组自动转换指向数组元素指针意味着我们可以直接数组用于指针运算例如函数传递参数遍历数组

实例
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]); //
数组arr当作指针使用
}
}

int main() {
int myArray[5] = {10, 20, 30, 40, 50};
printArray(myArray, 5); //
数组传递函数
return 0;
}
上述代码,printArray 函数接受整数数组和数大小作为参数我们 myArray 数组传递函数函数内部可以使用指针一样使用 arr 数组

C
数组详解
C 数组非常重要我们需要了解有关数组细节下面列出 C 程序员必须清楚一些数组相关重要概念

概念 描述
多维数组 C 支持多维数组多维数组简单形式二维数组
传递数组函数 可以通过指定不带索引数组名称函数传递指向数组指针
函数返回数组 C 允许函数返回数组
指向数组指针 可以通过指定不带索引数组名称生成指向数组第一元素指针
静态数组动态数组 静态数组编译分配内存大小固定动态数组运行时手动分配内存大小可变


18. C enum(
枚举)
枚举 C 语言中的一种基本数据类型用于定义具有离散常量可以数据简洁易读

枚举类型通常用于程序中的相关常量名字以便程序可读性维护

定义枚举类型需要使用 enum 关键字后面跟着枚举类型名称以及大括号 {} 起来枚举常量枚举常量可以标识表示可以它们指定整数如果没有指定那么默认 0 开始递增

枚举语法定义格式

enum 
枚举 {枚举元素1,枚举元素2,……};
接下来我们例子比如星期 7 如果不用枚举我们需要使用 #define 整数定义个别

#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
这个看起来代码比较接下来我们看看使用枚举方式

enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
这样看起来是不是简洁

注意第一枚举成员默认整型 0,后续枚举成员在前成员 1。我们这个实例第一枚举成员定义 1,第二 2,以此类推

可以定义枚举类型改变枚举元素

enum season {spring, summer=3, autumn, winter};
没有指定枚举元素元素 1。 spring 0,summer 3,autumn 4,winter 5

枚举变量定义
前面我们只是声明枚举类型接下来我们看看如何定义枚举变量

我们可以通过以下方式定义枚举变量

1、
定义枚举类型定义枚举变量

enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
2、
定义枚举类型同时定义枚举变量

enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
3、
省略枚举名称直接定义枚举变量

enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
实例
#include <stdio.h>

enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};

int main()
{
enum DAY day;
day = WED;
printf("%d",day);
return 0;
}
以上实例输出结果

3
C 语言枚举类型当做 int 或者 unsigned int 类型处理所以按照 C 语言规范没有办法遍历枚举类型

不过一些特殊情况枚举类型必须连续可以现有条件遍历

以下实例使用 for 遍历枚举元素

实例
#include <stdio.h>

enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
int main()
{
//
遍历枚举元素
for (day = MON; day <= SUN; day++) {
printf("
枚举元素:%d \n", day);
}
}
以上实例输出结果

枚举元素:1
枚举元素:2
枚举元素:3
枚举元素:4
枚举元素:5
枚举元素:6
枚举元素:7
以下枚举类型不连续这种枚举无法遍历

enum
{
ENUM_0,
ENUM_10 = 10,
ENUM_11
};
枚举 switch 中的使用

实例
#include <stdio.h>
#include <stdlib.h>
int main()
{

enum color { red=1, green, blue };

enum color favorite_color;

/*
用户输入数字选择颜色 */
printf("
输入喜欢颜色: (1. red, 2. green, 3. blue): ");
scanf("%u", &favorite_color);

/*
输出结果 */
switch (favorite_color)
{
case red:
printf("
喜欢颜色红色");
break;
case green:
printf("
喜欢颜色绿色");
break;
case blue:
printf("
喜欢颜色蓝色");
break;
default:
printf("
没有选择喜欢颜色");
}

return 0;
}
以上实例输出结果

输入喜欢颜色: (1. red, 2. green, 3. blue): 1
喜欢颜色红色
整数转换枚举
以下实例整数转换枚举

实例
#include <stdio.h>
#include <stdlib.h>

int main()
{

enum day
{
saturday,
sunday,
monday,
tuesday,
wednesday,
thursday,
friday
} workday;

int a = 1;
enum day weekend;
weekend = ( enum day ) a; //
类型转换
//weekend = a; //
错误
printf("weekend:%d",weekend);
return 0;
}
以上实例输出结果

weekend:1


19. C
指针
学习 C 语言指针简单有趣通过指针可以简化一些 C 编程任务执行还有一些任务动态内存分配没有指针无法执行所以想要成为优秀 C 程序员学习指针必要

正如知道变量内存位置内存位置定义使用 & 运算访问地址表示在内中的地址

请看下面实例输出定义变量地址

实例
#include <stdio.h>

int main ()
{
int var_runoob = 10;
int *p; //
定义指针变量
p = &var_runoob;

printf("var_runoob
变量地址: %p\n", p);
return 0;
}
当上面的代码编译执行产生下列结果

var_runoob
变量地址: 0x7ffeeaae08d8


通过上面实例我们了解什么内存地址以及如何访问接下来我们看看什么指针

什么指针
指针也就是内存地址指针变量用来存放内存地址变量其他变量常量一样必须使用指针存储其他变量地址之前进行声明指针变量声明一般形式

type *var_name;
这里,type 指针类型必须有效 C 数据类型,var_name 指针变量名称用来声明指针星号 * 乘法使用星号相同但是这个语句星号用来指定变量指针以下有效指针声明

int *ip; /*
整型指针 */
double *dp; /*
double 指针 */
float *fp; /*
浮点型指针 */
char *ch; /*
字符指针 */
所有实际数据类型不管整型浮点型字符还是其他数据类型对应指针类型一样代表内存地址十六进制

不同数据类型指针之间唯一不同指针指向变量常量数据类型不同

如何使用指针
使用指针频繁进行以下几个操作定义指针变量变量地址赋值指针访问指针变量地址这些通过使用一元运算 * 返回位于操作数指定地址变量下面实例涉及到了这些操作

实例
#include <stdio.h>

int main ()
{
int var = 20; /*
实际变量声明 */
int *ip; /*
指针变量声明 */

ip = &var; /*
指针变量存储 var 地址 */

printf("var
变量地址: %p\n", &var );

/*
指针变量存储地址 */
printf("ip
变量存储地址: %p\n", ip );

/*
使用指针访问 */
printf("*ip
变量: %d\n", *ip );

return 0;
}
当上面的代码编译执行产生下列结果

var
变量地址: 0x7ffeeef168d8
ip
变量存储地址: 0x7ffeeef168d8
*ip
变量: 20
C
中的 NULL 指针
变量声明时候如果没有确切地址可以赋值指针变量 NULL 良好编程习惯 NULL 指针称为指针

NULL
指针定义标准中的零的常量请看下面程序

实例
#include <stdio.h>

int main ()
{
int *ptr = NULL;

printf("ptr
地址 %p\n", ptr );

return 0;
}
当上面的代码编译执行产生下列结果

ptr
地址 0x0
大多数操作系统程序允许访问地址 0 内存因为内存操作系统保留然而内存地址 0 特别重要意义表明指针指向访问内存位置按照惯例如果指针包含空值),假定指向任何东西

检查指针可以使用 if 语句如下

if(ptr) /*
如果 p 非空完成 */
if(!ptr) /*
如果 p 完成 */
C
指针详解
C 指针相关概念这些概念简单但是重要下面列出 C 程序员必须清楚一些指针相关重要概念

概念 描述
指针算术运算 可以指针进行算术运算:++、--、+、-
指针数组 可以定义用来存储指针数组
指向指针指针 C 允许指向指针指针
传递指针函数 通过引用地址传递参数使传递参数调用函数改变
函数返回指针 C 允许函数返回指针局部变量静态变量动态内存分配


20.
函数指针
函数指针指向函数指针变量

通常我们指针变量指向整型字符数组变量函数指针指向函数

函数指针可以一般函数一样用于调用函数传递参数

函数指针类型声明

typedef int (*fun_ptr)(int,int); //
声明指向同样参数返回函数指针类型
实例
以下实例声明函数指针变量 p,指向函数 max:

实例
#include <stdio.h>

int max(int x, int y)
{
return x > y ? x : y;
}

int main(void)
{
/* p
函数指针 */
int (* p)(int, int) = & max; // &
可以省略
int a, b, c, d;

printf("
输入数字:");
scanf("%d %d %d", & a, & b, & c);

/*
直接调用函数等价,d = max(max(a, b), c) */
d = p(p(a, b), c);

printf("
数字: %d\n", d);

return 0;
}
编译执行输出结果如下

输入数字:1 2 3
数字: 3
回调函数
函数指针作为函数参数
函数指针变量可以作为函数参数使用回调函数就是通过函数指针调用函数

简单回调函数别人函数执行调用实现函数

以下来自知乎作者解说

商店买东西刚好东西没有于是店员那里留下电话几天店员电话然后接到电话去取这个例子电话号码回调函数电话留给店员登记回调函数后来叫做触发回调关联事件店员打电话叫做调用回调函数去取叫做响应回调事件

实例
实例 populate_array() 函数定义参数其中参数函数指针通过函数设置数组

实例我们定义回调函数 getNextRandomValue(),返回随机作为函数指针传递 populate_array() 函数

populate_array()
调用 10 回调函数回调函数返回赋值数组

实例
#include <stdlib.h>
#include <stdio.h>

void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}

//
获取随机
int getNextRandomValue(void)
{
return rand();
}

int main(void)
{
int myarray[10];
/* getNextRandomValue
不能括号否则无法编译因为加上括号之后相当于传入参数传入 int , 不是函数指针*/
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
编译执行输出结果如下

16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709


21. C
字符串
C 语言字符串实际上使用字符 \0 结尾一维字符数组因此,\0 用于标记字符串结束

字符(Null character)又称结束缩写 NUL,数值 0 控制字符,\0 转义字符意思告诉编译器不是字符 0,而是字符

下面声明初始化创建 RUNOOB 字符串由于数组末尾存储字符 \0,所以字符数组大小单词 RUNOOB 字符

char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
依据数组初始化规则可以上面语句以下语句

char site[] = "RUNOOB";
以下 C/C++ 定义字符串内存表示

C/C++
中的字符串表示
其实需要 null 字符字符串常量末尾。C 编译器初始化数组自动 \0 字符串末尾我们尝试输出上面字符串

实例
#include <stdio.h>

int main ()
{
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};

printf("
菜鸟教程: %s\n", site );

return 0;
}
当上面的代码编译执行产生下列结果

菜鸟教程: RUNOOB
C
大量操作字符串函数

序号 函数 & 目的
1 strcpy(s1, s2);
复制字符串 s2 字符串 s1。
2 strcat(s1, s2);
连接字符串 s2 字符串 s1 末尾
3 strlen(s1);
返回字符串 s1 长度
4 strcmp(s1, s2);
如果 s1 s2 相同返回 0;如果 s1<s2 返回小于 0;如果 s1>s2 返回大于 0。
5 strchr(s1, ch);
返回指针指向字符串 s1 字符 ch 第一次出现位置
6 strstr(s1, s2);
返回指针指向字符串 s1 字符串 s2 第一次出现位置
下面实例使用上述一些函数

实例
#include <stdio.h>
#include <string.h>

int main ()
{
char str1[14] = "runoob";
char str2[14] = "google";
char str3[14];
int len ;

/*
复制 str1 str3 */
strcpy(str3, str1);
printf("strcpy( str3, str1) : %s\n", str3 );

/*
连接 str1 str2 */
strcat( str1, str2);
printf("strcat( str1, str2): %s\n", str1 );

/*
连接,str1 长度 */
len = strlen(str1);
printf("strlen(str1) : %d\n", len );

return 0;
}
当上面的代码编译执行产生下列结果

strcpy( str3, str1) : runoob
strcat( str1, str2): runoobgoogle
strlen(str1) : 12
可以 C 标准找到字符串相关函数


22. C
结构
C
数组允许定义存储相同类型数据变量结构 C 编程一种用户自定义数据类型允许存储不同类型数据

结构中的数据成员可以基本数据类型 int、float、char ),可以其他结构类型指针类型

结构用于表示记录假设想要跟踪图书馆书本动态可能需要跟踪下列属性

Title
Author
Subject
Book ID
定义结构
结构定义关键字 struct 结构组成结构可以根据需要自行定义

struct
语句定义包含多个成员数据类型,struct 语句格式如下

struct tag {
member-list
member-list
member-list
...
} variable-list ;
tag
结构标签

member-list
标准变量定义比如 int i; 或者 float f;,或者其他有效变量定义

variable-list
结构变量定义结构末尾最后分号之前可以指定多个结构变量下面声明 Book 结构方式

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
一般情况,tag、member-list、variable-list 3 部分至少出现 2 以下实例

//
声明声明拥有3成员结构分别整型a,字符b精度c
//
同时明了结构变量s1
//
这个结构没有标明标签
struct
{
int a;
char b;
double c;
} s1;

//
声明声明拥有3成员结构分别整型a,字符b精度c
//
结构标签名为SIMPLE,没有声明变量
struct SIMPLE
{
int a;
char b;
double c;
};
//
SIMPLE标签结构另外声明变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;

//
可以typedef创建类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//
现在可以Simple2作为类型声明结构变量
Simple2 u1, u2[20], *u3;
上面声明第一第二声明编译器当作完全不同类型即使他们成员列表一样如果 t3=&s1,非法

结构成员可以包含其他结构可以包含指向自己结构类型指针通常这种指针应用为了实现一些高级数据结构链表

//
结构声明包含其他结构
struct COMPLEX
{
char string[100];
struct SIMPLE a;
};

//
结构声明包含指向自己类型指针
struct NODE
{
char string[100];
struct NODE *next_node;
};
如果结构互相包含需要其中结构进行完整声明如下

struct B; //
结构B进行完整声明

//
结构A包含指向结构B指针
struct A
{
struct B *partner;
//other members;
};

//
结构B包含指向结构A指针A声明,B随之进行声明
struct B
{
struct A *partner;
//other members;
};
结构变量初始化
其它类型变量一样结构变量可以定义指定初始

实例
#include <stdio.h>

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C
语言", "RUNOOB", "编程语言", 123456};

int main()
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}
执行输出结果

title : C
语言
author: RUNOOB
subject:
编程语言
book_id: 123456
访问结构成员
为了访问结构成员我们使用成员访问运算(.)。成员访问运算结构变量名称我们访问结构成员之间句号可以使用 struct 关键字定义结构类型变量面的实例演示结构用法

实例
#include <stdio.h>
#include <string.h>

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};

int main( )
{
struct Books Book1; /*
声明 Book1,类型 Books */
struct Books Book2; /*
声明 Book2,类型 Books */

/* Book1
详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;

/* Book2
详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;

/*
输出 Book1 信息 */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);

/*
输出 Book2 信息 */
printf( "Book 2 title : %s\n", Book2.title);
printf( "Book 2 author : %s\n", Book2.author);
printf( "Book 2 subject : %s\n", Book2.subject);
printf( "Book 2 book_id : %d\n", Book2.book_id);

return 0;
}
当上面的代码编译执行产生下列结果

Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700
结构作为函数参数
可以结构作为函数参数传参方式其他类型变量指针类似可以使用上面实例中的方式访问结构变量

实例
#include <stdio.h>
#include <string.h>

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};

/*
函数声明 */
void printBook( struct Books book );
int main( )
{
struct Books Book1; /*
声明 Book1,类型 Books */
struct Books Book2; /*
声明 Book2,类型 Books */

/* Book1
详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;

/* Book2
详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;

/*
输出 Book1 信息 */
printBook( Book1 );

/*
输出 Book2 信息 */
printBook( Book2 );

return 0;
}
void printBook( struct Books book )
{
printf( "Book title : %s\n", book.title);
printf( "Book author : %s\n", book.author);
printf( "Book subject : %s\n", book.subject);
printf( "Book book_id : %d\n", book.book_id);
}
当上面的代码编译执行产生下列结果

Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700
指向结构指针
可以定义指向结构指针方式定义指向其他类型变量指针相似如下

struct Books *struct_pointer;
现在可以上述定义指针变量存储结构变量地址为了查找结构变量地址 & 运算结构名称前面如下

struct_pointer = &Book1;
为了使用指向结构指针访问结构成员必须使用 -> 运算如下

struct_pointer->title;
我们使用结构指针上面实例有助于理解结构指针概念

实例
#include <stdio.h>
#include <string.h>

struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};

/*
函数声明 */
void printBook( struct Books *book );
int main( )
{
struct Books Book1; /*
声明 Book1,类型 Books */
struct Books Book2; /*
声明 Book2,类型 Books */

/* Book1
详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;

/* Book2
详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;

/*
通过 Book1 地址输出 Book1 信息 */
printBook( &Book1 );

/*
通过 Book2 地址输出 Book2 信息 */
printBook( &Book2 );

return 0;
}
void printBook( struct Books *book )
{
printf( "Book title : %s\n", book->title);
printf( "Book author : %s\n", book->author);
printf( "Book subject : %s\n", book->subject);
printf( "Book book_id : %d\n", book->book_id);
}
当上面的代码编译执行产生下列结果

Book title : C Programming
Book author : Nuha Ali
Book subject : C Programming Tutorial
Book book_id : 6495407
Book title : Telecom Billing
Book author : Zara Ali
Book subject : Telecom Billing Tutorial
Book book_id : 6495700
结构大小计算
C
语言我们可以使用 sizeof 运算计算结构大小,sizeof 返回给定类型变量字节大小

对于结构,sizeof 返回结构字节数包括所有成员变量大小以及可能填充字节

以下实例演示如何计算结构大小

实例
#include <stdio.h>

struct Person {
char name[20];
int age;
float height;
};

int main() {
struct Person person;
printf("
结构 Person 大小: %zu 字节\n", sizeof(person));
return 0;
}
以上实例我们定义名为 Person 结构包含字符数组 name、整数 age 浮点数 height。

main 函数我们声明 Person 类型变量 person,然后使用 sizeof 运算获取 person 结构大小

最后我们使用 printf 函数打印结构大小输出结果如下

结构 Person 大小: 28 字节
注意结构大小可能受到编译器优化对齐规则影响编译器可能结构插入一些额外填充字节对齐结构成员变量提高内存访问效率因此结构实际大小可能大于成员变量大小总和如果需要确切了解结构内存布局对齐方式可以使用 offsetof __attribute__((packed)) 属性进一步控制查询结构大小对齐方式


23. C
共用
共用一种特殊数据类型允许相同内存位置存储不同数据类型可以定义带有成员共用但是任何时候只能成员带有共用提供一种使用相同内存位置有效方式

定义共用
为了定义共用必须使用 union 语句方式定义结构类似。union 语句定义数据类型带有多个成员。union 语句格式如下

union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
union tag
可选 member definition 标准变量定义比如 int i; 或者 float f; 或者其他有效变量定义共用定义末尾最后分号之前可以指定多个共用变量可选下面定义名为 Data 共用类型成员 i、f str:

union Data
{
int i;
float f;
char str[20];
} data;
现在,Data 类型变量可以存储整数浮点数或者字符串意味着变量相同内存位置可以存储多个多种类型数据可以根据需要共用体内使用任何内置或者用户自定义数据类型

共用占用内存足够存储共用成员例如上面实例,Data 占用 20 字节内存空间因为各个成员字符串占用空间下面实例显示上面共用占用内存大小

实例
#include <stdio.h>
#include <string.h>

union Data
{
int i;
float f;
char str[20];
};

int main( )
{
union Data data;

printf( "Memory size occupied by data : %d\n", sizeof(data));

return 0;
}
当上面的代码编译执行产生下列结果

Memory size occupied by data : 20
访问共用成员
为了访问共用成员我们使用成员访问运算(.)。成员访问运算共用变量名称我们访问共用成员之间句号可以使用 union 关键字定义共用类型变量下面实例演示共用用法

实例
#include <stdio.h>
#include <string.h>

union Data
{
int i;
float f;
char str[20];
};

int main( )
{
union Data data;

data.i = 10;
data.f = 220.5;
strcpy( data.str, "C Programming");

printf( "data.i : %d\n", data.i);
printf( "data.f : %f\n", data.f);
printf( "data.str : %s\n", data.str);

return 0;
}
当上面的代码编译执行产生下列结果

data.i : 1917853763
data.f : 4122360580327794860452759994368.000000
data.str : C Programming
这里我们可以看到共用 i f 成员损坏因为最后变量占用内存位置 str 成员能够完好输出原因现在我们相同实例我们同一时间使用变量演示使用共用主要目的

实例
#include <stdio.h>
#include <string.h>

union Data
{
int i;
float f;
char str[20];
};

int main( )
{
union Data data;

data.i = 10;
printf( "data.i : %d\n", data.i);

data.f = 220.5;
printf( "data.f : %f\n", data.f);

strcpy( data.str, "C Programming");
printf( "data.str : %s\n", data.str);

return 0;
}
当上面的代码编译执行产生下列结果

data.i : 10
data.f : 220.500000
data.str : C Programming
这里所有成员完好输出因为同一时间成员


24. C

C
语言(bit-field)一种特殊结构成员允许我们成员进行定义指定占用

如果程序结构包含多个开关变量变量 TRUE/FALSE,如下

struct
{
unsigned int widthValidated;
unsigned int heightValidated;
} status;
这种结构需要 8 字节内存空间实际上变量我们存储 0 1,这种情况,C 语言提供一种利用内存空间方式如果结构使用这样变量可以定义变量宽度告诉编译器使用这些字节例如上面结构可以

struct
{
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status;
现在上面结构,status 变量占用 4 字节内存空间但是只有 2 用来存储如果 32 变量变量宽度 1 那么 status 结构使用 4 字节只要多用变量如果使用 33 变量那么分配内存存储 33 变量这个时候开始使用 8 字节我们看看下面实例理解这个概念

实例
#include <stdio.h>
#include <string.h>

/*
定义简单结构 */
struct
{
unsigned int widthValidated;
unsigned int heightValidated;
} status1;

/*
定义结构 */
struct
{
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status2;

int main( )
{
printf( "Memory size occupied by status1 : %d\n", sizeof(status1));
printf( "Memory size occupied by status2 : %d\n", sizeof(status2));

return 0;
}
当上面的代码编译执行产生下列结果

Memory size occupied by status1 : 8
Memory size occupied by status2 : 4
特点使用方法如下

定义可以指定成员宽度成员占用
宽度不能超过数据类型大小因为必须适应使用整数类型
数据类型可以 int、unsigned int、signed int 整数类型可以枚举类型
可以单独使用可以其他成员一起组成结构
访问通过运算(.)实现普通结构成员访问方式相同
声明
有些信息存储并不需要占用完整字节几个二进制例如存放开关只有 0 1 状态 1 二进即可为了节省存储空间使处理简便,C 语言提供一种数据结构称为""""。

所谓""字节中的二进分为几个不同区域说明区域域名允许程序域名进行操作这样可以几个不同对象字节二进制表示

典型实例

1 二进存放开关只有 0 1 状态
读取外部文件格式——可以读取准的文件格式例如:9 整数
定义变量说明
定义结构定义相仿形式

struct
结构
{

列表

};
其中列表形式

type [member_name] : width ;
下面有关变量元素描述

元素 描述
type
只能 int(整型),unsigned int(无符号整型),signed int(符号整型) 种类决定如何解释
member_name
名称
width
数量宽度必须小于等于指定类型宽度
带有预定义宽度变量称为可以存储多于 1 例如需要变量存储 0 7 可以定义宽度 3 如下

struct
{
unsigned int age : 3;
} Age;
上面结构定义指示 C 编译器,age 变量使用 3 存储这个如果试图使用超过 3 无法完成

struct bs{
int a:8;
int b:2;
int c:6;
}data;
以上代码定义名为 struct bs 结构,data bs 结构变量字节

对于它们宽度不能超过数据类型大小这种情况,int 类型大小通常 4 字节(32)。

相邻字段类型相同小于类型 sizeo f大小后面字段紧邻字段存储直到不能容纳为止

我们实例

struct packed_struct {
unsigned int f1:1;
unsigned int f2:1;
unsigned int f3:1;
unsigned int f4:1;
unsigned int type:4;
unsigned int my_int:9;
} pack;
以上代码定义名为 packed_struct 结构其中包含成员变量,pack packed_struct 结构变量

这里,packed_struct 包含 6 成员 1 标识 f1..f4、 4 type 9 my_int。

我们下面实例

实例 1
#include <stdio.h>

struct packed_struct {
unsigned int f1 : 1; // 1

unsigned int f2 : 1; // 1

unsigned int f3 : 1; // 1

unsigned int f4 : 1; // 1

unsigned int type : 4; // 4

unsigned int my_int : 9; // 9

};

int main() {
struct packed_struct pack;

pack.f1 = 1;
pack.f2 = 0;
pack.f3 = 1;
pack.f4 = 0;
pack.type = 7;
pack.my_int = 255;

printf("f1: %u\n", pack.f1);
printf("f2: %u\n", pack.f2);
printf("f3: %u\n", pack.f3);
printf("f4: %u\n", pack.f4);
printf("type: %u\n", pack.type);
printf("my_int: %u\n", pack.my_int);

return 0;
}
以上实例定义名为 packed_struct 结构其中包含个位成员

main 函数创建 packed_struct 类型结构变量 pack,分别个位成员赋值

然后使用 printf 语句打印个位成员

输出结果

f1: 1
f2: 0
f3: 1
f4: 0
type: 7
my_int: 255
实例 2
#include <stdio.h>
#include <string.h>

struct
{
unsigned int age : 3;
} Age;

int main( )
{
Age.age = 4;
printf( "Sizeof( Age ) : %d\n", sizeof(Age) );
printf( "Age.age : %d\n", Age.age );

Age.age = 7;
printf( "Age.age : %d\n", Age.age );

Age.age = 8; //
二进制表示 1000 超出
printf( "Age.age : %d\n", Age.age );

return 0;
}
当上面的代码编译带有警告当上面的代码执行产生下列结果

Sizeof( Age ) : 4
Age.age : 4
Age.age : 7
Age.age : 0
计算字节数

实例
#include <stdio.h>

struct example1 {
int a : 4;
int b : 5;
int c : 7;
};

int main() {
struct example1 ex1;

printf("Size of example1: %lu bytes\n", sizeof(ex1));

return 0;
}
以上实例,example1 结构包含个位成员 a,b c,它们分别占用 4 、5 7

通过 sizeof 运算计算 example1 结构字节数输出结果

Size of example1: 4 bytes
对于定义以下几点说明

个位存储同一字节如一字节空间不够存放另一单元存放可以有意使单元开始例如

struct bs{
unsigned a:4;
unsigned :4; /*
*/
unsigned b:4; /*
单元开始存放 */
unsigned c:4
}
这个定义,a 第一字节 4 4 0 表示使用,b 第二字节开始占用 4 ,c 占用 4

宽度不能超过依附数据类型长度成员变量类型这个类型限制成员变量长度,: 后面数字不能超过这个长度

可以无名这时用来填充调整位置无名不能使用例如

struct k{
int a:1;
int :2; /*
2 不能使用 */
int b:3;
int c:2;
};
以上分析可以看出本质上就是一种结构类型不过成员二进分配

使用
使用结构成员使用相同一般形式

变量.域名
变量->域名
允许各种格式输出

请看下面实例

实例
#include <stdio.h>

int main(){
struct bs{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1; /*
赋值注意赋值不能超过允许范围) */
bit.b=7; /*
赋值注意赋值不能超过允许范围) */
bit.c=15; /*
赋值注意赋值不能超过允许范围) */
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /*
整型格式输出内容 */
pbit=&bit; /*
变量 bit 地址送给指针变量 pbit */
pbit->a=0; /*
指针方式 a 重新赋值 0 */
pbit->b&=3; /*
使用复合运算 "&=",相当于:pbit->b=pbit->b&3, b 原有 7, 3 运算结果 3(111&011=011,十进制 3) */
pbit->c|=1; /*
使用复合运算"|=",相当于:pbit->c=pbit->c|1,结果 15 */
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /*
指针方式输出 */
}
程序定义结构 bs,个位 a、b、c。说明 bs 类型变量 bit 指向 bs 类型指针变量 pbit。表示可以使用指针


25. C typedef
C
语言提供 typedef 关键字可以使用类型名字下面实例单字数字定义术语 BYTE:

typedef unsigned char BYTE;
这个类型定义之后标识 BYTE 作为类型 unsigned char 缩写例如

BYTE b1, b2;
按照惯例定义大写字母以便提醒用户类型名称象征性缩写可以使用小写字母如下

typedef unsigned char byte;
可以使用 typedef 用户自定义数据类型名字例如可以结构使用 typedef 定义数据类型名字然后使用这个数据类型直接定义结构变量如下

实例
#include <stdio.h>
#include <string.h>

typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} Book;

int main( )
{
Book book;

strcpy( book.title, "C
教程");
strcpy( book.author, "Runoob");
strcpy( book.subject, "
编程语言");
book.book_id = 12345;

printf( "
标题 : %s\n", book.title);
printf( "
作者 : %s\n", book.author);
printf( "
类目 : %s\n", book.subject);
printf( "
ID : %d\n", book.book_id);

return 0;
}
当上面的代码编译执行产生下列结果

标题 : C 教程
作者 : Runoob
类目 : 编程语言
ID : 12345
typedef vs #define
#define
C 指令用于各种数据类型定义别名 typedef 类似但是它们以下几点不同

typedef
限于类型定义符号名称,#define 不仅可以类型定义别名数值定义别名比如可以定义 1 ONE。
typedef
编译器执行解释,#define 语句编译器进行处理
下面 #define 简单用法

实例
#include <stdio.h>

#define TRUE 1
#define FALSE 0

int main( )
{
printf( "TRUE
: %d\n", TRUE);
printf( "FALSE
: %d\n", FALSE);

return 0;
}
当上面的代码编译执行产生下列结果

TRUE
: 1
FALSE
: 0

25. C
输入 & 输出
我们提到输入意味着程序填充一些数据输入可以是以文件形式从命进行。C 语言提供一系列内置函数读取给定输入根据需要填充程序

我们提到输出意味着屏幕打印机任意文件显示一些数据。C 语言提供一系列内置函数输出数据计算机屏幕保存数据文本文件二进制文件

标准文件
C
语言所有设备当作文件所以设备比如显示器处理方式文件相同以下文件程序执行自动打开以便访问键盘屏幕

标准文件 文件指针 设备
标准输入 stdin 键盘
标准输出 stdout 屏幕
标准错误 stderr 屏幕
文件指针访问文件方式讲解如何键盘读取以及如何结果输出屏幕

C
语言中的 I/O (输入/输出) 通常使用 printf() scanf() 函数

scanf()
函数用于标准输入键盘读取格式化, printf() 函数发送格式化输出标准输出屏幕)。

printf()
函数
printf()
函数用于格式化数据输出标准输出设备通常屏幕)。

语法

int printf(const char *format, ...);
参数

format:
格式化字符串指定输出格式

...:
可变参数列表根据格式化字符串中的格式说明提供输出数据

实例
#include <stdio.h> //
执行 printf() 函数需要
int main()
{
printf("
菜鸟教程"); //显示引号中的内容
return 0;
}
编译以上程序输出结果

菜鸟教程
实例解析

所有 C 语言程序需要包含 main() 函数代码 main() 函数开始执行
printf()
用于格式化输出屏幕。printf() 函数 "stdio.h" 文件声明
stdio.h
个头文件 (标准输入输出文件) and #include 预处理命令用来引入文件编译器遇到 printf() 函数如果没有找到 stdio.h 文件发生编译错误
return 0;
语句用于表示退出程序
%d
格式化输出整数
#include <stdio.h>
int main()
{
int testInteger = 5;
printf("Number = %d", testInteger);
return 0;
}
编译以上程序输出结果

Number = 5
printf() 函数引号使用 "%d" (整型) 匹配整型变量 testInteger 输出屏幕

%f
格式化输出浮点型数据
#include <stdio.h>
int main()
{
float f;
printf("Enter a number: ");
// %f
匹配浮点型数据
scanf("%f",&f);
printf("Value = %f", f);
return 0;
}
scanf()
函数
scanf()
函数用于标准输入设备通常键盘读取格式化输入

语法

int scanf(const char *format, ...);
参数

format:
格式化字符串指定输入格式

...:
可变参数列表根据格式化字符串中的格式说明提供存储输入数据变量地址

实例
#include <stdio.h>

int main() {
int a;
float b;
printf("Enter an integer and a float: ");
scanf("%d %f", &a, &b);
printf("You entered: %d and %.2f\n", a, b);
return 0;
}
执行以上代码然后输入

10 3.14
输出

You entered: 10 and 3.14
字符输入输出
getchar() & putchar()
函数
int getchar(void)
函数屏幕读取下一个字符返回整数这个函数同一时间读取单一字符可以循环使用这个方法以便屏幕读取多个字符

int putchar(int c)
函数字符输出屏幕返回相同字符这个函数同一时间输出单一字符可以循环使用这个方法以便屏幕输出多个字符

请看下面实例

实例
#include <stdio.h>

int main( )
{
int c;

printf( "Enter a value :");
c = getchar( );

printf( "\nYou entered: ");
putchar( c );
printf( "\n");
return 0;
}
当上面的代码编译执行等待输入一些文本输入文本按下回车键程序继续读取单一字符显示如下

$./a.out
Enter a value :runoob

You entered: r
字符串输入输出
gets()
fgets() 函数
gets()
函数用于标准输入设备读取一行字符串推荐使用因为容易导致缓冲溢出推荐使用 fgets() 函数

语法

char *fgets(char *str, int n, FILE *stream);
参数

str:
指向字符数组指针用于存储读取字符串

n:
读取字符包括字符\0)。

stream:
文件通常使用stdin表示标准输入

实例
#include <stdio.h>

int main() {
char str[100];
printf("Enter a string: ");
fgets(str, sizeof(str), stdin);
printf("You entered: %s", str);
return 0;
}
puts()
函数
puts()
函数用于字符串输出标准输出设备自动末尾添加换行

语法

int puts(const char *str);
参数

str:
输出字符串

返回

成功返回负值失败返回EOF。

实例
#include <stdio.h>

int main() {
char str[] = "Hello, World!";
puts(str);
return 0;
}
输出

Hello, World!
fputs()
函数
fputs()
函数用于字符串输出指定标准输出文件),不会自动字符串末尾添加换行

语法

int fputs(const char *str, FILE *stream);
参数

str:
输出字符串字符 \0 结尾字符数组)。

stream:
指定输出可以标准输出(stdout)、文件

返回

成功返回负值通常输出字符)。

失败返回 EOF。

特点

添加换行:fputs() 不会输出字符串自动添加换行

灵活输出:fputs() 可以输出任意标准输出文件

实例
#include <stdio.h>

int main() {
char str[] = "Hello, World!";
fputs(str, stdout); //
输出 "Hello, World!",换行
return 0;
}
puts()
fputs() 区别
特性 puts() fputs()
换行 自动字符串末尾添加换行 添加换行
输出 只能输出标准输出屏幕可以输出任意文件屏幕
参数 需要字符串参数 需要字符串参数参数
返回 成功返回负值失败返回 EOF 成功返回负值失败返回 EOF
scanf()
printf() 函数
int scanf(const char *format, ...)
函数标准输入 stdin 读取输入根据提供 format 浏览输入

int printf(const char *format, ...)
函数输出标准输出 stdout ,根据提供格式产生输出

format
可以简单常量字符串但是可以分别指定 %s、%d、%c、%f 输出读取字符串整数字符浮点数还有许多其他格式选项可以根据需要使用了解完整细节可以查看这些函数参考手册现在我们通过下面这个简单实例加深理解

实例
#include <stdio.h>
int main( ) {

char str[100];
int i;

printf( "Enter a value :");
scanf("%s %d", str, &i);

printf( "\nYou entered: %s %d ", str, i);
printf("\n");
return 0;
}
当上面的代码编译执行等待输入一些文本输入文本按下回车键程序继续读取输入显示如下

$./a.out
Enter a value :runoob 123

You entered: runoob 123
这里应当指出,scanf() 期待输入格式 %s %d 相同意味着必须提供有效输入比如 "string integer",如果提供 "string string" "integer integer",认为错误输入另外读取字符串只要遇到空格,scanf() 停止读取所以 "this is test" scanf() 字符串

文件输入输出
C
语言提供文件输入输出功能允许文件读取数据文件数据

fopen()
函数
fopen()
函数用于打开文件

语法

FILE *fopen(const char *filename, const char *mode);
参数

filename:
打开文件

mode:
打开文件模式"r"(只读)、"w"()、"a"(追加

返回

成功返回指向FILE对象指针失败返回NULL。

fclose()
函数
fclose()
函数用于关闭打开文件

语法

int fclose(FILE *stream);
参数

stream:
指向FILE对象指针

返回

成功返回0,失败返回EOF。

实例
#include <stdio.h>

int main() {
FILE *file;
file = fopen("example.txt", "w"); //
打开文件用于
if (file != NULL) {
fprintf(file, "Hello, world!\n"); //
文件
fclose(file); //
关闭文件
}

char buffer[100];
file = fopen("example.txt", "r"); //
打开文件用于读取
if (file != NULL) {
fscanf(file, "%s", buffer); //
读取数据
printf("Read from file: %s\n", buffer);
fclose(file); //
关闭文件
}
return 0;
}


26. C
文件读写
我们讲解 C 语言处理标准输入输出设备我们介绍 C 程序员如何创建打开关闭文本文件二进制文件

文件无论文本文件还是二进制文件代表一系列字节。C 语言不仅提供访问顶层函数提供底层(OS)用来处理存储设备文件讲解文件管理重要调用

打开文件
可以使用 fopen( ) 函数创建文件或者打开有的文件这个调用初始化类型 FILE 对象类型 FILE 包含所有用来控制必要信息下面这个函数调用原型

FILE *fopen( const char *filename, const char *mode );
这里,filename 字符串用来命名文件访问模式 mode 可以下列中的

模式 描述
r
打开有的文本文件允许读取文件
w
打开文本文件允许文件如果文件存在创建文件这里程序文件开头内容如果文件存在文件内容清空文件长度截断0)。
a
打开文本文件追加模式文件如果文件存在创建文件这里程序有的文件内容追加内容
r+
打开文本文件允许读写文件
w+
打开文本文件允许读写文件如果文件存在文件截断长度如果文件存在创建文件
a+
打开文本文件允许读写文件如果文件存在创建文件读取文件开头开始只能追加模式
如果处理二进制文件使用下面访问模式取代上面访问模式

"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
关闭文件
为了关闭文件使用 fclose( ) 函数函数原型如下

int fclose( FILE *fp );
如果成功关闭文件,fclose( ) 函数返回如果关闭文件发生错误函数返回 EOF。这个函数实际上清空缓冲中的数据关闭文件释放用于文件所有内存。EOF 定义文件 stdio.h 中的常量

C
标准提供各种函数字符或者固定长度字符串形式读写文件

文件
下面字符中的简单函数

int fputc( int c, FILE *fp );
函数 fputc() 参数 c 字符 fp 指向输出如果成功返回字符如果发生错误返回 EOF。可以使用下面函数 null 结尾字符串

int fputs( const char *s, FILE *fp );
函数 fputs() 字符串 s fp 指向输出如果成功返回负值如果发生错误返回 EOF。可以使用 int fprintf(FILE *fp,const char *format, ...) 函数字符串文件尝试下面实例

注意确保 tmp 目录如果存在目录需要计算机创建目录

/tmp
一般 Linux 系统临时目录如果 Windows 系统运行需要修改本地环境存在目录例如: C:\tmp、D:\tmp

实例
#include <stdio.h>

int main()
{
FILE *fp = NULL;

fp = fopen("/tmp/test.txt", "w+");
fprintf(fp, "This is testing for fprintf...\n");
fputs("This is testing for fputs...\n", fp);
fclose(fp);
}
当上面的代码编译执行 /tmp 目录创建文件 test.txt,使用不同函数接下来我们读取这个文件

读取文件
下面文件读取单个字符简单函数

int fgetc( FILE * fp );
fgetc()
函数 fp 指向输入文件读取字符返回读取字符如果发生错误返回 EOF。下面函数允许读取字符串

char *fgets( char *buf, int n, FILE *fp );
函数 fgets() fp 指向输入读取 n - 1 字符读取字符串复制缓冲 buf,最后追加 null 字符终止字符串

如果这个函数读取最后字符之前遇到换行 '\n' 文件末尾 EOF,返回读取字符包括换行可以使用 int fscanf(FILE *fp, const char *format, ...) 函数文件读取字符串但是遇到第一空格换行停止读取

实例
#include <stdio.h>

int main()
{
FILE *fp = NULL;
char buff[255];

fp = fopen("/tmp/test.txt", "r");
fscanf(fp, "%s", buff);
printf("1: %s\n", buff );

fgets(buff, 255, (FILE*)fp);
printf("2: %s\n", buff );

fgets(buff, 255, (FILE*)fp);
printf("3: %s\n", buff );
fclose(fp);

}
当上面的代码编译执行读取一部分创建文件产生下列结果

1: This
2: is testing for fprintf...

3: This is testing for fputs...
首先,fscanf() 方法读取 This,因为后边到了空格其次调用 fgets() 读取剩余部分直到最后调用 fgets() 完整读取第二

二进制 I/O 函数
下面函数用于二进制输入输出

size_t fread(void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);

size_t fwrite(const void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
函数用于存储读写 - 通常数组结构


27. C
处理器
C
处理器(CPP)编译过程中的独立阶段实际编译源代码进行文本处理主要功能包括

展开
文件包含
条件编译
特殊指令处理
C
处理器不是编译器组成部分但是编译过程单独步骤

简言之,C 处理器只不过文本替换工具而已它们指示编译器实际编译之前完成所需预处理

我们 C 处理器(C Preprocessor)简写 CPP。

有的处理器命令是以井号 # 开头必须第一非空字符为了增强可读性处理器指令应从第一开始

下面列出所有重要处理器指令

指令 描述 使用示例
#define
定义符号常量函数) #define PI 3.14159
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#include
包含文件 #include <stdio.h>
#include "myheader.h"
#undef
取消定义 #undef PI
#ifdef
如果定义编译后续代码 #ifdef DEBUG
printf("Debug info\n");
#endif
#ifndef
如果定义编译后续代码常用文件保护) #ifndef HEADER_H
#define HEADER_H
/*
内容 */
#endif
#if
条件编译配合defined操作符使用) #if VERSION > 2
/*
新版代码 */
#endif
#else #if/#ifdef/#ifndef
替代分支 #ifdef WIN32
/* Windows
代码 */
#else
/*
其他系统 */
#endif
#elif
类似else if #if defined(UNIX)
/* Unix
代码 */
#elif defined(WIN32)
/* Windows
代码 */
#endif
#endif
结束条件编译
#error
产生编译错误输出消息 #if !defined(C99)
#error "
需要C99标准"
#endif
#pragma
编译器特定指令非标准编译器不同) #pragma once
#pragma pack(1)
处理器实例
分析下面实例理解不同指令

#define MAX_ARRAY_LENGTH 20
这个指令告诉 CPP 所有 MAX_ARRAY_LENGTH 定义 20。使用 #define 定义常量增强可读性

#include <stdio.h>
#include "myheader.h"
这些指令告诉 CPP 系统获取 stdio.h,添加文本当前文件一行告诉 CPP 本地目录获取 myheader.h,添加内容当前文件

#undef FILE_SIZE
#define FILE_SIZE 42
这个指令告诉 CPP 取消定义 FILE_SIZE,定义 42。

#ifndef MESSAGE
#define MESSAGE "You wish!"
#endif
这个指令告诉 CPP 只有 MESSAGE 定义定义 MESSAGE。

#ifdef DEBUG
/* Your debugging statements here */
#endif
这个指令告诉 CPP 如果定义 DEBUG,执行处理语句编译如果 gcc 编译器传递 -DDEBUG 开关这个指令非常有用定义 DEBUG,可以编译期间随时开启关闭调试

实例
#include <stdio.h>

//
定义常量
#define PI 3.1415926
#define GREETING "Hello, World!"

//
定义函数注意括号使用
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))

//
条件编译示例
#define DEBUG 1

int main() {
//
使用常量
printf("PI
: %f\n", PI);
printf("%s\n", GREETING);

//
使用函数
int x = 5;
printf("%d
平方: %d\n", x, SQUARE(x));
printf("3
5较大: %d\n", MAX(3, 5));

//
条件编译示例
#ifdef DEBUG
printf("[
调试信息] 程序运行main函数\n");
#endif

//
编译器版本检查
#if __STDC_VERSION__ >= 201112L
printf("
使用C11标准\n");
#elif __STDC_VERSION__ >= 199901L
printf("
使用C99标准\n");
#else
printf("
使用C89/C90标准\n");
#endif

//
错误指令示例取消注释导致编译错误
// #error "
手动触发错误"

return 0;
}
最佳实践建议
命名

使用大写字母下划线命名
示例:#define MAX_SIZE 100
函数注意事项

参数整个表达式括号起来
避免使用副作用参数SQUARE(x++))
文件保护

#ifndef MY_HEADER_H
#define MY_HEADER_H
/*
文件内容 */
#endif
条件编译调试

#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...)
#endif
跨平台开发

#if defined(_WIN32)
// Windows
特定代码
#elif defined(__linux__)
// Linux
特定代码
#elif defined(__APPLE__)
// macOS
特定代码
#endif
预定义
ANSI C
定义许多编程可以使用这些但是不能直接修改这些预定义

描述
__DATE__
当前日期 "MMM DD YYYY" 格式表示字符常量
__TIME__
当前时间 "HH:MM:SS" 格式表示字符常量
__FILE__
包含当前文件字符串常量
__LINE__
包含当前行号十进制常量
__STDC__
编译器 ANSI 标准编译定义 1。
我们尝试下面实例

实例
#include <stdio.h>

/*
*
预定义演示程序
*
展示ANSI C标准常用预定义及其用途
*/
int main() {
//
打印当前文件字符串常量
printf("
当前文件: %s\n", __FILE__);

//
打印编译日期("MMM DD YYYY"格式
printf("
编译日期: %s\n", __DATE__);

//
打印编译时间("HH:MM:SS"格式
printf("
编译时间: %s\n", __TIME__);

//
打印当前行号十进制整数
printf("
当前行号: %d\n", __LINE__);

//
检查是否符合ANSI/ISO标准(1表示符合
printf("ANSI
标准: %d\n", __STDC__);

//
实用示例调试信息输出
printf("\n[
调试信息] %s (%d) 编译 %s %s\n",
__FILE__, __LINE__, __DATE__, __TIME__);

return 0;
}
当上面的代码文件 test.c 编译执行产生下列结果

当前文件: predef_macros.c
编译日期: Jul 5 2023
编译时间: 14:30:45
当前行号: 13
ANSI
标准: 1

[
调试信息] predef_macros.c (16) 编译 Jul 5 2023 14:30:45
处理器运算
C
处理器提供下列运算帮助创建

延续运算(\)
通常单行但是如果单行容纳不下使用延续运算(\)。例如

#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")

字符串量化运算(#)
定义需要参数转换字符串常量使用字符串量化运算(#)。使用运算特定参数参数列表例如

实例
#include <stdio.h>

#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")

int main(void)
{
message_for(Carole, Debra);
return 0;
}
当上面的代码编译执行产生下列结果

Carole and Debra: We love you!
标记粘贴运算(##)
定义标记粘贴运算(##)合并参数允许定义独立标记合并标记例如

实例
#include <stdio.h>

#define tokenpaster(n) printf ("token" #n " = %d", token##n)

int main(void)
{
int token34 = 40;

tokenpaster(34);
return 0;
}
当上面的代码编译执行产生下列结果

token34 = 40
怎么发生因为这个实例编译器产生下列实际输出

printf ("token34 = %d", token34);
这个实例演示 token##n 连接 token34 这里我们使用字符串量化运算(#)标记粘贴运算(##)。

defined()
运算
处理器 defined 运算常量表达式中的用来确定标识是否已经使用 #define 定义如果指定标识定义非零)。如果指定标识定义)。下面实例演示 defined() 运算用法

实例
#include <stdio.h>

#if !defined (MESSAGE)
#define MESSAGE "You wish!"
#endif

int main(void)
{
printf("Here is the message: %s\n", MESSAGE);
return 0;
}
当上面的代码编译执行产生下列结果

Here is the message: You wish!
参数
CPP
强大功能可以使用参数模拟函数例如下面代码计算个数平方

int square(int x) {
return x * x;
}
我们可以使用上面代码如下

#define square(x) ((x) * (x))
使用带有参数之前必须使用 #define 指令定义参数列表圆括号必须紧跟名称后边名称圆括号之间允许有空例如

实例
#include <stdio.h>

#define MAX(x,y) ((x) > (y) ? (x) : (y))

int main(void)
{
printf("Max between 20 and 10 is %d\n", MAX(10, 20));
return 0;
}
当上面的代码编译执行产生下列结果

Max between 20 and 10 is 20

28. C
文件
文件扩展名为 .h 文件包含 C 函数声明定义多个文件引用共享种类文件程序员编写文件编译器自带文件

程序使用文件需要使用 C 预处理指令 #include 引用前面我们已经 stdio.h 文件编译器自带文件

引用文件相当于复制文件内容但是我们不会直接文件复制文件内容因为这么容易出错特别程序多个文件组成时候

A simple practice in C
C++ 程序建议所有常量系统全局变量函数原型文件需要时候随时引用这些文件

引用文件语法
使用预处理指令 #include 可以引用用户系统文件形式以下

#include <file>
这种形式用于引用系统文件系统目录标准列表搜索名为 file 文件编译源代码可以通过 -I 选项目录前置列表

#include "file"
这种形式用于引用用户文件包含当前文件目录搜索名为 file 文件编译源代码可以通过 -I 选项目录前置列表

引用文件操作
#include
指令指示 C 处理器浏览指定文件作为输入处理器输出包含已经生成输出引用文件生成输出以及 #include 指令之后文本输出例如如果个头文件 header.h,如下

char *test (void);
使用文件程序 program.c,如下

int x;
#include "header.h"

int main (void)
{
puts (test ());
}
编译器看到如下代码信息

int x;
char *test (void);

int main (void)
{
puts (test ());
}
引用一次文件
如果个头文件引用编译器处理文件内容产生错误为了防止这种情况标准做法文件整个内容条件编译语句如下

#ifndef HEADER_FILE
#define HEADER_FILE

the entire header file file

#endif
这种结构就是通常包装 #ifndef。再次引用文件条件因为 HEADER_FILE 定义此时处理器跳过文件整个内容编译器忽略

条件引用
有时需要多个不同文件选择引用程序例如需要指定不同操作系统使用配置参数可以通过一系列条件实现如下

#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
但是如果文件比较时候这么妥当处理器使用定义文件名称就是所谓条件引用不是文件名称作为 #include 直接参数需要使用名称代替即可

#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H
SYSTEM_H
扩展处理器查找 system_1.h, #include 最初编写那样。SYSTEM_H 通过 -D 选项 Makefile 定义

标准文件
C
标准文件(Standard Library Header Files) ANSI C(称为 C89/C90) ISO C(C99 C11)标准定义文件这些文件提供大量函数类型定义用于处理输入输出字符串操作数学计算内存管理常见编程任务

以下一些常见 C 标准文件及其功能简介

文件 功能简介
<stdio.h>
标准输入输出包含 printf、scanf 函数
<stdlib.h>
标准函数包含内存分配程序控制函数
<string.h>
字符串操作函数 strlen、strcpy
<math.h>
数学函数 sin、cos、sqrt
<time.h>
时间日期函数 time、strftime
<ctype.h>
字符处理函数 isalpha、isdigit
<limits.h>
定义各种类型限制 INT_MAX
<float.h>
定义浮点类型限制 FLT_MAX
<assert.h>
断言 assert,用于调试检查
<errno.h>
定义错误变量 errno 相关
<stddef.h>
定义通用类型 size_t、NULL
<signal.h>
处理信号函数 signal
<setjmp.h>
提供本地跳转功能函数
<locale.h>
地域相关函数 setlocale


29. C
强制类型转换
强制类型转换变量一种类型转换一种数据类型例如如果存储 long 类型简单整型需要 long 类型强制转换 int 类型可以使用强制类型转换运算一种类型转换一种类型如下

(type_name) expression
请看下面实例使用强制类型转换运算整数变量除以另一整数变量得到浮点数

实例
#include <stdio.h>

int main()
{
int sum = 17, count = 5;
double mean;

mean = (double) sum / count;
printf("Value of mean : %f\n", mean );

}
当上面的代码编译执行产生下列结果

Value of mean : 3.400000
这里注意强制类型转换运算优先级大于除法因此 sum 首先转换 double 然后除以 count,得到类型 double

类型转换可以编译器自动执行可以通过使用强制类型转换运算指定编程需要类型转换时候强制类型转换运算一种良好编程习惯

整数提升
整数提升小于 int unsigned int 整数类型转换 int unsigned int 过程请看下面实例 int 添加字符

实例
#include <stdio.h>

int main()
{
int i = 17;
char c = 'c'; /* ascii
99 */
int sum;

sum = i + c;
printf("Value of sum : %d\n", sum );

}
当上面的代码编译执行产生下列结果

Value of sum : 116
这里,sum 116,因为编译器进行整数提升执行实际加法运算 'c' 转换对应 ascii

常用算术转换
常用算术转换强制转换相同类型编译器首先执行整数提升如果操作数类型不同它们转换下列层次出现最高层次类型

Usual Arithmetic Conversion
常用算术转换不适用于赋值运算逻辑运算 && ||。我们看看下面实例理解这个概念

实例
#include <stdio.h>

int main()
{
int i = 17;
char c = 'c'; /* ascii
99 */
float sum;

sum = i + c;
printf("Value of sum : %f\n", sum );

}
当上面的代码编译执行产生下列结果

Value of sum : 116.000000
这里,c 首先转换整数但是由于最后 float 所以应用常用算术转换编译器 i c 转换浮点型它们相加得到浮点数


30. C
错误处理
C
语言提供错误处理直接支持但是作为一种系统编程语言返回形式允许访问底层数据发生错误大多数 C UNIX 函数调用返回 1 NULL,同时设置错误代码 errno,错误代码全局变量表示函数调用期间发生错误可以 errno.h 文件找到各种各样错误代码

所以,C 程序员可以通过检查返回然后根据返回决定采取适当动作开发人员应该程序初始化 errno 设置 0,一种良好编程习惯。0 表示程序没有错误

errno、perror()
strerror()
C
语言提供 perror() strerror() 函数显示 errno 相关文本消息

perror()
函数显示传给字符串后跟冒号空格当前 errno 文本表示形式
strerror()
函数返回指针指针指向当前 errno 文本表示形式
我们模拟一种错误情况尝试打开存在文件可以使用多种方式输出错误消息这里我们使用函数演示用法另外有一点需要注意应该使用 stderr 文件输出所有错误

实例
#include <stdio.h>
#include <errno.h>
#include <string.h>

extern int errno ;

int main ()
{
FILE * pf;
int errnum;
pf = fopen ("unexist.txt", "rb");
if (pf == NULL)
{
errnum = errno;
fprintf(stderr, "
错误: %d\n", errno);
perror("
通过 perror 输出错误");
fprintf(stderr, "
打开文件错误: %s\n", strerror( errnum ));
}
else
{
fclose (pf);
}
return 0;
}
当上面的代码编译执行产生下列结果

错误: 2
通过 perror 输出错误: No such file or directory
打开文件错误: No such file or directory
错误
进行除法运算如果检查除数是否导致运行时错误

为了避免这种情况发生下面代码进行除法运算检查除数是否

实例
#include <stdio.h>
#include <stdlib.h>

int main()
{
int dividend = 20;
int divisor = 0;
int quotient;

if( divisor == 0){
fprintf(stderr, "
除数 0 退出运行...\n");
exit(-1);
}
quotient = dividend / divisor;
fprintf(stderr, "quotient
变量 : %d\n", quotient );

exit(0);
}
当上面的代码编译执行产生下列结果

除数 0 退出运行...
程序退出状态
通常情况程序成功执行操作正常退出时候带有 EXIT_SUCCESS。这里,EXIT_SUCCESS 定义 0。

如果程序存在一种错误情况退出程序带有状态 EXIT_FAILURE,定义 -1。所以上面程序可以

实例
#include <stdio.h>
#include <stdlib.h>

int main()
{
int dividend = 20;
int divisor = 5;
int quotient;

if( divisor == 0){
fprintf(stderr, "
除数 0 退出运行...\n");
exit(EXIT_FAILURE);
}
quotient = dividend / divisor;
fprintf(stderr, "quotient
变量: %d\n", quotient );

exit(EXIT_SUCCESS);
}
当上面的代码编译执行产生下列结果

quotient
变量 : 4


31. C
递归
递归函数定义使用函数自身方法

例子
从前和尚正在和尚故事故事什么?"从前和尚正在和尚故事故事什么?'从前和尚正在和尚故事故事什么?……'"

语法格式如下

void recursion()
{
statements;
... ... ...
recursion(); /*
函数调用自身 */
... ... ...
}

int main()
{
recursion();
}
流程图



C
语言支持递归函数可以调用其自身使用递归程序员需要注意定义函数退出条件否则进入循环

递归函数解决许多数学问题至关重要作用比如计算个数阶乘生成斐波那契数列等等

阶乘
下面实例使用递归函数计算给定阶乘

实例
#include <stdio.h>

double factorial(unsigned int i)
{
if(i <= 1)
{
return 1;
}
return i * factorial(i - 1);
}
int main()
{
int i = 15;
printf("%d
阶乘 %f\n", i, factorial(i));
return 0;
}
当上面的代码编译执行产生下列结果

15
阶乘 1307674368000.000000
斐波那契数列
下面实例使用递归函数生成给定斐波那契数列

实例
#include <stdio.h>

int fibonaci(int i)
{
if(i == 0)
{
return 0;
}
if(i == 1)
{
return 1;
}
return fibonaci(i-1) + fibonaci(i-2);
}

int main()
{
int i;
for (i = 0; i < 10; i++)
{
printf("%d\t\n", fibonaci(i));
}
return 0;
}
当上面的代码编译执行产生下列结果

0
1
1
2
3
5
8
13
21
34


32. C
可变参数
有时可能碰到这样情况希望函数带有可变数量参数不是预定义数量参数

C
语言这种情况提供解决方案允许定义函数根据具体需求接受可变数量参数

声明方式

int func_name(int arg1, ...);
其中省略号 ... 表示可变参数列表

下面实例演示这种函数使用

int func(int, ... ) {
.
.
.
}

int main() {
func(2, 2, 3);
func(3, 2, 3, 4);
}
注意函数 func() 最后参数省略号点号(...),省略号之前那个参数 int,代表传递可变参数总数为了使用这个功能需要使用 stdarg.h 文件文件提供实现可变参数功能函数具体步骤如下

定义函数最后参数省略号省略号前面可以设置自定义参数
函数定义创建 va_list 类型变量类型 stdarg.h 文件定义
使用 int 参数 va_start() 初始化 va_list 变量参数列表 va_start() stdarg.h 文件定义
使用 va_arg() va_list 变量访问参数列表中的
使用 va_end() 清理赋予 va_list 变量内存
常用

va_start(ap, last_arg):
初始化可变参数列表。ap va_list 类型变量,last_arg 最后固定参数名称也就是可变参数列表之前参数)。 ap 指向可变参数列表中的第一参数

va_arg(ap, type):
获取可变参数列表中的下一个参数。ap va_list 类型变量,type 下一个参数类型返回类型 type ap 指向下一个参数

va_end(ap):
结束可变参数列表访问。ap va_list 类型变量 ap NULL。

现在我们按照上面步骤编写带有可变数量参数函数返回它们平均值

实例
#include <stdio.h>
#include <stdarg.h>

double average(int num,...)
{

va_list valist;
double sum = 0.0;
int i;

/*
num 参数初始化 valist */
va_start(valist, num);

/*
访问所有 valist 参数 */
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
/*
清理 valist 保留内存 */
va_end(valist);

return sum/num;
}

int main()
{
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}
上面例子,average() 函数接受整数 num 任意数量整数参数函数内部使用 va_list 类型变量 va_list 访问可变参数列表循环每次使用 va_arg() 获取下一个整数参数输出最后函数结束使用 va_end() 结束可变参数列表访问

当上面的代码编译执行产生下列结果应该指出函数 average() 调用每次第一参数表示可变参数总数省略号用来传递可变数量参数

Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000


33. C
内存管理
讲解 C 中的动态内存管理。C 语言内存分配管理提供几个函数这些函数可以 <stdlib.h> 文件找到

C 语言内存通过指针变量管理指针变量存储内存地址这个内存地址可以指向任何数据类型变量包括整数浮点数字符和数。C 语言提供一些函数运算使得程序员可以对内进行操作包括分配释放移动复制

序号 函数描述
1 void *calloc(int num, int size);
在内动态分配 num 长度 size 连续空间字节初始化 0。所以结果分配 num*size 字节长度内存空间并且字节 0。
2 void free(void *address);
函数释放 address 指向内存,释放动态分配内存空间
3 void *malloc(int num);
区分一块指定大小内存空间用来存放数据内存空间函数执行完成不会初始化它们未知
4 void *realloc(void *address, int newsize);
函数重新分配内存内存扩展 newsize。
注意:void * 类型表示未确定类型指针。C、C++ 规定 void * 类型可以通过类型转换强制转换任何其它类型指针


动态分配内存
编程如果预先知道数组大小那么定义数组比较容易例如存储人名数组最多容纳 100 字符所以可以定义数组如下

char name[100];
但是如果预先知道需要存储文本长度例如存储有关主题详细描述这里我们需要定义指针指针指向未定所需内存大小字符后续根据需求分配内存如下

实例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
char name[100];
char *description;

strcpy(name, "Zara Ali");

/*
动态分配内存 */
description = (char *)malloc( 200 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy( description, "Zara ali a DPS student in class 10th");
}
printf("Name = %s\n", name );
printf("Description: %s\n", description );
}
当上面的代码编译执行产生下列结果

Name = Zara Ali
Description: Zara ali a DPS student in class 10th
上面程序可以使用 calloc() 编写需要 malloc 替换 calloc 即可如下

calloc(200, sizeof(char));
动态分配内存完全控制权可以传递任何大小那些预先定义大小数组一旦定义无法改变大小

重新调整内存大小释放内存
程序退出操作系统自动释放所有分配程序内存但是建议需要内存应该调用函数 free() 释放内存

或者可以通过调用函数 realloc() 增加减少分配内存大小我们使用 realloc() free() 函数再次查看上面实例

实例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
char name[100];
char *description;

strcpy(name, "Zara Ali");

/*
动态分配内存 */
description = (char *)malloc( 30 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy( description, "Zara ali a DPS student.");
}
/*
假设想要存储描述信息 */
description = (char *) realloc( description, 100 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcat( description, "She is in class 10th");
}

printf("Name = %s\n", name );
printf("Description: %s\n", description );

/*
使用 free() 函数释放内存 */
free(description);
}
当上面的代码编译执行产生下列结果

Name = Zara Ali
Description: Zara ali a DPS student.She is in class 10th
可以尝试一下重新分配额外内存,strcat() 函数生成错误因为存储 description 内存不足

C
语言中常内存管理函数运算
malloc()
函数用于动态分配内存接受参数需要分配内存大小字节单位),返回指向分配内存指针

free()
函数用于释放先前分配内存接受指向释放内存指针作为参数内存标记使用状态

calloc()
函数用于动态分配内存初始化接受参数需要分配内存内存大小字节单位),返回指向分配内存指针

realloc()
函数用于重新分配内存接受参数先前分配指针内存大小然后尝试重新调整先前分配内存大小如果调整成功返回指向重新分配内存指针否则返回指针

sizeof
运算用于获取数据类型变量大小字节单位)。

指针运算用于获取指针指向内存地址变量

&
运算用于获取变量内存地址

*
运算用于获取指针指向变量

->
运算用于指针访问结构成员语法 pointer->member,等价 (*pointer).member。

memcpy()
函数用于内存区域复制数据目标内存区域接受参数目标内存区域指针内存区域指针复制数据大小字节单位)。

memmove()
函数类似 memcpy() 函数可以处理重叠内存区域接受参数目标内存区域指针内存区域指针复制数据大小字节单位)。


34. C
命令行参数
执行程序可以从命 C 程序这些称为命令行参数它们程序重要特别外部控制程序不是代码这些进行硬编码显得尤为重要

C 语言命令行参数一种从命获取输入方法可以用于运行程序传递信息程序命令行参数通过 main 函数参数传递程序。main 函数原型可以如下形式之一

int main(int argc, char *argv[]);
或者:

int main(int argc, char **argv);
argc (argument count):
表示命令行参数数量包括程序本身因此,argc 至少 1。

argv (argument vector):
指向字符串数组指针其中字符串命令行参数数组第一元素 argv[0])通常程序名称接下来元素传递程序命令行参数

下面简单实例检查命令行是否提供参数根据参数执行相应动作

实例
#include <stdio.h>

int main( int argc, char *argv[] )
{
if( argc == 2 )
{
printf("The argument supplied is %s\n", argv[1]);
}
else if( argc > 2 )
{
printf("Too many arguments supplied.\n");
}
else
{
printf("One argument expected.\n");
}
}
使用参数编译执行上面代码产生下列结果

$./a.out testing
The argument supplied is testing
使用参数编译执行上面代码产生下列结果

$./a.out testing1 testing2
Too many arguments supplied.
任何参数编译执行上面代码产生下列结果

$./a.out
One argument expected
应当指出,argv[0] 存储程序名称,argv[1] 指向第一命令行参数指针,*argv[n] 最后参数如果没有提供任何参数,argc 1,否则如果传递参数,argc 设置 2。

多个命令行参数之间空格分隔但是如果参数本身带有空格那么传递参数时候参数放置双引号 "" 单引号 '' 内部我们重新编写上面实例空格那么可以通过这样观点它们双引号单引号""""。我们重新编写上面实例程序传递放置双引号内部命令行参数

实例
#include <stdio.h>

int main( int argc, char *argv[] )
{
printf("Program name %s\n", argv[0]);

if( argc == 2 )
{
printf("The argument supplied is %s\n", argv[1]);
}
else if( argc > 2 )
{
printf("Too many arguments supplied.\n");
}
else
{
printf("One argument expected.\n");
}
}
使用空格分隔简单参数参数双引号编译执行上面代码产生下列结果

$./a.out "testing1 testing2"

Progranm name ./a.out
The argument supplied is testing1 testing2
使用场景
命令行参数许多情况有用例如

配置文件路径
模式选择例如调试模式
输入文件输出文件
运行时选项标志 -v 表示详细模式
注意事项
命令行参数通常字符串如果需要转换数值类型可以使用标准函数 atoi strtol。
应该始终验证处理命令行参数防止输入错误恶意输入


35. C
安全函数
C 语言为了提高代码安全性尤其是防止缓冲溢出常见安全问题,C11 标准引入一些 "安全函数",称为 "Annex K" 标准函数这些安全函数主要标准字符串内存操作函数增强版本通过增加参数缓冲大小提供错误检测处理

安全函数特点

缓冲大小检查所有安全函数要求传入目标缓冲大小参数防止缓冲溢出
返回检查大多数函数返回 errno_t 类型错误代码可以检查函数是否成功执行
错误处理缓冲大小不够出现其他问题这些函数返回错误尝试清空初始化输出缓冲
安全函数 Visual Studio 编译器得到较好支持一些版本编译器可能不可需要注意兼容性

以下 C 常见安全函数及其对应传统函数对比

1、
字符串操作安全函数
strcpy_s:
安全版本 strcpy,复制字符串检查目标缓冲大小

errno_t strcpy_s(char *dest, rsize_t destsz, const char *src);
strcat_s:
安全版本 strcat,字符串追加目标字符串末尾检查缓冲大小

errno_t strcat_s(char *dest, rsize_t destsz, const char *src);
strncpy_s:
安全版本 strncpy,复制最多 n 字符检查缓冲大小

errno_t strncpy_s(char *dest, rsize_t destsz, const char *src, rsize_t count);
strncat_s:
安全版本 strncat,追加最多 n 字符目标字符串末尾检查缓冲大小

errno_t strncat_s(char *dest, rsize_t destsz, const char *src, rsize_t count);
strtok_s:
安全版本 strtok,引入上下文参数解决线程安全问题

char *strtok_s(char *str, const char *delim, char **context);
2、
格式化输出安全函数
sprintf_s:
安全版本 sprintf,格式化输出字符串检查缓冲大小

int sprintf_s(char *buffer, rsize_t sizeOfBuffer, const char *format, ...);
snprintf_s:
安全版本 snprintf,格式化输出限制字符检查缓冲大小

int snprintf_s(char *buffer, rsize_t sizeOfBuffer, const char *format, ...);
vsprintf_s:
安全版本 vsprintf,接收 va_list 参数列表检查缓冲大小

int vsprintf_s(char *buffer, rsize_t sizeOfBuffer, const char *format, va_list argptr);
3、
内存操作安全函数
memcpy_s:
安全版本 memcpy,复制内存区域检查目标缓冲大小

errno_t memcpy_s(void *dest, rsize_t destsz, const void *src, rsize_t count);
memmove_s:
安全版本 memmove,复制内存区域允许重叠检查目标缓冲大小

errno_t memmove_s(void *dest, rsize_t destsz, const void *src, rsize_t count);
memset_s:
安全版本 memset,指定字符填充内存检查缓冲大小

errno_t memset_s(void *dest, rsize_t destsz, int ch, rsize_t count);
4、
其他常用安全函数
_itoa_s
_ultoa_s:安全版本整数转换函数整数转换字符串检查目标缓冲大小

errno_t _itoa_s(int value, char *buffer, size_t sizeOfBuffer, int radix);
errno_t _ultoa_s(unsigned long value, char *buffer, size_t sizeOfBuffer, int radix);
_strlwr_s
_strupr_s:字符串转换小写大写安全版本

errno_t _strlwr_s(char *str, size_t numberOfElements);
errno_t _strupr_s(char *str, size_t numberOfElements);
实例
以下使用 C 安全函数进行字符串操作内存操作示例展示它们如何避免常见缓冲溢出问题提供安全编程方式

示例 1:strcpy_s strcat_s
实例
#include <stdio.h>
#include <string.h>

int main() {
char dest[20]; //
目标缓冲大小 20
const char *src = "Hello, World!";

//
使用 strcpy_s src 复制 dest
if (strcpy_s(dest, sizeof(dest), src) != 0) {
printf("strcpy_s failed!\n");
return 1; //
返回错误代码
} else {
printf("After strcpy_s: %s\n", dest);
}

//
使用 strcat_s " C Language" 追加 dest
const char *appendStr = " C Language";
if (strcat_s(dest, sizeof(dest), appendStr) != 0) {
printf("strcat_s failed!\n");
return 1; //
返回错误代码
} else {
printf("After strcat_s: %s\n", dest);
}

return 0;
}
输出:

After strcpy_s: Hello, World!
strcat_s failed!
上述代码,strcpy_s 成功复制字符串 "Hello, World!" dest,由于 dest 大小 20,不足容纳 "Hello, World! C Language",所以 strcat_s 检测缓冲不足返回错误代码

示例 2:memcpy_s
实例
#include <stdio.h>
#include <string.h>

int main() {
char src[] = "Sensitive Data";
char dest[15]; //
目标缓冲大小 15

//
使用 memcpy_s 数据复制 dest
if (memcpy_s(dest, sizeof(dest), src, strlen(src) + 1) != 0) {
printf("memcpy_s failed!\n");
return 1; //
返回错误代码
} else {
printf("After memcpy_s: %s\n", dest);
}

return 0;
}
输出:

After memcpy_s: Sensitive Data
示例,memcpy_s 检查目标缓冲 dest 是否足够容纳 src 数据包括字符串末尾字符如果 destsz 小于 strlen(src) + 1,函数返回错误并不执行内存复制

示例 3:strtok_s
实例
#include <stdio.h>
#include <string.h>

int main() {
char str[] = "apple,orange,banana";
char *token;
char *context = NULL;

//
使用 strtok_s 分割字符串
token = strtok_s(str, ",", &context);
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok_s(NULL, ",", &context);
}

return 0;
}
输出:

Token: apple
Token: orange
Token: banana
这个示例,strtok_s 分割字符串使用 context 参数保存上下文信息从而避免 strtok 线程安全问题

示例 4:sprintf_s
实例
#include <stdio.h>

int main() {
char buffer[50];
int num = 42;
const char *str = "Hello";

//
使用 sprintf_s 格式化字符串检查缓冲大小
if (sprintf_s(buffer, sizeof(buffer), "Number: %d, String: %s", num, str) < 0) {
printf("sprintf_s failed!\n");
return 1; //
返回错误代码
} else {
printf("Formatted String: %s\n", buffer);
}

return 0;
}
输出:

Formatted String: Number: 42, String: Hello
这里,sprintf_s 格式化字符串接受缓冲大小作为参数如果格式化字符串超过 buffer 大小函数返回错误从而避免缓冲溢出

以上例子展示使用 C 安全函数进行字符串复制拼接内存复制字符串分割格式化输出方式这些函数提供缓冲大小检查显著提高代码安全性


36. C
排序算法

排序
排序英语:Bubble Sort)一种简单排序算法重复走访排序数列一次比较元素如果他们顺序首字母AZ)错误他们交换过来

过程演示

实例
#include <stdio.h>

//
函数声明
void bubble_sort(int arr[], int len);

int main() {
int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
int len = sizeof(arr) / sizeof(arr[0]); //
计算数组长度

bubble_sort(arr, len); //
调用排序函数

//
打印排序数组
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}

return 0;
}

//
排序函数
void bubble_sort(int arr[], int len) {
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - 1 - i; j++) {
//
交换元素位置
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
选择排序
选择排序(Selection sort)一种简单直观排序算法工作原理如下首先排序序列找到元素存放排序序列起始位置然后剩余排序元素继续寻找元素然后排序序列末尾以此类推直到所有元素排序完毕

过程演示

实例
#include <stdio.h>

//
函数声明
void selection_sort(int a[], int len);

int main() {
int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
int len = sizeof(arr) / sizeof(arr[0]); //
计算数组长度

selection_sort(arr, len); //
调用选择排序函数

//
打印排序数组
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}

return 0;
}

//
选择排序函数
void selection_sort(int a[], int len) {
for (int i = 0; i < len - 1; i++) {
int min = i; //
记录最小值位置第一元素默认
for (int j = i + 1; j < len; j++) {
if (a[j] < a[min]) { //
找到目前最小值
min = j; //
记录最小值位置
}
}
//
交换变量
if (min != i) {
int temp = a[min];
a[min] = a[i];
a[i] = temp;
}
}
}

/*
//
自定义交换函数
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
*/
插入排序
插入排序英语:Insertion Sort)一种简单直观排序算法工作原理通过构建有序序列对于排序数据排序序列向前扫描找到相应位置插入插入排序实现通常采用in-place排序 {\displaystyle O(1)} {\displaystyle O(1)}额外空间排序),因而向前扫描过程需要反复排序元素逐步向后

最新元素提供插入空间
过程演示

实例
#include <stdio.h>

//
函数声明
void insertion_sort(int arr[], int len);

int main() {
int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
int len = sizeof(arr) / sizeof(arr[0]); //
计算数组长度

insertion_sort(arr, len); //
调用插入排序函数

//
打印排序数组
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}

return 0;
}

//
插入排序函数
void insertion_sort(int arr[], int len) {
for (int i = 1; i < len; i++) {
int temp = arr[i]; //
当前插入元素
int j = i;
//
移动大于temp元素
while (j > 0 && arr[j - 1] > temp) {
arr[j] = arr[j - 1];
j--;
}
arr[j] = temp; //
插入元素正确位置
}
}
希尔排序
希尔排序递减增量排序算法插入排序一种高效改进版本希尔排序是非稳定排序算法

希尔排序基于插入排序以下性质提出改进方法

插入排序几乎已经数据操作效率可以达到线性排序效率
插入排序一般来说低效因为插入排序每次只能数据移动
过程演示

实例
#include <stdio.h>

//
函数声明
void shell_sort(int arr[], int len);

int main() {
int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
int len = sizeof(arr) / sizeof(arr[0]); //
计算数组长度

shell_sort(arr, len); //
调用希尔排序函数

//
打印排序数组
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}

return 0;
}

//
希尔排序函数
void shell_sort(int arr[], int len) {
//
计算初始间隔
for (int gap = len / 2; gap > 0; gap /= 2) {
//
间隔进行插入排序
for (int i = gap; i < len; i++) {
int temp = arr[i]; //
当前插入元素
int j = i;
//
移动大于temp元素
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp; //
插入元素正确位置
}
}
}
归并排序
数据分为逐个小的元素数据段末尾

进行

过程演示


迭代
#include <stdio.h>
#include <stdlib.h>

//
函数声明
int min(int x, int y);
void merge_sort(int arr[], int len);

int main() {
int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
int len = sizeof(arr) / sizeof(arr[0]); //
计算数组长度

merge_sort(arr, len); //
调用归并排序函数

//
打印排序数组
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}

return 0;
}

//
返回个数中的最小值
int min(int x, int y) {
return x < y ? x : y;
}

//
归并排序函数
void merge_sort(int arr[], int len) {
int* a = arr;
int* b = (int*) malloc(len * sizeof(int));

if (b == NULL) { //
检查内存分配是否成功
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}

for (int seg = 1; seg < len; seg += seg) {
for (int start = 0; start < len; start += seg + seg) {
int low = start;
int mid = min(start + seg, len);
int high = min(start + seg + seg, len);
int k = low;
int start1 = low, end1 = mid;
int start2 = mid, end2 = high;

//
合并个子数组
while (start1 < end1 && start2 < end2) {
b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
}
while (start1 < end1) {
b[k++] = a[start1++];
}
while (start2 < end2) {
b[k++] = a[start2++];
}
}

//
交换数组指针
int* temp = a;
a = b;
b = temp;
}

//
如果aarr相同a内容复制arr
if (a != arr) {
for (int i = 0; i < len; i++) {
b[i] = a[i];
}
b = a;
}

free(b); //
释放内存
}
递归
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//
函数声明
void merge_sort_recursive(int arr[], int reg[], int start, int end);
void merge_sort(int arr[], const int len);

int main() {
int arr[] = { 22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70 };
int len = sizeof(arr) / sizeof(arr[0]); //
计算数组长度

merge_sort(arr, len); //
调用归并排序函数

//
打印排序数组
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}

return 0;
}

//
递归实现归并排序
void merge_sort_recursive(int arr[], int reg[], int start, int end) {
if (start >= end)
return;

int mid = start + (end - start) / 2;
int start1 = start, end1 = mid;
int start2 = mid + 1, end2 = end;

merge_sort_recursive(arr, reg, start1, end1);
merge_sort_recursive(arr, reg, start2, end2);

int k = start;
while (start1 <= end1 && start2 <= end2) {
reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
}
while (start1 <= end1) {
reg[k++] = arr[start1++];
}
while (start2 <= end2) {
reg[k++] = arr[start2++];
}

//
使用memcpy进行数组复制提高效率
memcpy(arr + start, reg + start, (end - start + 1) * sizeof(int));
}

//
归并排序入口函数
void merge_sort(int arr[], const int len) {
int* reg = (int*)malloc(len * sizeof(int));
if (reg == NULL) { //
检查内存分配是否成功
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
merge_sort_recursive(arr, reg, 0, len - 1);
free(reg); //
释放内存
}
快速排序
区间随机挑选元素基准小于基准元素基准之前大于基准元素基准之后分别小数大数进行排序

过程演示



迭代
#include <stdio.h>

//
范围结构
typedef struct _Range {
int start, end;
} Range;

//
创建范围
Range new_Range(int s, int e) {
Range r;
r.start = s;
r.end = e;
return r;
}

//
交换整数
void swap(int *x, int *y) {
int t = *x;
*x = *y;
*y = t;
}

//
快速排序函数
void quick_sort(int arr[], const int len) {
if (len <= 0)
return; //
避免 len 等于负值引发段错误(Segment Fault)

Range r[len];
int p = 0;
r[p++] = new_Range(0, len - 1);

while (p > 0) {
Range range = r[--p];
if (range.start >= range.end)
continue;

int mid = arr[(range.start + range.end) / 2]; //
选取中间基准
int left = range.start, right = range.end;

do {
while (arr[left] < mid) ++left; //
检测基准左侧是否符合要求
while (arr[right] > mid) --right; //
检测基准右侧是否符合要求

if (left <= right) {
swap(&arr[left], &arr[right]);
left++;
right--; //
移动指针继续
}
} while (left <= right);

if (range.start < right) r[p++] = new_Range(range.start, right);
if (range.end > left) r[p++] = new_Range(left, range.end);
}
}

int main() {
int arr[] = {22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70};
int len = sizeof(arr) / sizeof(arr[0]); //
计算数组长度

quick_sort(arr, len); //
调用快速排序函数

//
打印排序数组
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}

return 0;
}
递归
#include <stdio.h>

//
交换整数
void swap(int *x, int *y) {
int t = *x;
*x = *y;
*y = t;
}

//
递归实现快速排序
void quick_sort_recursive(int arr[], int start, int end) {
if (start >= end)
return;

int mid = arr[end];
int left = start, right = end - 1;

while (left < right) {
while (left < right && arr[left] < mid)
left++;
while (left < right && arr[right] >= mid)
right--;
swap(&arr[left], &arr[right]);
}

if (arr[left] >= arr[end])
swap(&arr[left], &arr[end]);
else
left++;

quick_sort_recursive(arr, start, left - 1);
quick_sort_recursive(arr, left + 1, end);
}

//
快速排序入口函数
void quick_sort(int arr[], int len) {
quick_sort_recursive(arr, 0, len - 1);
}

int main() {
int arr[] = {22, 34, 3, 32, 82, 55, 89, 50, 37, 5, 64, 35, 9, 70};
int len = sizeof(arr) / sizeof(arr[0]); //
计算数组长度

quick_sort(arr, len); //
调用快速排序函数

//
打印排序数组
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}

return 0;
}