- C语言编程教程
- C语言 - 首页
- C语言基础
- C语言 - 概述
- C语言 - 特性
- C语言 - 历史
- C语言 - 环境搭建
- C语言 - 程序结构
- C语言 - Hello World
- C语言 - 编译过程
- C语言 - 注释
- C语言 - 词法单元
- C语言 - 关键字
- C语言 - 标识符
- C语言 - 用户输入
- C语言 - 基本语法
- C语言 - 数据类型
- C语言 - 变量
- C语言 - 整型提升
- C语言 - 类型转换
- C语言 - 类型强制转换
- C语言 - 布尔值
- C语言中的常量和字面量
- C语言 - 常量
- C语言 - 字面量
- C语言 - 转义序列
- C语言 - 格式说明符
- C语言中的运算符
- C语言 - 运算符
- C语言 - 算术运算符
- C语言 - 关系运算符
- C语言 - 逻辑运算符
- C语言 - 位运算符
- C语言 - 赋值运算符
- C语言 - 一元运算符
- C语言 - 自增和自减运算符
- C语言 - 三元运算符
- C语言 - sizeof运算符
- C语言 - 运算符优先级
- C语言 - 其他运算符
- C语言中的决策语句
- C语言 - 决策语句
- C语言 - if语句
- C语言 - if...else语句
- C语言 - 嵌套if语句
- C语言 - switch语句
- C语言 - 嵌套switch语句
- C语言中的循环语句
- C语言 - 循环语句
- C语言 - while循环
- C语言 - for循环
- C语言 - do...while循环
- C语言 - 嵌套循环
- C语言 - 无限循环
- C语言 - break语句
- C语言 - continue语句
- C语言 - goto语句
- C语言中的函数
- C语言 - 函数
- C语言 - 主函数
- C语言 - 按值调用函数
- C语言 - 按引用调用函数
- C语言 - 嵌套函数
- C语言 - 可变参数函数
- C语言 - 用户自定义函数
- C语言 - 回调函数
- C语言 - 返回语句
- C语言 - 递归
- C语言中的作用域规则
- C语言 - 作用域规则
- C语言 - 静态变量
- C语言 - 全局变量
- C语言中的数组
- C语言 - 数组
- C语言 - 数组的特性
- C语言 - 多维数组
- C语言 - 向函数传递数组
- C语言 - 从函数返回数组
- C语言 - 变长数组
- C语言中的指针
- C语言 - 指针
- C语言 - 指针和数组
- C语言 - 指针的应用
- C语言 - 指针运算
- C语言 - 指针数组
- C语言 - 指向指针的指针
- C语言 - 向函数传递指针
- C语言 - 从函数返回指针
- C语言 - 函数指针
- C语言 - 指向数组的指针
- C语言 - 指向结构体的指针
- C语言 - 指针链
- C语言 - 指针与数组的比较
- C语言 - 字符指针和函数
- C语言 - 空指针
- C语言 - void指针
- C语言 - 悬空指针
- C语言 - 指针解引用
- C语言 - 近指针、远指针和巨大指针
- C语言 - 指针数组的初始化
- C语言 - 指针与多维数组的比较
- C语言中的字符串
- C语言 - 字符串
- C语言 - 字符串数组
- C语言 - 特殊字符
- C语言中的结构体和联合体
- C语言 - 结构体
- C语言 - 结构体和函数
- C语言 - 结构体数组
- C语言 - 自引用结构体
- C语言 - 查找表
- C语言 - 点(.)运算符
- C语言 - 枚举(enum)
- C语言 - 结构体填充和打包
- C语言 - 嵌套结构体
- C语言 - 匿名结构体和联合体
- C语言 - 联合体
- C语言 - 位域
- C语言 - typedef
- C语言中的文件处理
- C语言 - 输入输出
- C语言 - 文件I/O (文件处理)
- C语言预处理器
- C语言 - 预处理器
- C语言 - 编译指示
- C语言 - 预处理器运算符
- C语言 - 宏
- C语言 - 头文件
- C语言中的内存管理
- C语言 - 内存管理
- C语言 - 内存地址
- C语言 - 存储类别
- 其他主题
- C语言 - 错误处理
- C语言 - 可变参数
- C语言 - 命令执行
- C语言 - 数学函数
- C语言 - static关键字
- C语言 - 随机数生成
- C语言 - 命令行参数
- C语言编程资源
- C语言 - 问答
- C语言快速入门
- C语言 - 速查表
- C语言 - 有用资源
- C语言 - 讨论
C语言快速入门
C语言是一种通用的高级语言,最初由Dennis M. Ritchie开发,用于在贝尔实验室开发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年代初推出。
该语言于1988年由美国国家标准协会(ANSI)正式确定。
UNIX操作系统完全是用C语言编写的。
如今,C语言是最广泛使用和最流行的系统编程语言。
大多数最先进的软件都是使用C语言实现的。
如今最流行的Linux操作系统和RDBMS MySQL都是用C语言编写的。
为什么要使用C语言?
C语言最初用于系统开发工作,特别是构成操作系统的程序。C语言被采用作为系统开发语言,因为它生成的代码运行速度几乎与用汇编语言编写的代码一样快。一些C语言的使用示例可能包括:
- 操作系统
- 语言编译器
- 汇编器
- 文本编辑器
- 打印后台程序
- 网络驱动程序
- 现代程序
- 数据库
- 语言解释器
- 实用程序
C语言程序
C程序的长度可以从3行到数百万行不等,它应该被写入一个或多个扩展名为".c"的文本文件中;例如,hello.c。您可以使用"vi"、"vim"或任何其他文本编辑器将您的C程序写入文件。
本教程假设您知道如何编辑文本文件以及如何在程序文件中编写源代码。
C语言 - 环境搭建
本地环境搭建
如果您想为C编程语言设置您的环境,您需要在您的计算机上提供以下两个软件工具:(a) 文本编辑器和(b) C编译器。
文本编辑器
这将用于键入您的程序。一些编辑器的示例包括Windows记事本、OS Edit命令、Brief、Epsilon、EMACS以及vim或vi。
文本编辑器的名称和版本在不同的操作系统上可能会有所不同。例如,Notepad将用于Windows,而vim或vi可以在Windows以及Linux或UNIX上使用。
您使用编辑器创建的文件称为源文件,它们包含程序源代码。C程序的源文件通常以扩展名".c"命名。
在开始编程之前,请确保您已准备好一个文本编辑器,并且您有足够的经验来编写计算机程序,将其保存在文件中,编译它,最后执行它。
C编译器
写在源文件中的源代码是您的程序的人类可读源代码。它需要被“编译”成机器语言,以便您的CPU可以根据给定的指令实际执行程序。
编译器将源代码编译成最终的可执行程序。最常用且免费提供的编译器是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,则您需要使用https://gcc.gnu.org/install/中提供的详细说明自行安装。
本教程是基于Linux编写的,所有给出的示例都在Cent OS版本的Linux系统上编译。
在Mac OS上安装
如果您使用的是Mac OS X,获取GCC最简单的方法是从Apple的网站下载Xcode开发环境,然后按照简单的安装说明进行操作。设置好Xcode后,您就可以使用GNU C/C++编译器了。
Xcode目前可在developer.apple.com/technologies/tools/获得。
在Windows上安装
要在Windows上安装GCC,您需要安装MinGW。要安装MinGW,请访问MinGW主页www.mingw.org,然后点击链接到MinGW下载页面。下载最新版本的MinGW安装程序,其名称应为MinGW-<version>.exe。
安装 MinGW 时,至少必须安装 gcc-core、gcc-g++、binutils 和 MinGW 运行时,但您可能希望安装更多组件。
将 MinGW 安装目录下的 bin 子目录添加到您的 **PATH** 环境变量中,以便您可以通过简单的名称在命令行中指定这些工具。
安装完成后,您将能够从 Windows 命令行运行 gcc、g++、ar、ranlib、dlltool 和其他一些 GNU 工具。
C语言 - 程序结构
在我们学习 C 编程语言的基本构建块之前,让我们先看一下 C 程序的最小结构,以便在接下来的章节中将其作为参考。
Hello World 示例
C 程序主要由以下部分组成:
- 预处理器命令
- 函数
- 变量
- 语句和表达式
- 注释
让我们来看一段简单的代码,它将打印“Hello World”:
#include <stdio.h> int main() { /* my first program in C */ printf("Hello, World! \n"); return 0; }
让我们看一下上面程序的各个部分:
程序的第一行 #include
是一个预处理器命令,它告诉 C 编译器在进行实际编译之前包含 stdio.h 文件。 下一行 int main() 是主函数,程序执行从此处开始。
下一行 /*...*/ 将被编译器忽略,它被用来在程序中添加额外的注释。因此,这样的行被称为程序中的注释。
下一行 printf(...) 是 C 中另一个可用的函数,它会使消息“Hello, World!”显示在屏幕上。
下一行 **return 0;** 终止 main() 函数并返回 0 值。
编译和执行 C 程序
让我们看看如何将源代码保存在文件中,以及如何编译和运行它。以下是简单的步骤:
打开一个文本编辑器并添加上述代码。
将文件保存为 hello.c
打开命令提示符并转到保存文件的目录。
键入 gcc hello.c 并按 Enter 键编译代码。
如果代码中没有错误,命令提示符将带您进入下一行并生成 a.out 可执行文件。
现在,键入 a.out 来执行您的程序。
您将看到输出“Hello World”打印在屏幕上。
$ gcc hello.c $ ./a.out Hello, World!
确保 gcc 编译器在您的 PATH 中,并且您在包含源文件 hello.c 的目录中运行它。
C语言 - 基本语法
您已经看到了 C 程序的基本结构,因此很容易理解 C 编程语言的其他基本构建块。
C语言中的标记
C 程序由各种标记组成,标记是关键字、标识符、常量、字符串文字或符号。例如,以下 C 语句包含五个标记:
printf("Hello, World! \n");
各个标记是:
printf ( "Hello, World! \n" ) ;
分号
在 C 程序中,分号是语句终止符。也就是说,每个语句都必须以分号结尾。它表示一个逻辑实体的结束。
下面给出两个不同的语句:
printf("Hello, World! \n"); return 0;
注释
注释就像 C 程序中的帮助文本,编译器会忽略它们。它们以 /* 开头,以 */ 结尾,如下所示:
/* my first program in C */
您不能在注释中嵌套注释,并且它们不会出现在字符串或字符文字中。
标识符
C 标识符是用于标识变量、函数或任何其他用户定义项的名称。标识符以字母 A 到 Z、a 到 z 或下划线 '_' 开头,后跟零个或多个字母、下划线和数字 (0 到 9)。
C 不允许在标识符中使用 @、$ 和 % 等标点符号。C 是一种 **区分大小写** 的编程语言。因此,Manpower 和 manpower 在 C 中是两个不同的标识符。以下是一些可接受的标识符示例:
mohd zara abc move_name a_123 myname50 _temp j a23b9 retVal
关键字
以下列表显示了 C 中的保留字。这些保留字不能用作常量或变量或任何其他标识符名称。
auto | else | long | switch |
break | enum | register | typedef |
case | extern | return | union |
char | float | short | unsigned |
const | for | signed | void |
continue | goto | sizeof | volatile |
default | if | static | while |
do | int | struct | _Packed |
double |
C语言中的空白字符
仅包含空白字符(可能还有注释)的行称为空行,C 编译器会完全忽略它。
空白字符是 C 中用来描述空格、制表符、换行符和注释的术语。空白字符将语句的一个部分与另一部分隔开,并使编译器能够识别语句中一个元素(例如 int)的结尾和下一个元素的开始。因此,在以下语句中:
int age;
int 和 age 之间必须至少有一个空白字符(通常是空格),以便编译器能够区分它们。另一方面,在以下语句中:
fruit = apples + oranges; // get the total fruit
fruit 和 = 之间,或 = 和 apples 之间不需要空白字符,尽管您可以根据需要添加一些以提高可读性。
C语言 - 数据类型
C语言中的数据类型指的是一个广泛的系统,用于声明不同类型的变量或函数。变量的类型决定了它在存储中占据的空间大小以及如何解释存储的位模式。
C语言中的类型可以分为如下几类:
序号 | 类型和描述 |
---|---|
1 | 基本类型 它们是算术类型,进一步分为:(a)整数类型和(b)浮点类型。 |
2 | 枚举类型 它们也是算术类型,用于定义只能在整个程序中赋值某些离散整数值的变量。 |
3 | void 类型 类型说明符 void 表示没有可用值。 |
4 | 派生类型 它们包括 (a) 指针类型,(b) 数组类型,(c) 结构类型,(d) 联合类型和 (e) 函数类型。 |
数组类型和结构类型统称为聚合类型。函数的类型指定函数返回值的类型。我们将在下一节中看到基本类型,而其他类型将在接下来的章节中介绍。
整数类型
下表提供了标准整数类型及其存储大小和值范围的详细信息:
类型 | 存储大小 | 值范围 |
---|---|---|
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 | 8 字节 | -9223372036854775808 到 9223372036854775807 |
unsigned long | 8 字节 | 0 到 18446744073709551615 |
要获取特定平台上某个类型或变量的确切大小,可以使用 **sizeof** 运算符。表达式 sizeof(type) 以字节为单位给出对象或类型的存储大小。下面是一个示例,它使用 limits.h 头文件中定义的不同常量来获取机器上各种类型的 size:
#include <stdio.h> #include <stdlib.h> #include <limits.h> #include <float.h> int main(int argc, char** argv) { printf("CHAR_BIT : %d\n", CHAR_BIT); printf("CHAR_MAX : %d\n", CHAR_MAX); printf("CHAR_MIN : %d\n", CHAR_MIN); printf("INT_MAX : %d\n", INT_MAX); printf("INT_MIN : %d\n", INT_MIN); printf("LONG_MAX : %ld\n", (long) LONG_MAX); printf("LONG_MIN : %ld\n", (long) LONG_MIN); printf("SCHAR_MAX : %d\n", SCHAR_MAX); printf("SCHAR_MIN : %d\n", SCHAR_MIN); printf("SHRT_MAX : %d\n", SHRT_MAX); printf("SHRT_MIN : %d\n", SHRT_MIN); printf("UCHAR_MAX : %d\n", UCHAR_MAX); printf("UINT_MAX : %u\n", (unsigned int) UINT_MAX); printf("ULONG_MAX : %lu\n", (unsigned long) ULONG_MAX); printf("USHRT_MAX : %d\n", (unsigned short) USHRT_MAX); return 0; }
编译并执行上述程序时,它在 Linux 上会产生以下结果:
CHAR_BIT : 8 CHAR_MAX : 127 CHAR_MIN : -128 INT_MAX : 2147483647 INT_MIN : -2147483648 LONG_MAX : 9223372036854775807 LONG_MIN : -9223372036854775808 SCHAR_MAX : 127 SCHAR_MIN : -128 SHRT_MAX : 32767 SHRT_MIN : -32768 UCHAR_MAX : 255 UINT_MAX : 4294967295 ULONG_MAX : 18446744073709551615 USHRT_MAX : 65535
浮点类型
下表提供了标准浮点类型及其存储大小、值范围和精度的详细信息:
类型 | 存储大小 | 值范围 | 精度 |
---|---|---|---|
float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位小数 |
double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位小数 |
long double | 10 字节 | 3.4E-4932 到 1.1E+4932 | 19 位小数 |
float.h 头文件定义了宏,允许您在程序中使用这些值以及有关实数二进制表示的其他详细信息。以下示例打印 float 类型占用的存储空间及其范围值:
#include <stdio.h> #include <stdlib.h> #include <limits.h> #include <float.h> int main(int argc, char** argv) { printf("Storage size for float : %d \n", sizeof(float)); printf("FLT_MAX : %g\n", (float) FLT_MAX); printf("FLT_MIN : %g\n", (float) FLT_MIN); printf("-FLT_MAX : %g\n", (float) -FLT_MAX); printf("-FLT_MIN : %g\n", (float) -FLT_MIN); printf("DBL_MAX : %g\n", (double) DBL_MAX); printf("DBL_MIN : %g\n", (double) DBL_MIN); printf("-DBL_MAX : %g\n", (double) -DBL_MAX); printf("Precision value: %d\n", FLT_DIG ); return 0; }
编译并执行上述程序时,它在 Linux 上会产生以下结果:
Storage size for float : 4 FLT_MAX : 3.40282e+38 FLT_MIN : 1.17549e-38 -FLT_MAX : -3.40282e+38 -FLT_MIN : -1.17549e-38 DBL_MAX : 1.79769e+308 DBL_MIN : 2.22507e-308 -DBL_MAX : -1.79769e+308 Precision value: 6
void 类型
void 类型指定没有可用值。它用于三种情况:
序号 | 类型和描述 |
---|---|
1 | 函数返回 void C 中有许多函数不返回值,或者可以说它们返回 void。没有返回值的函数的返回类型为 void。例如,void exit (int status); |
2 | 函数参数为 void C 中有许多函数不接受任何参数。不带参数的函数可以接受 void。例如,int rand(void); |
3 | 指向 void 的指针 void * 类型的指针表示对象的地址,但不表示其类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以将其强制转换为任何数据类型。 |
C语言 - 变量
变量只不过是赋予程序可以操作的存储区域的名称。C 中的每个变量都有一个特定的类型,该类型决定变量内存的大小和布局;可以存储在该内存中的值的范围;以及可以应用于该变量的操作集。
变量的名称可以由字母、数字和下划线组成。它必须以字母或下划线开头。大写字母和小写字母是不同的,因为 C 区分大小写。基于上一章中解释的基本类型,将有以下基本变量类型:
序号 | 类型和描述 |
---|---|
1 | char 通常是一个单字节(一个字节)。它是一个整数类型。 |
2 | int 机器上最自然的整数大小。 |
3 | float 单精度浮点值。 |
4 | double 双精度浮点值。 |
5 | void 表示类型不存在。 |
C 编程语言还允许定义各种其他类型的变量,我们将在后续章节中介绍,例如枚举、指针、数组、结构、联合等。在本节中,我们只学习基本变量类型。
C语言中的变量定义
变量定义告诉编译器在哪里以及为变量创建多少存储空间。变量定义指定一个数据类型,并包含一个或多个该类型变量的列表,如下所示:
type variable_list;
这里,type 必须是有效的 C 数据类型,包括 char、w_char、int、float、double、bool 或任何用户定义的对象;variable_list 可以包含一个或多个用逗号分隔的标识符名称。这里显示了一些有效的声明:
int i, j, k; char c, ch; float f, salary; double d;
语句 int i, j, k; 声明并定义了变量 i、j 和 k;它指示编译器创建名为 i、j 和 k 的 int 类型变量。
可以在变量声明中初始化(赋值初始值)变量。初始化器由等号后跟一个常量表达式组成,如下所示:
type variable_name = value;
一些例子:
extern int d = 3, f = 5; // declaration of d and f. int d = 3, f = 5; // definition and initializing d and f. byte z = 22; // definition and initializes z. char x = 'x'; // the variable x has the value 'x'.
对于没有初始化器的定义:具有静态存储期的变量隐式初始化为 NULL(所有字节的值均为 0);所有其他变量的初始值未定义。
C 语言中的变量声明
变量声明向编译器保证存在具有给定类型和名称的变量,以便编译器可以在不需要关于变量的完整细节的情况下继续进行编译。变量定义只在编译时才有意义,编译器在链接程序时需要实际的变量定义。
当使用多个文件并将变量定义在一个文件中(该文件将在程序链接时可用)时,变量声明非常有用。你将使用关键字 extern 在任何地方声明变量。虽然你可以在 C 程序中多次声明一个变量,但它在一个文件、一个函数或一个代码块中只能定义一次。
示例
尝试以下示例,其中变量已在顶部声明,但已在 main 函数内部定义和初始化:
#include <stdio.h> // Variable declaration: extern int a, b; extern int c; extern float f; int main () { /* variable definition: */ int a, b; int c; float f; /* actual initialization */ a = 10; b = 20; c = a + b; printf("value of c : %d \n", c); f = 70.0/3.0; printf("value of f : %f \n", f); return 0; }
编译并执行上述代码时,会产生以下结果:
value of c : 30 value of f : 23.333334
相同的概念也适用于函数声明,你可以在函数声明时提供函数名称,其实际定义可以在其他任何地方给出。例如:
// function declaration int func(); int main() { // function call int i = func(); } // function definition int func() { return 0; }
C 语言中的左值和右值
C 语言中有两种表达式:
左值 (lvalue) - 指向内存位置的表达式称为“左值”表达式。左值可以出现在赋值的左侧或右侧。
右值 (rvalue) - 右值是指存储在内存某个地址的数据值。右值是一个不能为其赋值的表达式,这意味着右值可以出现在赋值的右侧,但不能出现在左侧。
变量是左值,因此它们可以出现在赋值的左侧。数字字面量是右值,因此它们不能被赋值,也不能出现在左侧。请看以下有效和无效的语句:
int g = 20; // valid statement 10 = 20; // invalid statement; would generate compile-time error
C 语言 - 常量和字面量
常量是指程序在其执行期间可能不会更改的固定值。这些固定值也称为字面量。
常量可以是任何基本数据类型,例如整数常量、浮点常量、字符常量或字符串字面量。还有枚举常量。
常量与常规变量一样,只是它们的定义后不能修改其值。
整数字面量
整数字面量可以是十进制、八进制或十六进制常量。前缀指定基数或基:十六进制为 0x 或 0X,八进制为 0,十进制为无。
整数字面量还可以带后缀,它是 U 和 L 的组合,分别表示无符号和长整型。后缀可以是大写或小写,并且可以按任何顺序排列。
以下是一些整数字面量的示例:
212 /* Legal */ 215u /* Legal */ 0xFeeL /* Legal */ 078 /* Illegal: 8 is not an octal digit */ 032UU /* Illegal: cannot repeat a suffix */
以下是各种类型整数字面量的其他示例:
85 /* decimal */ 0213 /* octal */ 0x4b /* hexadecimal */ 30 /* int */ 30u /* unsigned int */ 30l /* long */ 30ul /* unsigned long */
浮点字面量
浮点字面量具有整数部分、小数点、小数部分和指数部分。你可以以十进制形式或指数形式表示浮点字面量。
在表示十进制形式时,必须包含小数点、指数或两者;在表示指数形式时,必须包含整数部分、小数部分或两者。带符号的指数由 e 或 E 引入。
以下是一些浮点字面量的示例:
3.14159 /* Legal */ 314159E-5L /* Legal */ 510E /* Illegal: incomplete exponent */ 210f /* Illegal: no decimal or exponent */ .e55 /* Illegal: missing integer or fraction */
字符常量
字符字面量用单引号括起来,例如,'x' 可以存储在 char 类型的简单变量中。
字符字面量可以是普通字符(例如 'x')、转义序列(例如 '\t')或通用字符(例如 '\u02C0')。
在 C 语言中,某些字符在以反斜杠开头时表示特殊含义,例如换行符 (\n) 或制表符 (\t)。
以下示例显示了一些转义序列字符:
#include <stdio.h> int main() { printf("Hello\tWorld\n\n"); return 0; }
编译并执行上述代码时,会产生以下结果:
Hello World
字符串字面量
字符串字面量或常量用双引号 "" 括起来。字符串包含类似于字符字面量的字符:普通字符、转义序列和通用字符。
可以使用字符串字面量将长行分成多行,并使用空格分隔它们。
以下是一些字符串字面量的示例。所有三种形式都是相同的字符串。
"hello, dear" "hello, \ dear" "hello, " "d" "ear"
定义常量
在 C 语言中定义常量有两种简单的方法:
使用 #define 预处理器。
使用 const 关键字。
#define 预处理器
以下是使用 #define 预处理器定义常量的形式:
#define identifier value
以下示例详细说明了它:
#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 type variable = value;
以下示例详细说明了它:
#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
请注意,良好的编程习惯是用大写字母定义常量。
C语言 - 存储类别
存储类定义 C 程序中变量和/或函数的作用域(可见性)和生命周期。它们位于它们修饰的类型之前。C 程序中有四种不同的存储类:
- auto
- register
- static
- extern
auto 存储类
auto 存储类是所有局部变量的默认存储类。
{ int mount; auto int month; }
上面的示例在同一个存储类中定义了两个变量。'auto' 只能在函数中使用,即局部变量。
register 存储类
register 存储类用于定义应存储在寄存器而不是 RAM 中的局部变量。这意味着变量的最大大小等于寄存器大小(通常是一个字),并且不能对其应用一元 '&' 运算符(因为它没有内存位置)。
{ register int miles; }
register 应该只用于需要快速访问的变量,例如计数器。还应注意,定义 'register' 并不意味着变量将存储在寄存器中。这意味着它可能会存储在寄存器中,具体取决于硬件和实现限制。
static 存储类
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不是在每次进入和离开作用域时创建和销毁它。因此,使局部变量静态允许它们在函数调用之间保持其值。
static 修饰符也可以应用于全局变量。当这样做时,它会导致该变量的作用域限制在声明它的文件中。
在 C 编程中,当在全局变量上使用static 时,它会导致其类的所有对象共享该成员的单个副本。
#include <stdio.h> /* function declaration */ void func(void); static int count = 5; /* global variable */ main() { while(count--) { func(); } return 0; } /* function definition */ void func( void ) { static int i = 5; /* local static variable */ i++; printf("i is %d and count is %d\n", i, count); }
编译并执行上述代码时,会产生以下结果:
i is 6 and count is 4 i is 7 and count is 3 i is 8 and count is 2 i is 9 and count is 1 i is 10 and count is 0
extern 存储类
extern 存储类用于引用对所有程序文件可见的全局变量。当使用 'extern' 时,变量不能被初始化,但是,它将变量名称指向先前定义的存储位置。
当有多个文件并定义一个全局变量或函数,并且该变量或函数也将用于其他文件时,则将在另一个文件中使用extern 来提供已定义变量或函数的引用。为了便于理解,extern 用于在另一个文件中声明全局变量或函数。
如以下所述,当两个或多个文件共享相同的全局变量或函数时,extern 修饰符最常用。
第一个文件:main.c
#include <stdio.h> int count ; extern void write_extern(); main() { count = 5; write_extern(); }
第二个文件:support.c
#include <stdio.h> extern int count; void write_extern(void) { printf("count is %d\n", count); }
在这里,extern 用于在第二个文件中声明count,而它在第一个文件 main.c 中定义。现在,编译这两个文件,如下所示:
$gcc main.c support.c
它将生成可执行程序a.out。执行此程序时,会产生以下结果:
count is 5
C语言 - 运算符
运算符是一个符号,它告诉编译器执行特定的数学或逻辑函数。C 语言富含内置运算符,并提供以下类型的运算符:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 位运算符
- 赋值运算符
- 其他运算符
本章将探讨每个运算符的工作方式。
算术运算符
下表显示了 C 语言支持的所有算术运算符。假设变量A值为 10,变量B值为 20,则:
运算符 | 描述 | 示例 |
---|---|---|
+ | 将两个操作数相加。 | A + B = 30 |
- | 从第一个操作数中减去第二个操作数。 | A - B = -10 |
* | 将两个操作数相乘。 | A * B = 200 |
/ | 将分子除以分母。 | B / A = 2 |
% | 取模运算符,整数除法后的余数。 | B % A = 0 |
++ | 递增运算符使整数的值增加一。 | A++ = 11 |
-- | 递减运算符使整数的值减少一。 | A-- = 9 |
关系运算符
下表显示了 C 语言支持的所有关系运算符。假设变量A值为 10,变量B值为 20,则:
运算符 | 描述 | 示例 |
---|---|---|
== | 检查两个操作数的值是否相等。如果相等,则条件为真。 | (A == B) 为假。 |
!= | 检查两个操作数的值是否相等。如果不相等,则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值。如果是,则条件为真。 | (A > B) 为假。 |
< | 检查左操作数的值是否小于右操作数的值。如果是,则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值。如果是,则条件为真。 | (A >= B) 为假。 |
<= | 检查左操作数的值是否小于或等于右操作数的值。如果是,则条件为真。 | (A <= B) 为真。 |
逻辑运算符
下表显示了C语言支持的所有逻辑运算符。假设变量A的值为1,变量B的值为0,则:
运算符 | 描述 | 示例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中任何一个非零,则条件为真。 | (A || B) 为真。 |
! | 称为逻辑非运算符。用于反转其操作数的逻辑状态。如果条件为真,则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
位运算符
位运算符作用于位并执行逐位运算。&,| 和 ^ 的真值表如下:
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,则:
运算符 | 描述 | 示例 |
---|---|---|
& | 二进制与运算符:如果位同时存在于两个操作数中,则将其复制到结果中。 | (A & B) = 12,即 0000 1100 |
| | 二进制或运算符:如果位存在于任何一个操作数中,则将其复制。 | (A | B) = 61,即 0011 1101 |
^ | 二进制异或运算符:如果位在一个操作数中设置,但在另一个操作数中未设置,则复制该位。 | (A ^ B) = 49,即 0011 0001 |
~ | 二进制反码运算符:是一元运算符,其作用是“反转”位。 | (~A ) = ~(60),即 -61 |
<< | 二进制左移运算符:左操作数的值向左移动右操作数指定的位数。 | A << 2 = 240,即 1111 0000 |
>> | 二进制右移运算符:左操作数的值向右移动右操作数指定的位数。 | A >> 2 = 15,即 0000 1111 |
赋值运算符
下表列出了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 相同 |
其他运算符 ↦ sizeof & 三元运算符
除了上面讨论的运算符外,C语言还支持一些其他重要的运算符,包括sizeof和? :。
运算符 | 描述 | 示例 |
---|---|---|
sizeof() | 返回变量的大小。 | sizeof(a),其中a是整数,将返回4。 |
& | 返回变量的地址。 | &a; 返回变量的实际地址。 |
* | 指向变量的指针。 | *a; |
? : | 条件表达式。 | 如果条件为真 ? 则值为 X : 否则值为 Y |
C语言中的运算符优先级
运算符优先级决定了表达式中项的组合方式以及表达式的计算方式。某些运算符的优先级高于其他运算符;例如,乘法运算符的优先级高于加法运算符。
例如,x = 7 + 3 * 2; 这里,x 的值为 13,而不是 20,因为 * 运算符的优先级高于 +,所以它先计算 3*2,然后加上 7。
表中,优先级最高的运算符位于顶部,优先级最低的运算符位于底部。在一个表达式中,优先级高的运算符将首先被计算。
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元 | + - ! ~ ++ -- (type)* & sizeof | 从右到左 |
乘法 | * / % | 从左到右 |
加法 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
按位与 | & | 从左到右 |
按位异或 | ^ | 从左到右 |
按位或 | | | 从左到右 |
逻辑与 | && | 从左到右 |
逻辑或 | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
C语言 - 决策语句
决策结构要求程序员指定一个或多个条件供程序进行评估或测试,以及如果条件确定为真则要执行的语句,以及可选地,如果条件确定为假则要执行的其他语句。
以下是大多数编程语言中典型的决策结构的一般形式:
C语言将任何非零和非空值视为真,如果值为零或空,则将其视为假值。
C语言提供以下类型的决策语句。
序号 | 语句 & 说明 |
---|---|
1 | if 语句
if 语句由一个布尔表达式和一个或多个语句组成。 |
2 | if...else 语句
if 语句后面可以跟一个可选的else 语句,当布尔表达式为假时执行。 |
3 | 嵌套 if 语句
可以在另一个if或else if语句中使用一个if或else if语句。 |
4 | switch 语句
switch语句允许测试变量与值的列表是否相等。 |
5 | 嵌套 switch 语句
可以在另一个switch语句中使用一个switch语句。 |
? : 运算符
我们在上一章中介绍了条件运算符 ? :,它可以用来替换if...else语句。它具有以下一般形式:
Exp1 ? Exp2 : Exp3;
其中 Exp1、Exp2 和 Exp3 是表达式。注意冒号的使用和位置。
? 表达式的值如下确定:
计算 Exp1。如果为真,则计算 Exp2,并将其作为整个 ? 表达式的值。
如果 Exp1 为假,则计算 Exp3,并将其值作为表达式的值。
C语言 - 循环语句
您可能会遇到需要多次执行代码块的情况。通常情况下,语句是按顺序执行的:函数中的第一个语句首先执行,然后是第二个语句,依此类推。
编程语言提供各种控制结构,允许更复杂的执行路径。
循环语句允许我们多次执行语句或语句组。以下是大多数编程语言中循环语句的一般形式:
C语言提供以下类型的循环来处理循环需求。
序号 | 循环类型 & 说明 |
---|---|
1 | while 循环
在给定条件为真时重复执行语句或语句组。它在执行循环体之前测试条件。 |
2 | for 循环
多次执行一系列语句,并缩短管理循环变量的代码。 |
3 | do...while 循环
它更像 while 语句,只是它在循环体末尾测试条件。 |
4 | 嵌套循环
您可以在任何其他 while、for 或 do..while 循环中使用一个或多个循环。 |
循环控制语句
循环控制语句改变执行的正常顺序。当执行离开作用域时,在该作用域中创建的所有自动对象都将被销毁。
C语言支持以下控制语句。
序号 | 控制语句 & 说明 |
---|---|
1 | break 语句
终止循环或switch语句,并将执行转移到循环或switch语句后的语句。 |
2 | continue 语句
使循环跳过其主体其余部分,并在重复迭代之前立即重新测试其条件。 |
3 | goto 语句
将控制转移到标记的语句。 |
无限循环
如果条件永不为假,则循环将变成无限循环。for循环通常用于此目的。由于构成“for”循环的三个表达式都不需要,因此您可以通过省略条件表达式来创建一个无限循环。
#include <stdio.h> int main () { for( ; ; ) { printf("This loop will run forever.\n"); } return 0; }
如果条件表达式不存在,则假定其为真。您可能有一个初始化和增量表达式,但是C程序员更常用 for(;;) 结构来表示无限循环。
注意 - 可以通过按 Ctrl + C 键来终止无限循环。
C语言 - 函数
函数是一组共同执行任务的语句。每个C程序至少有一个函数,即main(),并且大多数最简单的程序都可以定义附加函数。
您可以将代码划分为单独的函数。如何将代码划分为不同的函数取决于您,但逻辑上划分方式是每个函数执行特定任务。
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。
C标准库提供了许多内置函数,程序可以调用这些函数。例如,strcat()用于连接两个字符串,memcpy()用于将一个内存位置复制到另一个位置,以及许多其他函数。
函数也可以称为方法、子例程或过程等。
定义函数
C语言中函数定义的一般形式如下:
return_type function_name( parameter list ) { body of the function }
C语言中的函数定义由函数头和函数体组成。以下是函数的所有部分:
返回类型 - 函数可以返回值。返回类型是函数返回的值的数据类型。有些函数执行所需的运算而不返回值。在这种情况下,返回类型是关键字void。
函数名 - 这是函数的实际名称。函数名和参数列表一起构成函数签名。
参数 − 参数就像一个占位符。当调用函数时,您将值传递给参数。此值称为实际参数或自变量。参数列表指的是函数参数的类型、顺序和数量。参数是可选的;也就是说,函数可能不包含任何参数。
函数体 − 函数体包含定义函数功能的一组语句。
示例
下面是名为max()函数的源代码。此函数接受两个参数 num1 和 num2,并返回两者之间的最大值 −
/* function returning the max between two numbers */ int max(int num1, int num2) { /* local variable declaration */ 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 函数时,您会给出函数必须执行的操作的定义。要使用函数,您必须调用该函数来执行定义的任务。
当程序调用函数时,程序控制将转移到被调用函数。被调用的函数执行定义的任务,当执行其 return 语句或到达其函数结束的闭合大括号时,它将程序控制返回到主程序。
要调用函数,您只需将所需的参数与函数名称一起传递,如果函数返回值,则可以存储返回值。例如 −
#include <stdio.h> /* function declaration */ int max(int num1, int num2); int main () { /* local variable definition */ int a = 100; int b = 200; int ret; /* calling a function to get max value */ ret = max(a, b); printf( "Max value is : %d\n", ret ); return 0; } /* function returning the max between two numbers */ int max(int num1, int num2) { /* local variable declaration */ int result; if (num1 > num2) result = num1; else result = num2; return result; }
我们将 max() 与 main() 保持在一起并编译了源代码。运行最终的可执行文件时,它将产生以下结果 −
Max value is : 200
函数参数
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数在函数内部的行为类似于其他局部变量,并在进入函数时创建,并在退出时销毁。
调用函数时,可以通过两种方式将参数传递给函数 −
序号 | 调用类型和描述 |
---|---|
1 | 按值调用
此方法将参数的实际值复制到函数的形式参数中。在这种情况下,对函数内部参数所做的更改不会影响参数。 |
2 | 按引用调用
此方法将参数的地址复制到形式参数中。在函数内部,该地址用于访问调用中使用的实际参数。这意味着对参数所做的更改会影响参数。 |
默认情况下,C 使用按值调用传递参数。一般来说,这意味着函数内的代码无法更改用于调用函数的参数。
C语言 - 作用域规则
任何编程中的作用域都是程序的一个区域,在该区域中,已定义的变量可以存在,并且超出该变量范围就不能访问它。在 C 编程语言中,可以在三个地方声明变量 −
在函数或块内,称为局部变量。
在所有函数之外,称为全局变量。
在函数参数的定义中,称为形式参数。
让我们了解一下什么是局部变量、全局变量和形式参数。
局部变量
在函数或块内声明的变量称为局部变量。它们只能由函数或代码块内的语句使用。局部变量对于其自身以外的函数是未知的。以下示例显示了如何使用局部变量。这里所有变量 a、b 和 c 对 main() 函数都是局部的。
#include <stdio.h> int main () { /* local variable declaration */ int a, b; int c; /* actual initialization */ 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> /* global variable declaration */ int g; int main () { /* local variable declaration */ int a, b; /* actual initialization */ 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> /* global variable declaration */ int g = 20; int main () { /* local variable declaration */ int g = 10; printf ("value of g = %d\n", g); return 0; }
编译并执行上述代码时,会产生以下结果:
value of g = 10
形式参数
形式参数在函数内被视为局部变量,并且它们优先于全局变量。以下是一个例子 −
#include <stdio.h> /* global variable declaration */ int a = 20; int main () { /* local variable declaration in main function */ int a = 10; int b = 20; int c = 0; printf ("value of a in main() = %d\n", a); c = sum( a, b); printf ("value of c in main() = %d\n", c); return 0; } /* function to add two integers */ 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
初始化局部变量和全局变量
定义局部变量时,系统不会对其进行初始化,您必须自行初始化它。当您按如下方式定义全局变量时,系统会自动对其进行初始化 −
数据类型 | 初始默认值 |
---|---|
int | 0 |
char | '\0' |
float | 0 |
double | 0 |
指针 | NULL |
正确初始化变量是一种良好的编程习惯,否则您的程序可能会产生意外结果,因为未初始化的变量将采用其内存位置中已有的某些垃圾值。
C语言 - 数组
数组是一种数据结构,可以存储相同类型元素的固定大小的顺序集合。数组用于存储数据集合,但通常将数组视为相同类型变量的集合更有用。
无需声明各个变量,例如 number0、number1、... 和 number99,您可以声明一个数组变量,例如 numbers,并使用 numbers[0]、numbers[1] 和 ...、numbers[99] 来表示各个变量。数组中的特定元素通过索引访问。
所有数组都由连续的内存位置组成。最低地址对应于第一个元素,最高地址对应于最后一个元素。
声明数组
要在 C 中声明数组,程序员应指定元素的类型和数组所需的元素数量,如下所示 −
type arrayName [ arraySize ];
这称为一维数组。arraySize必须是一个大于零的整数常量,而type可以是任何有效的 C 数据类型。例如,要声明一个名为balance的 10 个元素的双精度型数组,请使用此语句 −
double balance[10];
这里balance是一个变量数组,足以容纳多达 10 个双精度数。
初始化数组
您可以在 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;
上述语句将数组中的第 5个元素赋值为 50.0。所有数组的第一个元素的索引均为 0,也称为基索引,数组的最后一个索引将是数组的总大小减 1。下面是我们上面讨论的数组的图形表示 −
访问数组元素
通过对数组名称进行索引来访问元素。这是通过在数组名称后方括号中放置元素的索引来完成的。例如 −
double salary = balance[9];
上述语句将从数组中取出第 10个元素并将该值赋给 salary 变量。以下示例显示了如何使用上述所有三个概念,即声明、赋值和访问数组 −
#include <stdio.h> int main () { int n[ 10 ]; /* n is an array of 10 integers */ int i,j; /* initialize elements of array n to 0 */ for ( i = 0; i < 10; i++ ) { n[ i ] = i + 100; /* set element at location i to i + 100 */ } /* output each array element's value */ 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
数组详解
数组对 C 非常重要,需要更多关注。C 程序员应该清楚以下与数组相关的重要的概念 −
序号 | 概念和描述 |
---|---|
1 | 多维数组
C 支持多维数组。多维数组最简单的形式是二维数组。 |
2 | 将数组传递给函数
您可以通过指定数组的名称(不带索引)来将指向数组的指针传递给函数。 |
3 | 从函数返回数组
C 允许函数返回数组。 |
4 | 指向数组的指针
您可以通过简单地指定数组名称(不带任何索引)来生成指向数组第一个元素的指针。 |
C语言 - 指针
C 语言中的指针易于学习且趣味性强。有些 C 编程任务使用指针更容易完成,而其他任务(例如动态内存分配)则无法在不使用指针的情况下完成。因此,学习指针成为一名完美的 C 程序员是必要的。让我们以简单易懂的步骤开始学习它们。
如您所知,每个变量都是一个内存位置,每个内存位置都有其定义的地址,可以使用地址符 (&) 运算符访问,该运算符表示内存中的地址。考虑以下示例,它打印已定义变量的地址 −
#include <stdio.h> int main () { int var1; char var2[10]; printf("Address of var1 variable: %x\n", &var1 ); printf("Address of var2 variable: %x\n", &var2 ); return 0; }
编译并执行上述代码时,会产生以下结果:
Address of var1 variable: bff5a400 Address of var2 variable: bff5a3f6
什么是指针?
指针是一个变量,其值是另一个变量的地址,即内存位置的直接地址。与任何变量或常量一样,您必须在使用指针存储任何变量地址之前声明它。指针变量声明的一般形式为 −
type *var-name;
这里,type是指针的基类型;它必须是有效的 C 数据类型,而var-name是指针变量的名称。用于声明指针的星号 * 与用于乘法的星号相同。但是,在此语句中,星号用于将变量指定为指针。让我们看一些有效的指针声明 −
int *ip; /* pointer to an integer */ double *dp; /* pointer to a double */ float *fp; /* pointer to a float */ char *ch /* pointer to a character */
所有指针的实际数据类型,无论是整数、浮点数、字符还是其他类型,都是相同的,都是表示内存地址的长十六进制数。不同数据类型指针之间的唯一区别是指针指向的变量或常量的类型。
如何使用指针?
有一些重要的操作,我们将经常借助指针来完成。(a) 我们定义一个指针变量,(b) 将变量的地址赋给指针,并(c) 最终访问指针变量中可用地址处的值。这是通过使用一元运算符* 来完成的,该运算符返回其操作数指定的地址处变量的值。以下示例使用了这些操作 −
#include <stdio.h> int main () { int var = 20; /* actual variable declaration */ int *ip; /* pointer variable declaration */ ip = &var; /* store address of var in pointer variable*/ printf("Address of var variable: %x\n", &var ); /* address stored in pointer variable */ printf("Address stored in ip variable: %x\n", ip ); /* access the value using the pointer */ printf("Value of *ip variable: %d\n", *ip ); return 0; }
编译并执行上述代码时,会产生以下结果:
Address of var variable: bffd8b3c Address stored in ip variable: bffd8b3c Value of *ip variable: 20
空指针
如果您没有确切要分配的地址,则始终将 NULL 值赋给指针变量是一种良好的做法。这是在变量声明时完成的。赋予 NULL 值的指针称为空指针。
NULL 指针是一个值为零的常量,在多个标准库中定义。考虑以下程序 −
#include <stdio.h> int main () { int *ptr = NULL; printf("The value of ptr is : %x\n", ptr ); return 0; }
编译并执行上述代码时,会产生以下结果:
The value of ptr is 0
在大多数操作系统中,程序不允许访问地址为 0 的内存,因为该内存由操作系统保留。然而,内存地址 0 具有特殊意义;它表示指针并非指向可访问的内存位置。但按照惯例,如果指针包含空(零)值,则假定它不指向任何内容。
要检查空指针,可以使用如下所示的 'if' 语句:
if(ptr) /* succeeds if p is not null */ if(!ptr) /* succeeds if p is null */
指针详解
指针的概念很多,但都很容易理解,它们对 C 编程非常重要。任何 C 程序员都应该清楚以下重要的指针概念:
序号 | 概念和描述 |
---|---|
1 | 指针运算
指针可以使用四种算术运算符:++、--、+、- |
2 | 指针数组
可以定义数组来保存多个指针。 |
3 | 指向指针的指针
C 允许你拥有指向指针的指针,以此类推。 |
4 | 在 C 中将指针传递给函数
通过引用或地址传递参数使被调用函数能够在调用函数中更改传递的参数。 |
5 | 在 C 中从函数返回指针
C 允许函数返回指向局部变量、静态变量和动态分配内存的指针。 |
C语言 - 字符串
字符串实际上是由空字符 '\0' 终止的字符一维数组。因此,空终止字符串包含构成字符串的字符,后跟一个空字符。
以下声明和初始化创建一个包含单词“Hello”的字符串。为了在数组末尾保留空字符,包含字符串的字符数组的大小比单词“Hello”中的字符数多一个。
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
如果你遵循数组初始化规则,则可以按如下方式编写上述语句:
char greeting[] = "Hello";
以下是上述在 C/C++ 中定义的字符串的内存表示:
实际上,你不需要在字符串常量的末尾放置空字符。C 编译器在初始化数组时会自动在字符串末尾放置 '\0'。让我们尝试打印上面提到的字符串:
#include <stdio.h> int main () { char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; printf("Greeting message: %s\n", greeting ); return 0; }
编译并执行上述代码时,会产生以下结果:
Greeting message: Hello
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 |
5 | strchr(s1, ch); 返回指向字符串 s1 中字符 ch 首次出现的指针。 |
6 | strstr(s1, s2); 返回指向字符串 s1 中字符串 s2 首次出现的指针。 |
以下示例使用了一些上述函数:
#include <stdio.h> #include <string.h> int main () { char str1[12] = "Hello"; char str2[12] = "World"; char str3[12]; int len ; /* copy str1 into str3 */ strcpy(str3, str1); printf("strcpy( str3, str1) : %s\n", str3 ); /* concatenates str1 and str2 */ strcat( str1, str2); printf("strcat( str1, str2): %s\n", str1 ); /* total lenghth of str1 after concatenation */ len = strlen(str1); printf("strlen(str1) : %d\n", len ); return 0; }
编译并执行上述代码时,会产生以下结果:
strcpy( str3, str1) : Hello strcat( str1, str2): HelloWorld strlen(str1) : 10
C语言 - 结构体
数组允许定义可以保存多种相同类型数据的变量。类似地,结构体是 C 中另一种用户定义的数据类型,允许组合不同类型的数据项。
结构体用于表示记录。假设你想跟踪图书馆中你的书籍。你可能想要跟踪每本书的以下属性:
- 书名
- 作者
- 主题
- 图书 ID
定义结构体
要定义结构体,必须使用struct语句。struct 语句定义了一种新的数据类型,它具有多个成员。struct 语句的格式如下:
struct [structure tag] { member definition; member definition; ... member definition; } [one or more structure variables];
结构体标签是可选的,每个成员定义都是一个普通的变量定义,例如 int i; 或 float f; 或任何其他有效的变量定义。在结构体定义的末尾,在最后一个分号之前,可以指定一个或多个结构体变量,但这是可选的。以下是声明 Book 结构体的方法:
struct Books { char title[50]; char author[50]; char subject[100]; int book_id; } book;
访问结构体成员
要访问结构体的任何成员,我们使用成员访问运算符 (.)。成员访问运算符被编码为结构体变量名和我们想要访问的结构体成员之间的句点。你会使用关键字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; /* Declare Book1 of type Book */ struct Books Book2; /* Declare Book2 of type Book */ /* book 1 specification */ strcpy( Book1.title, "C Programming"); strcpy( Book1.author, "Nuha Ali"); strcpy( Book1.subject, "C Programming Tutorial"); Book1.book_id = 6495407; /* book 2 specification */ strcpy( Book2.title, "Telecom Billing"); strcpy( Book2.author, "Zara Ali"); strcpy( Book2.subject, "Telecom Billing Tutorial"); Book2.book_id = 6495700; /* print Book1 info */ 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); /* print Book2 info */ 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; }; /* function declaration */ void printBook( struct Books book ); int main( ) { struct Books Book1; /* Declare Book1 of type Book */ struct Books Book2; /* Declare Book2 of type Book */ /* book 1 specification */ strcpy( Book1.title, "C Programming"); strcpy( Book1.author, "Nuha Ali"); strcpy( Book1.subject, "C Programming Tutorial"); Book1.book_id = 6495407; /* book 2 specification */ strcpy( Book2.title, "Telecom Billing"); strcpy( Book2.author, "Zara Ali"); strcpy( Book2.subject, "Telecom Billing Tutorial"); Book2.book_id = 6495700; /* print Book1 info */ printBook( Book1 ); /* Print Book2 info */ 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; }; /* function declaration */ void printBook( struct Books *book ); int main( ) { struct Books Book1; /* Declare Book1 of type Book */ struct Books Book2; /* Declare Book2 of type Book */ /* book 1 specification */ strcpy( Book1.title, "C Programming"); strcpy( Book1.author, "Nuha Ali"); strcpy( Book1.subject, "C Programming Tutorial"); Book1.book_id = 6495407; /* book 2 specification */ strcpy( Book2.title, "Telecom Billing"); strcpy( Book2.author, "Zara Ali"); strcpy( Book2.subject, "Telecom Billing Tutorial"); Book2.book_id = 6495700; /* print Book1 info by passing address of Book1 */ printBook( &Book1 ); /* print Book2 info by passing address of 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
位域
位域允许在结构体中打包数据。当内存或数据存储非常宝贵时,这尤其有用。典型的例子包括:
将多个对象打包到一个机器字中。例如,可以压缩 1 位标志。
读取外部文件格式——可以读取非标准文件格式,例如 9 位整数。
C 允许我们在结构体定义中通过在变量后加上 :bit length 来做到这一点。例如:
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 包含 6 个成员:四个 1 位标志 f1..f3、一个 4 位类型和一个 9 位 my_int。
C 会自动尽可能紧凑地打包上述位域,前提是字段的最大长度小于或等于计算机的整数字长。如果不是这种情况,则某些编译器可能允许字段的内存重叠,而其他编译器则将下一个字段存储在下一个字中。
C语言 - 联合体
联合体是 C 中的一种特殊数据类型,允许在同一内存位置存储不同的数据类型。你可以定义一个具有多个成员的联合体,但在任何给定时间,只有一个成员可以包含值。联合体提供了一种有效的方式来将同一内存位置用于多种用途。
定义联合体
要定义联合体,必须像定义结构体一样使用union语句。union 语句定义了一种新的数据类型,为你的程序提供多个成员。union 语句的格式如下:
union [union tag] { member definition; member definition; ... member definition; } [one or more union variables];
联合体标签是可选的,每个成员定义都是一个普通的变量定义,例如 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
在这里,所有成员都能够很好地打印出来,因为一次只使用一个成员。
C语言 - 位域
假设你的 C 程序包含许多在名为 status 的结构体中分组的 TRUE/FALSE 变量,如下所示:
struct { unsigned int widthValidated; unsigned int heightValidated; } status;
此结构体需要 8 个字节的内存空间,但实际上,我们将在每个变量中存储 0 或 1。C 编程语言提供了一种更好的方法来利用此类情况下的内存空间。
如果在结构体中使用此类变量,则可以定义变量的宽度,这将告诉 C 编译器你将只使用这些字节数。例如,上述结构体可以改写如下:
struct { unsigned int widthValidated : 1; unsigned int heightValidated : 1; } status;
上述结构体需要 4 个字节的内存空间来存储 status 变量,但只有 2 位将用于存储值。
如果使用多达 32 个变量,每个变量的宽度为 1 位,则 status 结构体也将使用 4 个字节。但是,一旦你拥有 33 个变量,它将分配下一个内存槽,并将开始使用 8 个字节。让我们检查以下示例以了解这个概念:
#include <stdio.h> #include <string.h> /* define simple structure */ struct { unsigned int widthValidated; unsigned int heightValidated; } status1; /* define a structure with bit fields */ 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
位域声明
位域的声明在结构体内部具有以下形式:
struct { type [member_name] : width ; };
下表描述了位域的变量元素:
序号 | 元素及描述 |
---|---|
1 | 类型 确定如何解释位域值的整数类型。类型可以是 int、signed int 或 unsigned int。 |
2 | 成员名 位域的名称。 |
3 | 宽度 位域中的位数。宽度必须小于或等于指定类型的位宽度。 |
使用预定义宽度定义的变量称为位域。位域可以容纳多个位;例如,如果你需要一个变量来存储 0 到 7 的值,则可以定义一个宽度为 3 位的位域,如下所示:
struct { unsigned int age : 3; } Age;
上述结构体定义指示 C 编译器 age 变量将只使用 3 位来存储值。如果你尝试使用超过 3 位,则它将不允许你这样做。让我们尝试以下示例:
#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; printf( "Age.age : %d\n", Age.age ); return 0; }
编译上述代码时,它将编译并发出警告,执行时,将产生以下结果:
Sizeof( Age ) : 4 Age.age : 4 Age.age : 7 Age.age : 0
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 Programming"); strcpy( book.author, "Nuha Ali"); strcpy( book.subject, "C Programming Tutorial"); book.book_id = 6495407; 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); return 0; }
编译并执行上述代码时,会产生以下结果:
Book title : C Programming Book author : Nuha Ali Book subject : C Programming Tutorial Book book_id : 6495407
typedef 与 #define
#define是一个 C 指令,它也用于定义各种数据类型的别名,类似于typedef,但存在以下差异:
typedef仅限于为类型赋予符号名称,而#define也可用于为值定义别名,例如,你可以将 1 定义为 ONE 等。
typedef的解释由编译器执行,而#define语句由预处理器处理。
以下示例显示如何在程序中使用 #define:
#include <stdio.h> #define TRUE 1 #define FALSE 0 int main( ) { printf( "Value of TRUE : %d\n", TRUE); printf( "Value of FALSE : %d\n", FALSE); return 0; }
编译并执行上述代码时,会产生以下结果:
Value of TRUE : 1 Value of FALSE : 0
C - 输入和输出
当我们说输入时,指的是将一些数据馈送到程序中。输入可以以文件的形式给出,也可以从命令行给出。C编程提供了一套内置函数来读取给定的输入,并根据需要将其馈送到程序。
当我们说输出时,指的是在屏幕、打印机或任何文件中显示一些数据。C编程提供了一套内置函数来将数据输出到计算机屏幕,以及将其保存到文本文件或二进制文件中。
标准文件
C编程将所有设备都视为文件。因此,诸如显示器之类的设备的寻址方式与文件相同,并且在程序执行时会自动打开以下三个文件,以便访问键盘和屏幕。
标准文件 | 文件指针 | 设备 |
---|---|---|
标准输入 | stdin | 键盘 |
标准输出 | stdout | 屏幕 |
标准错误 | stderr | 您的屏幕 |
文件指针是访问文件以进行读写操作的方式。本节说明如何从屏幕读取值以及如何在屏幕上打印结果。
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 ); return 0; }
当编译并执行上述代码时,它会等待您输入一些文本。当您输入文本并按回车键时,程序继续执行,只读取单个字符并将其显示如下:
$./a.out Enter a value : this is test You entered: t
gets() 和 puts() 函数
char *gets(char *s) 函数从stdin读取一行到s指向的缓冲区,直到遇到终止换行符或 EOF(文件结束)。
int puts(const char *s) 函数将字符串 's' 和一个尾随换行符写入stdout。
注意:尽管已弃用使用 gets() 函数,但建议不要使用 gets,而应使用fgets()。
#include <stdio.h> int main( ) { char str[100]; printf( "Enter a value :"); gets( str ); printf( "\nYou entered: "); puts( str ); return 0; }
当编译并执行上述代码时,它会等待您输入一些文本。当您输入文本并按回车键时,程序继续执行,读取直到行尾的完整行,并将其显示如下:
$./a.out Enter a value : this is test You entered: this is test
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); return 0; }
当编译并执行上述代码时,它会等待您输入一些文本。当您输入文本并按回车键时,程序继续执行,读取输入并将其显示如下:
$./a.out Enter a value : seven 7 You entered: seven 7
这里需要注意的是,scanf() 期望输入的格式与您提供的 %s 和 %d 相同,这意味着您必须提供有效的输入,例如“字符串 整数”。如果您提供“字符串 字符串”或“整数 整数”,则会被认为是错误的输入。其次,在读取字符串时,scanf() 一旦遇到空格就会停止读取,因此“this is test”对于 scanf() 来说是三个字符串。
C - 文件 I/O
上一章解释了 C 编程语言处理的标准输入和输出设备。本章介绍 C 程序员如何创建、打开、关闭文本文件或二进制文件以进行数据存储。
文件表示一系列字节,无论它是文本文件还是二进制文件。C 编程语言提供高级函数以及低级(操作系统级)调用来处理存储设备上的文件。本章将引导您完成文件管理的重要调用。
打开文件
您可以使用fopen( )函数创建新文件或打开现有文件。此调用将初始化类型为FILE的对象,其中包含控制流所需的所有信息。此函数调用的原型如下:
FILE *fopen( const char * filename, const char * mode );
这里,filename是一个字符串文字,您将使用它来命名您的文件,并且access mode可以具有以下值之一:
序号 | 模式 & 说明 |
---|---|
1 | r 以只读方式打开现有文本文件。 |
2 | w 打开文本文件以写入。如果它不存在,则创建一个新文件。在这里,您的程序将从文件的开头开始写入内容。 |
3 | a 以追加模式打开文本文件以写入。如果它不存在,则创建一个新文件。在这里,您的程序将开始将内容追加到现有文件内容中。 |
4 | r+ 打开文本文件进行读写。 |
5 | w+ 打开文本文件进行读写。如果文件存在,则首先将其截断为零长度;如果不存在,则创建文件。 |
6 | 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目录。如果没有,则在继续之前,必须在您的机器上创建此目录。
#include <stdio.h> main() { FILE *fp; 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> main() { FILE *fp; 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);
这两个函数都应用于读取或写入内存块——通常是数组或结构。
C语言 - 预处理器
C 预处理器不是编译器的一部分,而是编译过程中的一个单独步骤。简单来说,C 预处理器只是一个文本替换工具,它指示编译器在实际编译之前进行必要的预处理。我们将 C 预处理器称为 CPP。
所有预处理器命令都以井号 (#) 开头。它必须是第一个非空字符,并且为了可读性,预处理器指令应该从第一列开始。以下部分列出了所有重要的预处理器指令:
序号 | 指令 & 说明 |
---|---|
1 | #define 替换预处理器宏。 |
2 | #include 从另一个文件插入特定的头文件。 |
3 | #undef 取消定义预处理器宏。 |
4 | #ifdef 如果此宏已定义,则返回 true。 |
5 | #ifndef 如果此宏未定义,则返回 true。 |
6 | #if 测试编译时条件是否为 true。 |
7 | #else #if 的替代方案。 |
8 | #elif #else 和 #if 合并成一个语句。 |
9 | #endif 结束预处理器条件。 |
10 | #error 在 stderr 上打印错误消息。 |
11 | #pragma 使用标准化的方法向编译器发出特殊命令。 |
预处理器示例
分析以下示例以了解各种指令。
#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,因此您可以在编译过程中动态地打开和关闭调试。
预定义宏
ANSI C 定义了许多宏。尽管每个宏都可以在编程中使用,但不应直接修改预定义宏。
序号 | 宏 & 描述 |
---|---|
1 | __DATE__ 当前日期,以 "MMM DD YYYY" 格式的字符字面量表示。 |
2 | __TIME__ 当前时间,以 "HH:MM:SS" 格式的字符字面量表示。 |
3 | __FILE__ 包含当前文件名,作为字符串字面量。 |
4 | __LINE__ 包含当前行号,作为十进制常量。 |
5 | __STDC__ 当编译器符合 ANSI 标准时定义为 1。 |
让我们尝试以下示例:
#include <stdio.h> int main() { printf("File :%s\n", __FILE__ ); printf("Date :%s\n", __DATE__ ); printf("Time :%s\n", __TIME__ ); printf("Line :%d\n", __LINE__ ); printf("ANSI :%d\n", __STDC__ ); }
当以上代码在一个名为test.c的文件中编译并执行时,会产生以下结果:
File :test.c Date :Jun 2 2012 Time :03:36:24 Line :8 ANSI :1
预处理器操作符
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操作符用于常量表达式中,以确定是否使用 #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
C语言 - 头文件
头文件是一个扩展名为.h的文件,其中包含要在多个源文件中共享的 C 函数声明和宏定义。头文件有两种类型:程序员编写的文件和编译器自带的文件。
您可以通过使用 C 预处理指令#include包含头文件来请求在程序中使用它,就像您已经看到包含编译器自带的stdio.h头文件一样。
包含头文件等同于复制头文件的内容,但我们不这样做,因为这容易出错,并且在源文件中复制头文件的内容不是一个好主意,尤其是在程序中有多个源文件的情况下。
在 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 ()); }
编译器将看到与program.c读取相同的令牌流。
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 可以通过您的 Makefile 使用 -D 选项定义。
C语言 - 类型强制转换
类型转换是一种将变量从一种数据类型转换为另一种数据类型的方法。例如,如果要将“long”值存储到简单的整数中,则可以将“long”类型转换为“int”类型。您可以使用强制转换运算符显式地将值从一种类型转换为另一种类型,如下所示:
(type_name) expression
考虑以下示例,其中强制转换运算符导致将一个整型变量除以另一个整型变量作为浮点运算执行:
#include <stdio.h> 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。考虑一个将字符与整数相加的例子:
#include <stdio.h> main() { int i = 17; char c = 'c'; /* ascii value is 99 */ int sum; sum = i + c; printf("Value of sum : %d\n", sum ); }
编译并执行上述代码时,会产生以下结果:
Value of sum : 116
这里,sum的值是116,因为编译器正在进行整数提升并将'c'的值转换为ASCII码,然后再执行实际的加法运算。
通常的算术转换
通常的算术转换是隐式执行的,以将其值转换为公共类型。编译器首先执行整数提升;如果操作数的类型仍然不同,则它们将转换为以下层次结构中出现的最高类型:
通常的算术转换不适用于赋值运算符,也不适用于逻辑运算符 && 和 ||。让我们来看下面的例子来理解这个概念:
#include <stdio.h> main() { int i = 17; char c = 'c'; /* ascii value is 99 */ float sum; sum = i + c; printf("Value of sum : %f\n", sum ); }
编译并执行上述代码时,会产生以下结果:
Value of sum : 116.000000
这里,很容易理解,首先 c 被转换为整数,但是由于最终值是 double,通常的算术转换适用,编译器将 i 和 c 转换为 'float' 并将它们相加,得到一个 'float' 结果。
C语言 - 错误处理
因此,C 编程语言不直接支持错误处理,但作为一个系统编程语言,它以返回值的形式提供对底层的访问。大多数 C 甚至 Unix 函数调用在发生任何错误时返回 -1 或 NULL,并设置错误代码errno。它被设置为全局变量,表示在任何函数调用期间发生错误。您可以在<error.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, "Value of errno: %d\n", errno); perror("Error printed by perror"); fprintf(stderr, "Error opening file: %s\n", strerror( errnum )); } else { fclose (pf); } return 0; }
编译并执行上述代码时,会产生以下结果:
Value of errno: 2 Error printed by perror: No such file or directory Error opening file: No such file or directory
除以零错误
这是一个常见问题,在除任何数字时,程序员不检查除数是否为零,最终会产生运行时错误。
下面的代码通过在除法之前检查除数是否为零来解决这个问题:
#include <stdio.h> #include <stdlib.h> main() { int dividend = 20; int divisor = 0; int quotient; if( divisor == 0){ fprintf(stderr, "Division by zero! Exiting...\n"); exit(-1); } quotient = dividend / divisor; fprintf(stderr, "Value of quotient : %d\n", quotient ); exit(0); }
编译并执行上述代码时,会产生以下结果:
Division by zero! Exiting...
程序退出状态
通常的做法是,如果程序在成功操作后退出,则以 EXIT_SUCCESS 的值退出。这里,EXIT_SUCCESS 是一个宏,定义为 0。
如果程序中存在错误条件并且您要退出,则应以 EXIT_FAILURE 状态退出,它定义为 -1。因此,让我们将以上程序编写如下:
#include <stdio.h> #include <stdlib.h> main() { int dividend = 20; int divisor = 5; int quotient; if( divisor == 0) { fprintf(stderr, "Division by zero! Exiting...\n"); exit(EXIT_FAILURE); } quotient = dividend / divisor; fprintf(stderr, "Value of quotient : %d\n", quotient ); exit(EXIT_SUCCESS); }
编译并执行上述代码时,会产生以下结果:
Value of quotient : 4
C语言 - 递归
递归是一个以自相似的方式重复项目的过程。在编程语言中,如果一个程序允许你在同一个函数内部调用该函数,那么这就被称为函数的递归调用。
void recursion() { recursion(); /* function calls itself */ } int main() { recursion(); }
C 编程语言支持递归,即函数自身调用自身。但是,在使用递归时,程序员需要注意定义函数的退出条件,否则它将进入无限循环。
递归函数非常有用,可以解决许多数学问题,例如计算数字的阶乘、生成斐波那契数列等。
数字阶乘
以下示例使用递归函数计算给定数字的阶乘:
#include <stdio.h> unsigned long long int factorial(unsigned int i) { if(i <= 1) { return 1; } return i * factorial(i - 1); } int main() { int i = 12; printf("Factorial of %d is %d\n", i, factorial(i)); return 0; }
编译并执行上述代码时,会产生以下结果:
Factorial of 12 is 479001600
斐波那契数列
以下示例使用递归函数生成给定数字的斐波那契数列:
#include <stdio.h> int fibonacci(int i) { if(i == 0) { return 0; } if(i == 1) { return 1; } return fibonacci(i-1) + fibonacci(i-2); } int main() { int i; for (i = 0; i < 10; i++) { printf("%d\t\n", fibonacci(i)); } return 0; }
编译并执行上述代码时,会产生以下结果:
0 1 1 2 3 5 8 13 21 34
C语言 - 可变参数
有时,你可能会遇到这样的情况:你想要一个函数,它可以接受可变数量的参数(即参数),而不是预定义数量的参数。C 编程语言为此提供了解决方案,你可以定义一个可以根据你的需求接受可变数量参数的函数。以下示例显示了此类函数的定义。
int func(int, ... ) { . . . } int main() { func(1, 2, 3); func(1, 2, 3, 4); }
需要注意的是,函数**func()** 的最后一个参数是省略号,即三个点 (**...**),而省略号之前的参数始终是**int** 类型,它将表示传递的可变参数的总数。要使用此功能,你需要使用**stdarg.h** 头文件,该文件提供实现可变参数功能的函数和宏,并遵循以下步骤:
定义一个函数,其最后一个参数为省略号,而省略号之前的参数始终为**int** 类型,它将表示参数的数量。
在函数定义中创建一个**va_list** 类型的变量。此类型在 stdarg.h 头文件中定义。
使用**int** 参数和**va_start** 宏将**va_list** 变量初始化为参数列表。va_start 宏在 stdarg.h 头文件中定义。
使用**va_arg** 宏和**va_list** 变量访问参数列表中的每个项目。
使用宏**va_end** 清理分配给**va_list** 变量的内存。
现在让我们遵循上述步骤,编写一个可以接受可变数量参数并返回其平均值的简单函数:
#include <stdio.h> #include <stdarg.h> double average(int num,...) { va_list valist; double sum = 0.0; int i; /* initialize valist for num number of arguments */ va_start(valist, num); /* access all the arguments assigned to valist */ for (i = 0; i < num; i++) { sum += va_arg(valist, int); } /* clean memory reserved for 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()** 被调用了两次,每次第一个参数都表示传递的可变参数的总数。仅使用省略号传递可变数量的参数。
Average of 2, 3, 4, 5 = 3.500000 Average of 5, 10, 15 = 10.000000
C语言 - 内存管理
本章解释 C 语言中的动态内存管理。C 编程语言提供了一些用于内存分配和管理的函数。这些函数可以在**<stdlib.h>** 头文件中找到。
序号 | 函数和描述 |
---|---|
1 | void *calloc(int num, int size); 此函数分配一个包含**num** 个元素的数组,每个元素的大小(以字节为单位)为**size**。 |
2 | void free(void *address); 此函数释放由 address 指定的内存块。 |
3 | void *malloc(size_t size); 此函数分配一个大小为**num** 字节的数组,并将其保留为未初始化状态。 |
4 | void *realloc(void *address, int newsize); 此函数重新分配内存,将其扩展到**newsize**。 |
动态分配内存
在编程中,如果你知道数组的大小,那么它很容易,你可以将其定义为数组。例如,要存储任何人的姓名,最多可以存储 100 个字符,因此你可以定义如下内容:
char name[100];
但是现在让我们考虑这种情况:你不知道需要存储文本的长度,例如,你想要存储有关某个主题的详细说明。这里我们需要定义一个指向字符的指针,而无需定义需要多少内存,之后,根据需要,我们可以分配内存,如下面的示例所示:
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char name[100]; char *description; strcpy(name, "Zara Ali"); /* allocate memory dynamically */ description = 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"); /* allocate memory dynamically */ description = malloc( 30 * sizeof(char) ); if( description == NULL ) { fprintf(stderr, "Error - unable to allocate required memory\n"); } else { strcpy( description, "Zara ali a DPS student."); } /* suppose you want to store bigger description */ description = 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 ); /* release memory using free() function */ free(description); }
编译并执行上述代码后,将产生以下结果。
Name = Zara Ali Description: Zara ali a DPS student.She is in class 10th
你可以尝试在不重新分配额外内存的情况下运行上述示例,并且由于 description 中缺少可用内存,strcat() 函数将给出错误。
C语言 - 命令行参数
可以将一些值从命令行传递到 C 程序的执行过程中。这些值称为**命令行参数**,它们在许多情况下对你的程序非常重要,尤其是在你想要从外部控制程序而不是在代码中硬编码这些值时。
命令行参数使用 main() 函数参数处理,其中**argc** 指的是传递的参数数量,**argv[]** 是一个指向传递给程序的每个参数的指针数组。以下是一个简单的示例,它检查是否从命令行提供了任何参数并相应地采取行动:
#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