C - 快速指南
C Language - Overview
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语言的继承者,它是在20世纪70年代早期引入的。
该语言于1988年由美国国家标准协会(ANSI)正式确定。
UNIX OS完全用C语言编写。
今天,C是最广泛使用和最流行的系统编程语言。
大多数最先进的软件都是用C语言实现的。
今天最流行的Linux操作系统和RDBMS MySQL都是用C语言编写的。
为什么用C?
C最初用于系统开发工作,尤其是构成操作系统的程序。 C被用作系统开发语言,因为它生成的代码运行速度几乎与汇编语言编写的代码一样快。 使用C的一些例子可能是 -
- 操作系统
- 语言编译器
- Assemblers
- 文字编辑
- Print Spoolers
- 网络驱动程序
- Modern Programs
- Databases
- 语言口译员
- Utilities
C程序
AC程序可以从3行到数百万行不等,应该写入一个或多个扩展名为".c"文本文件; 例如, hello.c 。 您可以使用"vi" , "vim"或任何其他文本编辑器将C程序写入文件。
本教程假定您知道如何编辑文本文件以及如何在程序文件中编写源代码。
C - Environment Setup
本地环境设置 (Local Environment Setup)
如果要为C编程语言设置环境,则需要在计算机上使用以下两种软件工具:(a)文本编辑器和(b)C编译器。
文本编辑器 (Text Editor)
这将用于键入您的程序。 少数编辑器的示例包括Windows Notepad,OS Edit命令,Brief,Epsilon,EMACS和vim或vi。
文本编辑器的名称和版本可能因不同的操作系统而异。 例如,Notepad将在Windows上使用,vim或vi可以在Windows上以及Linux或UNIX上使用。
使用编辑器创建的文件称为源文件,它们包含程序源代码。 C程序的源文件通常以扩展名“ .c ”命名。
在开始编程之前,请确保您有一个文本编辑器,并且您有足够的经验来编写计算机程序,将其保存在文件中,编译并最终执行。
C编译器
源文件中编写的源代码是程序的可读源代码。 它需要被“编译”成机器语言,以便您的CPU可以按照给出的指令实际执行程序。
编译器将源代码编译成最终的可执行程序。 最常用和免费的编译器是GNU C/C ++编译器,否则如果您有相应的操作系统,则可以使用HP或Solaris编译器。
以下部分说明如何在各种OS上安装GNU C/C ++编译器。 我们一直提到C/C ++,因为GNU gcc编译器适用于C和C ++编程语言。
在UNIX/Linux上安装
如果您使用的是Linux or 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编写的,所有给出的示例都是在Linux系统的Cent OS风格上编译的。
在Mac OS上安装
如果您使用的是Mac OS X,获取GCC的最简单方法是从Apple的网站下载Xcode开发环境,并按照简单的安装说明进行操作。 一旦你有Xcode设置,你就可以使用GNU编译器来实现C/C ++。
Xcode目前可在developer.apple.com/technologies/tools/ 。
Installation on Windows
要在Windows上安装GCC,您需要安装MinGW。 要安装MinGW,请访问MinGW主页www.mingw.org ,然后点击MinGW下载页面的链接。 下载最新版本的MinGW安装程序,该程序应命名为MinGW-“version”.exe。
在安装Min GW时,至少必须安装gcc-core,gcc-g ++,binutils和MinGW运行时,但您可能希望安装更多。
将MinGW安装的bin子目录添加到PATH环境变量中,以便您可以通过简单名称在命令行上指定这些工具。
安装完成后,您将能够从Windows命令行运行gcc,g ++,ar,ranlib,dlltool和其他几个GNU工具。
C - Program Structure
在我们研究C编程语言的基本构建块之前,让我们看一下最基本的C程序结构,以便我们在接下来的章节中将其作为参考。
Hello,World!的例子
AC程序基本上由以下部分组成 -
- 预处理程序命令
- Functions
- Variables
- 声明和表达
- Comments
让我们看一下打印“Hello World”字样的简单代码 -
#include <stdio.h>
int main() {
/* my first program in 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并按Enter键编译代码。
如果代码中没有错误,命令提示符将转到下一行并生成a.out可执行文件。
现在,键入a.out以执行您的程序。
您将在屏幕上看到输出"Hello World" 。
$ gcc hello.c
$ ./a.out
Hello, World!
确保gcc编译器在您的路径中,并且您在包含源文件hello.c的目录中运行它。
C - Basic Syntax
您已经看到了C程序的基本结构,因此很容易理解C编程语言的其他基本构建块。
C中的标记
AC程序由各种令牌组成,令牌可以是关键字,标识符,常量,字符串文字或符号。 例如,以下C语句由五个标记组成 -
printf("Hello, World! \n");
个人代币是 -
printf
(
"Hello, World! \n"
)
;
分号(Semicolons)
在C程序中,分号是语句终止符。 也就是说,每个单独的语句必须以分号结束。 它表示一个逻辑实体的结束。
以下是两个不同的陈述 -
printf("Hello, World! \n");
return 0;
注释 (Comments)
注释就像帮助C程序中的文本一样,编译器会忽略它们。 它们以/ *开头并以字符* /结尾,如下所示 -
/* my first program in C */
您不能在注释中添加注释,也不会在字符串或字符文字中出现。
标识符 (Identifiers)
AC标识符是用于标识变量,函数或任何其他用户定义项的名称。 标识符以字母A到Z,a到z或下划线'_'开头,后跟零个或多个字母,下划线和数字(0到9)。
C不允许标识符中的标点符号,如@,$和%。 C是case-sensitive编程语言。 因此, Manpower和manpower是C中的两个不同的标识符。以下是可接受标识符的一些示例 -
mohd zara abc move_name a_123
myname50 _temp j a23b9 retVal
关键字 (Keywords)
以下列表显示了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
水果和=之间,或者=和苹果之间不需要空格字符,但如果您希望提高可读性,可以自由添加一些空白字符。
C - Data Types
c中的数据类型是指用于声明不同类型的变量或函数的扩展系统。 变量的类型决定了它在存储中占用的空间大小以及如何解释存储的位模式。
C中的类型可分为以下几类 -
Sr.No. | 类型和描述 |
---|---|
1 | Basic Types 它们是算术类型,并进一步分为:(a)整数类型和(b)浮点类型。 |
2 | Enumerated types 它们又是算术类型,它们用于定义只能在整个程序中分配某些离散整数值的变量。 |
3 | The type void 类型说明符void表示没有可用的值。 |
4 | Derived types 它们包括(a)指针类型,(b)数组类型,(c)结构类型,(d)联合类型和(e)函数类型。 |
数组类型和结构类型统称为聚合类型。 函数的类型指定函数返回值的类型。 我们将在下一节中看到基本类型,其他类型将在后面的章节中介绍。
整数类型 (Integer Types)
下表提供了标准整数类型及其存储大小和值范围的详细信息 -
类型 | 存储大小 | 价值范围 |
---|---|---|
char | 1个字节 | -128到127或0到255 |
unsigned char | 1个字节 | 0到255 |
signed char | 1个字节 | -128 to 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 to 32,767 |
unsigned short | 2个字节 | 0 to 65,535 |
long | 4字节 | -2,147,483,648 to 2,147,483,647 |
unsigned long | 4字节 | 0 to 4,294,967,295 |
要在特定平台上获取类型或变量的确切大小,可以使用sizeof运算符。 表达式sizeof(type)产生对象或类型的存储大小(以字节为单位)。 下面给出了在任何机器上获取int类型大小的示例 -
#include <stdio.h>
#include <limits.h>
int main() {
printf("Storage size for int : %d \n", sizeof(int));
return 0;
}
编译并执行上述程序时,它会在Linux上生成以下结果 -
Storage size for int : 4
Floating-Point Types
下表提供了具有存储大小和值范围及其精度的标准浮点类型的详细信息 -
类型 | 存储大小 | 价值范围 | 精确 |
---|---|---|---|
float | 4个字节 | 1.2E-38至3.4E + 38 | 6 decimal places |
double | 8个字节 | 2.3E-308至1.7E + 308 | 15 decimal places |
long double | 10个字节 | 3.4E-4932至1.1E + 4932 | 19 decimal places |
头文件float.h定义了一些宏,允许您使用这些值以及有关程序中实数的二进制表示的其他详细信息。 以下示例打印float类型占用的存储空间及其范围值 -
#include <stdio.h>
#include <float.h>
int main() {
printf("Storage size for float : %d \n", sizeof(float));
printf("Minimum float positive value: %E\n", FLT_MIN );
printf("Maximum float positive value: %E\n", FLT_MAX );
printf("Precision value: %d\n", FLT_DIG );
return 0;
}
编译并执行上述程序时,它会在Linux上生成以下结果 -
Storage size for float : 4
Minimum float positive value: 1.175494E-38
Maximum float positive value: 3.402823E+38
Precision value: 6
虚空类型
void类型指定没有可用的值。 它用于三种情况 -
Sr.No. | 类型和描述 |
---|---|
1 | Function returns as void C中有各种函数不返回任何值,或者可以说它们返回void。 没有返回值的函数的返回类型为void。 例如, void exit (int status); |
2 | Function arguments as void C中有各种不接受任何参数的功能。 没有参数的函数可以接受void。 例如, int rand(void); |
3 | Pointers to void void *类型的指针表示对象的地址,但不表示其类型。 例如,内存分配函数void *malloc( size_t size ); 返回一个指向void的指针,该指针可以转换为任何数据类型。 |
C - Variables
变量只不过是我们的程序可以操作的存储区域的名称。 C中的每个变量都有一个特定的类型,它决定了变量内存的大小和布局; 可存储在该内存中的值范围; 以及可以应用于变量的操作集。
变量的名称可以由字母,数字和下划线字符组成。 它必须以字母或下划线开头。 大写和小写字母是不同的,因为C区分大小写。 基于前一章中解释的基本类型,将有以下基本变量类型 -
Sr.No. | 类型和描述 |
---|---|
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程序中多次声明变量,但只能在文件,函数或代码块中定义一次。
例子 (Example)
尝试以下示例,其中变量已在顶部声明,但它们已在主函数内定义和初始化 -
#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 - 术语rvalue是指存储在内存中某个地址的数据值。 rvalue是一个不能赋值给它的表达式,这意味着rvalue可能出现在右侧但不出现在赋值的左侧。
变量是左值,因此它们可能出现在赋值的左侧。 数字文字是右值,因此它们可能不会被分配,也不能出现在左侧。 看看以下有效和无效的陈述 -
int g = 20; // valid statement
10 = 20; // invalid statement; would generate compile-time error
C - Constants and Literals
常量是指程序在执行期间不会改变的固定值。 这些固定值也称为literals 。
常量可以是任何基本数据类型,如an integer constant, a floating constant, a character constant, or a string literal 。 还有枚举常量。
常量被视为常规变量,除了它们的值在定义后无法修改。
整型常量 (Integer Literals)
整数文字可以是十进制,八进制或十六进制常量。 前缀指定基数或基数:十六进制为0x或0X,八进制为0,十进制为空。
整数文字也可以有一个后缀,它是U和L的组合,分别对于unsigned和long。 后缀可以是大写或小写,可以按任何顺序排列。
以下是整数文字的一些示例 -
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 */
浮点型常量 (Floating-point Literals)
浮点文字有一个整数部分,一个小数点,一个小数部分和一个指数部分。 您可以以十进制形式或指数形式表示浮点文字。
在表示十进制形式时,必须包括小数点,指数或两者; 并且在表示指数形式时,必须包括整数部分,小数部分或两者。 带符号的指数由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)。
逃脱序列 | 含义 |
---|---|
\\ | \字符 |
\' | ' character(字符) |
\" | " character(字符) |
\? | ? 字符 |
\a | Alert or bell |
\b | Backspace |
\f | Form feed |
\n | Newline |
\r | Carriage return |
\t | 水平标签 |
\v | 垂直标签 |
\ooo | 八进制数字为一到三位数 |
\xhh。 。 。 | 十六进制数字的一个或多个数字 |
以下是显示一些转义序列字符的示例 -
#include <stdio.h>
int main() {
printf("Hello\tWorld\n\n");
return 0;
}
编译并执行上述代码时,会产生以下结果 -
Hello World
字符串常量 (String Literals)
字符串文字或常量用双引号“”括起来。 字符串包含与字符文字类似的字符:普通字符,转义序列和通用字符。
您可以使用字符串文字将长行分成多行,并使用空格分隔它们。
以下是字符串文字的一些示例。 所有三种形式都是相同的字符串。
"hello, dear"
"hello, \
dear"
"hello, " "d" "ear"
定义常量
C中有两种简单的方法来定义常量 -
使用#define预处理器。
使用const关键字。
#define预处理器 (The #define Preprocessor)
下面给出了使用#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
请注意,在CAPITALS中定义常量是一种很好的编程习惯。
C - Storage Classes
存储类定义C程序中变量和/或函数的范围(可见性)和生命周期。 它们位于它们修改的类型之前。 我们在C程序中有四种不同的存储类 -
- auto
- register
- static
- extern
自动存储类
auto存储类是所有局部变量的默认存储类。
{
int mount;
auto int month;
}
上面的示例在同一存储类中定义了两个变量。 'auto'只能在函数中使用,即局部变量。
寄存器存储类
register存储类用于定义应存储在寄存器而不是RAM中的局部变量。 这意味着变量的最大大小等于寄存器大小(通常是一个单词),并且不能将一元“&”运算符应用于它(因为它没有内存位置)。
{
register int miles;
}
该寄存器只应用于需要快速访问的变量,例如计数器。 还应注意,定义“寄存器”并不意味着变量将存储在寄存器中。 这意味着它可能会根据硬件和实现限制存储在寄存器中。
静态存储类
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用于在另一个文件中声明全局变量或函数。
当有两个或多个文件共享相同的全局变量或函数时,最常用的是外部修饰符,如下所述。
First File: main.c
#include <stdio.h>
int count ;
extern void write_extern();
main() {
count = 5;
write_extern();
}
Second File: 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 - Operators
运算符是告诉编译器执行特定数学或逻辑函数的符号。 C语言内置运算符丰富,并提供以下类型的运算符 -
- 算术运算符
- 关系运算符
- 逻辑运算符
- 按位运算符
- 分配运算符
- 其它运算符
在本章中,我们将研究每个运算符的工作方式。
算术运算符 (Arithmetic Operators)
下表显示了C语言支持的所有算术运算符。 假设变量A保持10,变量B保持20然后 -
操作者 | 描述 | 例 |
---|---|---|
+ | 添加两个操作数。 | A + B = 30 |
− | 从第一个减去第二个操作数。 | A - B = -10 |
* | 将两个操作数相乘。 | A * B = 200 |
/ | Divides numerator by de-numerator. | B/A = 2 |
% | 模数运算符和整数除法后的余数。 | B%A = 0 |
++ | 递增运算符将整数值增加1。 | A ++ = 11 |
-- | 递减运算符将整数值减1。 | A-- = 9 |
关系运算符 (Relational Operators)
下表显示了C支持的所有关系运算符。假设变量A保持10,变量B保持20然后 -
操作者 | 描述 | 例 |
---|---|---|
== | 检查两个操作数的值是否相等。 如果是,则条件成立。 | (A == B)不是真的。 |
!= | 检查两个操作数的值是否相等。 如果值不相等,则条件成立。 | (A!= B)是真的。 |
> | 检查左操作数的值是否大于右操作数的值。 如果是,则条件成立。 | (A> B)不是真的。 |
< | 检查左操作数的值是否小于右操作数的值。 如果是,则条件成立。 | (A < B) 为真 |
>= | 检查左操作数的值是否大于或等于右操作数的值。 如果是,则条件成立。 | (A> = B)不是真的。 |
<= | 检查左操作数的值是否小于或等于右操作数的值。 如果是,则条件成立。 | (A <= B)是真的。 |
逻辑运算符 (Logical Operators)
下表显示了C语言支持的所有逻辑运算符。 假设变量A保持1而变量B保持0,则 -
操作者 | 描述 | 例 |
---|---|---|
&& | 称为逻辑AND运算符。 如果两个操作数都不为零,则条件成立。 | (A && B)是假的。 |
|| | 称为逻辑OR运算符。 如果两个操作数中的任何一个非零,则条件变为真。 | (A || B)是真的。 |
! | 称为逻辑非运算符。 它用于反转其操作数的逻辑状态。 如果条件为真,则Logical NOT运算符将使其为false。 | !(A && B)是真的。 |
按位运算符 (Bitwise Operators)
按位运算符处理位并执行逐位运算。 &,|和^的真值表如下 -
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,则 -
操作者 | 描述 | 例 |
---|---|---|
& | 如果二进制AND运算符存在于两个操作数中,则它会将结果复制到结果中。 | (A&B)= 12,即0000 1100 |
| | 二进制OR运算符如果存在于任一操作数中,则复制一位。 | (A | B)= 61,即0011 1101 |
^ | 二进制异或运算符如果在一个操作数中设置但不在两个操作数中设置,则复制该位。 | (A ^ B)= 49,即0011 0001 |
~ | 二元一元补语运算符是一元的,具有“翻转”位的效果。 | (~A)= -60,即。 1100 0100 in 2的补码形式。 |
<< | 二进制左移运算符。 左操作数值向左移动右操作数指定的位数。 | A << 2 = 240即1111 0000 |
>> | 二进制右移运算符。 左操作数值向右移动右操作数指定的位数。 | A >> 2 = 15即0000 1111 |
赋值操作符 (Assignment Operators)
下表列出了C语言支持的赋值运算符 -
操作者 | 描述 | 例 |
---|---|---|
= | 简单赋值运算符。 将右侧操作数的值分配给左侧操作数 | C = A + B将A + B的值赋给C |
+= | 添加AND赋值运算符。 它将右操作数添加到左操作数并将结果分配给左操作数。 | C + = A等于C = C + A. |
-= | 减去AND赋值运算符。 它从左操作数中减去右操作数,并将结果赋给左操作数。 | C - = A相当于C = C - A. |
*= | 乘以AND赋值运算符。 它将右操作数与左操作数相乘,并将结果赋给左操作数。 | C * = A等于C = C * A. |
/= | 除法和赋值运算符。 它将左操作数与右操作数分开,并将结果赋给左操作数。 | C/= A相当于C = C/A. |
%= | 模数和赋值运算符。 它使用两个操作数来获取模数,并将结果赋给左操作数。 | C%= A等于C = C%A |
<<= | 左移AND赋值运算符。 | C << = 2与C = C << 2相同 |
>>= | 右移AND赋值运算符。 | C >> = 2与C = C >> 2相同 |
&= | 按位AND赋值运算符。 | C&= 2与C = C&2相同 |
^= | 按位异或和赋值运算符。 | C ^ = 2与C = C ^ 2相同 |
|= | 按位包含OR和赋值运算符。 | C | = 2与C = C |相同 2 |
其他运算符↦sizeof&trinary
除了上面讨论的运算符,还有一些其他重要的运算符,包括sizeof和? : ? : C语言支持。
操作者 | 描述 | 例 |
---|---|---|
sizeof() | 返回变量的大小。 | sizeof(a),其中a是整数,将返回4。 |
& | 返回变量的地址。 | &一个; 返回变量的实际地址。 |
* | Pointer to a variable. | *a; |
? : | 条件表达式。 | 如果条件为真? 然后值X:否则值Y. |
C中的运算符优先级
运算符优先级确定表达式中的术语分组,并决定如何计算表达式。 某些运算符的优先级高于其他运算符; 例如,乘法运算符的优先级高于加法运算符。
例如,x = 7 + 3 * 2; 这里,x被赋值为13,而不是20,因为operator *的优先级高于+,所以它首先乘以3 * 2然后加到7中。
此处,具有最高优先级的运算符显示在表的顶部,具有最低优先级的运算符显示在底部。 在表达式中,将首先评估更高优先级的运算符。
类别 | 操作者 | 关联性 |
---|---|---|
Postfix | ()[] - >。 ++ - - | 左到右 |
Unary | + - ! 〜++ - - (类型)*&sizeof | 右到左 |
Multiplicative | * /% | 左到右 |
Additive | + - | 左到右 |
Shift | << >> | 左到右 |
Relational | << => = | 左到右 |
Equality | ==!= | 左到右 |
Bitwise AND | & | 左到右 |
Bitwise XOR | ^ | 左到右 |
Bitwise OR | | | 左到右 |
Logical AND | && | 左到右 |
Logical OR | || | 左到右 |
Conditional | ?: | 右到左 |
Assignment | = + = - = * =/=%= >> = << =&= ^ = | = | 右到左 |
Comma | , | 左到右 |
C - Decision Making
决策结构要求程序员指定程序要评估或测试的一个或多个条件,以及在条件被确定为真时要执行的语句,以及可选的,如果条件要执行的其他语句被认定是假的。
下面显示的是大多数编程语言中的典型决策结构的一般形式 -
C编程语言将任何non-zero和non-null值假定为true ,如果它zero或null ,则将其假定为false值。
C编程语言提供以下类型的决策制定语句。
Sr.No. | 声明和说明 |
---|---|
1 | if 语句 if statement由布尔表达式后跟一个或多个语句组成。 |
2 | if...else 语句 if statement后面可以跟一个可选的else statement ,该else statement在布尔表达式为false时执行。 |
3 | 嵌套if语句 您可以在另一个if或else if语句中使用if或else if语句。 |
4 | switch 语句 switch语句允许测试变量与值列表的相等性。 |
5 | 嵌套的switch语句 您可以在另一个switch语句中使用一个switch语句。 |
(The ? : Operator)
我们覆盖了conditional operator ? : conditional operator ? :在前一章中可以用来替换if...else语句。 它有以下一般形式 -
Exp1 ? Exp2 : Exp3;
Exp1,Exp2和Exp3是表达式。 注意结肠的使用和放置。
一个值? 表达式是这样确定的 -
评估Exp1。 如果是,那么Exp2会被评估并成为整个值吗? 表达。
如果Exp1为false,则计算Exp3,其值将成为表达式的值。
C - Loops
当一段代码需要执行多次时,您可能会遇到这种情况。 通常,语句按顺序执行:首先执行函数中的第一个语句,然后执行第二个语句,依此类推。
编程语言提供各种控制结构,允许更复杂的执行路径。
循环语句允许我们多次执行语句或语句组。 下面给出的是大多数编程语言中循环语句的一般形式 -
C编程语言提供以下类型的循环来处理循环要求。
Sr.No. | 循环类型和描述 |
---|---|
1 | while 循环 在给定条件为真时重复语句或语句组。 它在执行循环体之前测试条件。 |
2 | for循环 多次执行一系列语句,并缩写管理循环变量的代码。 |
3 | 做... while循环 它更像是while语句,除了它测试循环体末尾的条件。 |
4 | 嵌套循环 您可以在while,for或do..while循环中使用任何其他循环内的一个或多个循环。 |
循环控制语句 (Loop Control Statements)
循环控制语句将执行从其正常序列更改。 当执行离开作用域时,将销毁在该作用域中创建的所有自动对象。
C支持以下控制语句。
Sr.No. | 控制声明和描述 |
---|---|
1 | break statement 终止loop或switch语句,并在循环或切换后立即将执行转移到语句。 |
2 | continue statement 导致循环跳过其身体的其余部分,并在重复之前立即重新测试其状态。 |
3 | goto statement 将控制转移到带标签的声明。 |
无限循环 (The Infinite Loop)
如果条件永远不会变为假,则循环变为无限循环。 for循环传统上用于此目的。 由于不需要构成'for'循环的三个表达式,因此可以通过将条件表达式留空来创建无限循环。
#include <stdio.h>
int main () {
for( ; ; ) {
printf("This loop will run forever.\n");
}
return 0;
}
当条件表达式不存在时,假定为真。 您可能有一个初始化和增量表达式,但C程序员更常使用for(;;)构造来表示无限循环。
NOTE - 您可以通过按Ctrl + C键终止无限循环。
C - 函数
函数是一组一起执行任务的语句。 每个C程序至少有一个函数,即main() ,所有最简单的程序都可以定义其他函数。
您可以将代码划分为单独的函数。 如何在不同的函数之间划分代码取决于你,但从逻辑上讲,划分是这样的,即每个函数执行特定的任务。
函数declaration告诉编译器函数的名称,返回类型和参数。 函数definition提供函数的实际主体。
C标准库提供了许多程序可以调用的内置函数。 例如, strcat()连接两个字符串, memcpy()将一个内存位置复制到另一个位置,还有更多的函数。
函数也可以称为方法或子例程或过程等。
定义一个函数 (Defining a Function)
C编程语言中函数定义的一般形式如下 -
return_type function_name( parameter list ) {
body of the function
}
C编程中的函数定义由function header和function body 。 以下是函数的所有部分 -
Return Type - 函数可以返回值。 return_type是函数返回的值的数据类型。 某些函数执行所需的操作而不返回值。 在这种情况下,return_type是关键字void 。
Function Name - 这是Function Name的实际名称。 函数名称和参数列表一起构成函数签名。
Parameters - 参数类似于占位符。 调用函数时,将值传递给参数。 该值称为实际参数或参数。 参数列表是指函数参数的类型,顺序和数量。 参数是可选的; 也就是说,函数可能不包含任何参数。
Function Body - 函数体包含一组语句,用于定义函数的功能。
例子 (Example)
下面给出了一个名为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;
}
函数声明 (Function Declarations)
函数declaration告诉编译器函数名称以及如何调用函数。 函数的实际主体可以单独定义。
功能声明包含以下部分 -
return_type function_name( parameter list );
对于上面定义的函数max(),函数声明如下 -
int max(int num1, int num2);
参数名称在函数声明中并不重要,只需要它们的类型,因此以下内容也是有效的声明 -
int max(int, int);
在一个源文件中定义函数并在另一个文件中调用该函数时,需要函数声明。 在这种情况下,您应该在调用该函数的文件顶部声明该函数。
调用一个函数 (Calling a Function)
在创建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;
}
我们将main()与main()一起保存并编译了源代码。 在运行最终可执行文件时,它会产生以下结果 -
Max value is : 200
函数参数(Function Arguments)
如果函数是使用参数,它必须声明接受参数值的变量。 这些变量称为函数的formal parameters 。
形式参数的行为与函数内部的其他局部变量相似,并在进入函数时创建,并在退出时销毁。
在调用函数时,有两种方法可以将参数传递给函数 -
Sr.No. | 通话类型和说明 |
---|---|
1 | Call by value 此方法将参数的实际值复制到函数的形式参数中。 在这种情况下,对函数内部参数所做的更改不会对参数产生影响。 |
2 | Call by reference 此方法将参数的地址复制到形式参数中。 在函数内部,该地址用于访问调用中使用的实际参数。 这意味着对参数所做的更改会影响参数。 |
默认情况下,C使用call by value来传递参数。 通常,它意味着函数内的代码不能改变用于调用函数的参数。
C - Scope Rules
任何编程中的范围都是程序的一个区域,其中定义的变量可以存在,并且超出该变量,无法访问它。 有三个地方可以用C编程语言声明变量 -
在函数或块内部,称为local变量。
在所有函数之外,称为global变量。
在函数参数的定义中称为formal参数。
让我们了解什么是local和global变量,以及formal参数。
局部变量 (Local Variables)
在函数或块内声明的变量称为局部变量。 它们只能由该函数或代码块中的语句使用。 本地变量不为其自身以外的函数所知。 以下示例显示了如何使用局部变量。 这里所有变量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;
}
全局变量 (Global Variables)
全局变量在函数外部定义,通常在程序之上。 全局变量在程序的整个生命周期中保持其值,并且可以在为程序定义的任何函数内访问它们。
任何函数都可以访问全局变量。 也就是说,全局变量在声明后可用于整个程序。 以下程序显示了如何在程序中使用全局变量。
#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 |
pointer | NULL |
正确初始化变量是一个很好的编程习惯,否则你的程序可能会产生意想不到的结果,因为未初始化的变量将在其内存位置获取一些已经可用的垃圾值。
C - Arrays
数组是一种数据结构,可以存储相同类型元素的固定大小顺序集合。 数组用于存储数据集合,但将数组视为相同类型的变量集合通常更有用。
您可以声明一个数组变量(例如数字)并使用数字[0],数字[1]和...,数字[99]来表示单个变量,例如number0,number1,...和number99,而不是声明单个变量。个别变数。 索引访问数组中的特定元素。
所有阵列都包含连续的内存位置。 最低地址对应于第一个元素,最高地址对应于最后一个元素。
声明数组 (Declaring Arrays)
要在C中声明一个数组,程序员指定元素的类型和数组所需的元素数量,如下所示 -
type arrayName [ arraySize ];
这称为一single-dimensional数组。 arraySize必须是大于零的整数常量, type可以是任何有效的C数据类型。 例如,要声明一个名为balance double类型的10元素数组,请使用此语句 -
double balance[10];
这里的balance是一个可变数组,足以容纳10个双数。
初始化数组 (Initializing Arrays)
您可以逐个初始化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.下面显示的是我们上面讨论的数组的图形表示 -
访问数组元素 (Accessing Array Elements)
通过索引数组名称来访问元素。 这是通过将元素的索引放在数组名称后面的方括号中来完成的。 例如 -
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程序员应该清楚以下与数组相关的重要概念 -
Sr.No. | 概念与描述 |
---|---|
1 | 多维数组 C支持多维数组。 多维数组的最简单形式是二维数组。 |
2 | 将数组传递给函数 您可以通过指定不带索引的数组名称来向函数传递指向数组的指针。 |
3 | 从函数返回数组 C允许函数返回数组。 |
4 | 指向数组的指针 您只需指定数组名称即可生成指向数组第一个元素的指针,而无需任何索引。 |
C - Pointers
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
什么是指针 (What are Pointers?)
pointer是一个变量,其值是另一个变量的地址,即存储器位置的直接地址。 与任何变量或常量一样,您必须在使用它来存储任何变量地址之前声明指针。 指针变量声明的一般形式是 -
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的指针称为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具有特殊意义; 它表示指针不是指向可访问的内存位置。 但按照惯例,如果指针包含null(零)值,则假定它指向任何内容。
要检查空指针,可以使用“if”语句,如下所示 -
if(ptr) /* succeeds if p is not null */
if(!ptr) /* succeeds if p is null */
细节指针
指针有很多但很简单的概念,它们对C编程非常重要。 任何C程序员都应该清楚以下重要的指针概念 -
Sr.No. | 概念与描述 |
---|---|
1 | 指针算术 可以在指针中使用四个算术运算符:++, - ,+, - |
2 | 指针数组 您可以定义数组以包含许多指针。 |
3 | 指向指针 C允许您在指针上指针等等。 |
4 | 将指针传递给C中的函数 通过引用或地址传递参数使得被调用函数可以在调用函数中更改传递的参数。 |
5 | 从C中的函数返回指针 C允许函数返回指向局部变量,静态变量和动态分配的内存的指针。 |
C - Strings
字符串实际上是由null字符'\ 0'终止的一维字符数组。 因此,以null结尾的字符串包含组成字符串后跟null 。
以下声明和初始化创建一个由单词“Hello”组成的字符串。 要将空字符保存在数组的末尾,包含字符串的字符数组的大小比单词“Hello”中的字符数多一个。
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
如果你遵循数组初始化的规则,那么你可以写如下语句 -
char greeting[] = "Hello";
以下是C/C ++中上面定义的字符串的内存表示 -
实际上,您不会将null字符放在字符串常量的末尾。 初始化数组时,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支持各种操作以null结尾的字符串的函数 -
Sr.No. | 功能与目的 |
---|---|
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 - Structures
数组允许定义可以容纳多个相同类型的数据项的变量类型。 类似地, structure是C中可用的另一个用户定义的数据类型,其允许组合不同类型的数据项。
结构用于表示记录。 假设您想要在图书馆中跟踪您的图书。 您可能希望跟踪每本书的以下属性 -
- Title
- Author
- Subject
- 书名
定义一个结构 (Defining a Structure)
要定义结构,必须使用struct语句。 struct语句定义了一个具有多个成员的新数据类型。 struct语句的格式如下 -
struct [structure tag] {
member definition;
member definition;
...
member definition;
} [one or more structure variables];
structure tag是可选的,每个成员定义是一个普通的变量定义,例如int i; 或浮动f; 或任何其他有效的变量定义。 在结构定义的最后,在最后一个分号之前,您可以指定一个或多个结构变量,但它是可选的。 以下是您声明Book结构的方式 -
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
访问结构成员 (Accessing Structure Members)
要访问结构的任何成员,我们使用member access operator (.) 。 成员访问运算符被编码为结构变量名称和我们希望访问的结构成员之间的句点。 您可以使用关键字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
作为函数参数的结构 (Structures as Function Arguments)
您可以将结构作为函数参数传递,方法与传递任何其他变量或指针的方式相同。
#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允许我们通过在变量之后放置:位长度来在结构定义中执行此操作。 例如 -
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 - Unions
union是C中可用的特殊数据类型,允许在同一内存位置存储不同的数据类型。 您可以定义具有许多成员的联合,但在任何给定时间只能有一个成员包含值。 联合提供了一种有效的方法,可以将相同的内存位置用于多用途。
定义 Union
要定义联合,必须以与定义结构时相同的方式使用union语句。 union语句定义了一个具有多个程序成员的新数据类型。 union语句的格式如下 -
union [union tag] {
member definition;
member definition;
...
member definition;
} [one or more union variables];
union tag是可选的,每个成员定义都是一个普通的变量定义,例如int i; 或浮动f; 或任何其他有效的变量定义。 在union的定义结束时,在最后一个分号之前,您可以指定一个或多个union变量,但它是可选的。 以下是定义名为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的任何成员,我们使用member access operator (.) 。 成员访问运算符被编码为联合变量名称和我们希望访问的联合成员之间的句点。 您可以使用关键字union来定义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
在这里,我们可以看到union的i和f成员的值被破坏了,因为分配给变量的最终值占用了内存位置,这就是str成员的值打印得很好的原因。
现在让我们再次研究相同的例子,我们将一次使用一个变量,这是拥有unions的主要目的 -
#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 - Bit Fields
假设您的C程序包含许多TRUE/FALSE变量,这些变量分组在一个名为status的结构中,如下所示 -
struct {
unsigned int widthValidated;
unsigned int heightValidated;
} status;
这种结构需要8个字节的存储空间,但实际上,我们将在每个变量中存储0或1。 C编程语言提供了在这种情况下利用内存空间的更好方法。
如果在结构中使用此类变量,则可以定义变量的宽度,该变量告诉C编译器您将仅使用这些字节数。 例如,上述结构可以重写如下 -
struct {
unsigned int widthValidated : 1;
unsigned int heightValidated : 1;
} status;
上述结构需要4个字节的存储空间用于状态变量,但只有2位用于存储值。
如果最多使用32个变量,每个变量宽度为1位,那么状态结构也将使用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 ;
};
下表描述了位字段的可变元素 -
Sr.No. | 元素和描述 |
---|---|
1 | type 一种整数类型,用于确定如何解释位字段的值。 类型可以是int,signed int或unsigned int。 |
2 | member_name 位字段的名称。 |
3 | width 位域中的位数。 宽度必须小于或等于指定类型的位宽。 |
使用预定义宽度定义的变量称为bit fields 。 位字段可以容纳多个位; 例如,如果您需要一个变量来存储0到7之间的值,那么您可以定义宽度为3位的位字段,如下所示 -
struct {
unsigned int age : 3;
} Age;
上面的结构定义指示C编译器年龄变量将仅使用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, for example.类型的缩写unsigned char, for example. 。
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 vs #define
#define是一个C指令,它也用于定义类似于typedef各种数据类型的别名,但有以下区别 -
typedef仅限于为类型提供符号名称,因为#define也可用于定义值的别名,q。,您可以将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 - Input and Output
当我们说Input ,它意味着将一些数据提供给程序。 输入可以以文件的形式或从命令行给出。 C编程提供了一组内置函数来读取给定的输入并根据需要将其提供给程序。
当我们说Output ,它意味着在屏幕,打印机或任何文件中显示一些数据。 C编程提供了一组内置函数,用于在计算机屏幕上输出数据以及将其保存在文本或二进制文件中。
标准文件
C编程将所有设备视为文件。 因此,诸如显示器之类的设备以与文件相同的方式被寻址,并且当程序执行以提供对键盘和屏幕的访问时,自动打开以下三个文件。
标准文件 | 文件指针 | 设备 |
---|---|---|
标准输入 | stdin | Keyboard |
标准输出 | stdout | Screen |
Standard error | stderr | Your screen |
文件指针是访问文件以进行读写目的的方法。 本节介绍如何从屏幕读取值以及如何在屏幕上打印结果。
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;
}
编译并执行上述代码时,它会等待您输入一些文本。 当您输入文本并按Enter键时,程序继续执行并只读取一个字符并显示如下 -
$./a.out
<b class="notranslate">Enter a value :</b> this is test
<b class="notranslate">You entered:</b> t
gets()和puts()函数
char *gets(char *s)函数从stdin读取一行到s指向的缓冲区,直到终止换行符或EOF(文件结束)。
int puts(const char *s)函数将字符串's'和'a'尾随换行符写入stdout 。
#include <stdio.h>
int main( ) {
char str[100];
printf( "Enter a value :");
gets( str );
printf( "\nYou entered: ");
puts( str );
return 0;
}
编译并执行上述代码时,它会等待您输入一些文本。 当您输入文本并按Enter键时,程序继续执行并读取完整的行直到结束,并显示如下 -
$./a.out
<b class="notranslate">Enter a value :</b> this is test
<b class="notranslate">You entered:</b> 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;
}
编译并执行上述代码时,它会等待您输入一些文本。 当您输入文本并按Enter键时,程序继续执行并读取输入并显示如下 -
$./a.out
<b class="notranslate">Enter a value :</b> seven 7
<b class="notranslate">You entered:</b> seven 7
在这里,应该注意scanf()期望输入的格式与您提供的%s和%d相同,这意味着您必须提供有效的输入,如“字符串整数”。 如果提供“字符串字符串”或“整数整数”,则将其视为错误输入。 其次,在读取字符串时,scanf()会在遇到空格时立即停止读取,因此“this is test”是scanf()的三个字符串。
C - File I/O
最后一章解释了C编程语言处理的标准输入和输出设备。 本章介绍C程序员如何为其数据存储创建,打开,关闭文本或二进制文件。
文件表示字节序列,无论它是文本文件还是二进制文件。 C编程语言提供对高级功能的访问以及低级(OS级)调用,以处理存储设备上的文件。 本章将指导您完成文件管理的重要调用。
打开文件
您可以使用fopen( )函数创建新文件或打开现有文件。 此调用将初始化FILE类型的对象,该对象包含控制流所需的所有信息。 这个函数调用的原型如下 -
FILE *fopen( const char * filename, const char * mode );
这里, filename是一个字符串文字,您将用它来命名您的文件,访问mode可以具有以下值之一 -
Sr.No. | 模式和说明 |
---|---|
1 | r 打开现有文本文件以供阅读。 |
2 | w 打开文本文件进行写入。 如果它不存在,则创建一个新文件。 在这里,您的程序将开始从文件的开头编写内容。 |
3 | a 打开文本文件以便以附加模式写入。 如果它不存在,则创建一个新文件。 在这里,您的程序将开始在现有文件内容中附加内容。 |
4 | r+ 打开文本文件以进行读写。 |
5 | w+ 打开文本文件以进行读写。 如果文件存在,它首先将文件截断为零长度,否则创建一个文件(如果它不存在)。 |
6 | a+ 打开文本文件以进行读写。 如果文件不存在,它会创建该文件。 读数将从头开始,但只能附加写入。 |
如果您要处理二进制文件,那么您将使用以下访问模式而不是上面提到的模式 -
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
关闭一个文件 (Closing a File)
要关闭文件,请使用fclose()函数。 这个功能的原型是 -
int fclose( FILE *fp );
fclose(-)函数在成功时返回零,如果在关闭文件时出错,则返回EOF 。 此函数实际上将缓冲区中仍未处理的任何数据刷新到文件,关闭文件,并释放用于该文件的所有内存。 EOF是头文件stdio.h定义的常量。
C标准库提供了各种功能,用于逐个字符地读取和写入文件,或者以固定长度字符串的形式。
写一个文件 (Writing a File)
以下是将单个字符写入流的最简单函数 -
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 ,并使用两个不同的函数写入两行。 我们将在下一节中阅读此文件。
Reading a File
以下是从文件中读取单个字符的最简单的功能 -
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()完全读取第二行。
Binary 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 - Preprocessors
C Preprocessor不是编译器的一部分,而是编译过程中的一个单独步骤。 简单来说,C预处理器只是一个文本替换工具,它指示编译器在实际编译之前进行必要的预处理。 我们将C预处理器称为CPP。
所有预处理器命令都以井号(#)开头。 它必须是第一个非空白字符,并且为了便于阅读,预处理程序指令应该从第一列开始。 以下部分列出了所有重要的预处理程序指令 -
Sr.No. | 指令和说明 |
---|---|
1 | #define 替换预处理器宏。 |
2 | #include 从另一个文件插入特定标头。 |
3 | #undef 取消定义预处理器宏。 |
4 | #ifdef 如果定义了此宏,则返回true。 |
5 | #ifndef 如果未定义此宏,则返回true。 |
6 | #if 测试编译时条件是否为真。 |
7 | #else #if的替代方案。 |
8 | #elif #else和#if在一个语句中。 |
9 | #endif 结束预处理器条件。 |
10 | #error 在stderr上打印错误消息。 |
11 | #pragma 使用标准化方法向编译器发出特殊命令。 |
预处理器示例
分析以下示例以了解各种指令。
#define MAX_ARRAY_LENGTH 20
该指令告诉CPP用20替换MAX_ARRAY_LENGTH的实例。使用#define作为常量来提高可读性。
#include <stdio.h>
#include "myheader.h"
这些指令告诉CPP从System Libraries获取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
如果定义了DEBUG,它告诉CPP处理所附的语句。 如果在编译时将-DDEBUG标志传递给gcc编译器,这将非常有用。 这将定义DEBUG,因此您可以在编译期间动态打开和关闭调试。
预定义的宏
ANSI C定义了许多宏。 尽管每个都可用于编程,但不应直接修改预定义的宏。
Sr.No. | 宏观和描述 |
---|---|
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")
The Stringize (#) Operator
stringize或数字符号运算符('#')在宏定义中使用时,将宏参数转换为字符串常量。 此运算符只能在具有指定参数或参数列表的宏中使用。 例如 -
#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);
此示例显示令牌## n到token34的串联,这里我们使用了stringize和token-pasting pasting。
The Defined() Operator
预处理器defined运算符用于常量表达式,以确定是否使用#define定义标识符。 如果定义了指定的标识符,则该值为true(非零)。 如果未定义符号,则值为false(零)。 定义的运算符指定如下 -
#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 - Header Files
头文件是扩展名为.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 ());
}
Once-Only Headers
如果头文件恰好包含两次,编译器将处理其内容两次,这将导致错误。 防止这种情况的标准方法是将文件的整个实际内容包含在条件中,如下所示 -
#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif
此构造通常称为包装器#ifndef 。 当再次包含标题时,条件将为false,因为定义了HEADER_FILE。 预处理器将跳过文件的整个内容,编译器将不会看到它两次。
计算包含
有时需要选择要包含在程序中的几个不同头文件中的一个。 例如,他们可能会指定要在不同类型的操作系统上使用的配置参数。 您可以通过以下一系列条件执行此操作 -
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
但随着它的增长,它变得乏味,而预处理器提供了使用宏作为标题名称的能力。 这称为computed include 。 您只需将宏名称放在那里,而不是将标题名称作为#include的直接参数写入 -
#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H
SYSTEM_H将被扩展,预处理器将查找system_1.h,就像#include最初是以这样的方式编写的一样。 SYSTEM_H可以由Makefile使用-D选项定义。
C - Type Casting
类型转换是一种将变量从一种数据类型转换为另一种数据类型的方法。 例如,如果要将“long”值存储到一个简单的整数中,则可以将“long”类型键入“int”。 您可以使用强制转换cast operator将值从一种类型转换为另一种类型,如下所示 -
(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值。
类型转换可以是隐式的,由编译器自动执行,也可以通过使用转换cast operator显式指定。 每当需要类型转换时,使用强制转换运算符被认为是一种很好的编程习惯。
整数推广
整数提升是将整数类型“小于” 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。
通常的算术转换
隐式执行usual arithmetic conversions以将其值转换为公共类型。 编译器首先执行integer promotion ; 如果操作数仍然具有不同的类型,则它们将转换为在以下层次结构中显示最高的类型 -
通常的算术转换不是为赋值运算符执行,也不是为逻辑运算符&&和||执行。 让我们以下面的例子来理解这个概念 -
#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 - Error Handling
因此,C编程不提供对错误处理的直接支持,而是作为系统编程语言,它以返回值的形式为您提供较低级别的访问。 在发生任何错误的情况下,大多数C或甚至Unix函数调用都返回-1或NULL,并设置错误代码errno 。 它被设置为全局变量,表示在任何函数调用期间发生错误。 您可以在“error.h”头文件中找到各种错误代码。
因此,C程序员可以检查返回的值,并根据返回值采取适当的操作。 在初始化程序时将errno设置为0是一种很好的做法。 值为0表示程序中没有错误。
errno, perror(). and 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 - Recursion
递归是以自相似的方式重复项目的过程。 在编程语言中,如果程序允许您在同一函数内调用函数,则称其为函数的递归调用。
void recursion() {
recursion(); /* function calls itself */
}
int main() {
recursion();
}
C编程语言支持递归,即调用自身的函数。 但是在使用递归时,程序员需要小心地从函数中定义退出条件,否则它将进入无限循环。
递归函数对于解决许多数学问题非常有用,例如计算数的阶乘,生成Fibonacci序列等。
数量因子
以下示例使用递归函数计算给定数字的阶乘 -
#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
斐波那契系列
以下示例使用递归函数为给定数字生成Fibonacci系列 -
#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 - Variable Arguments
有时,当您想要一个函数时,您可能会遇到这种情况,该函数可以使用可变数量的参数,即参数,而不是预定义数量的参数。 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 - Memory Management
本章介绍C语言中的动态内存管理.C编程语言提供了几种内存分配和管理功能。 这些函数可以在《stdlib.h》头文件中找到。
Sr.No. | 功能说明 |
---|---|
1 | void *calloc(int num, int size); 此函数分配一个num元素数组,每个元素的大小以字节为单位。 |
2 | void free(void *address); 此函数释放由地址指定的内存块块。 |
3 | void *malloc(int num); 此函数分配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();编写相同的程序calloc(); 唯一的问题是你需要用calloc替换malloc,如下所示 -
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
您可以在不重新分配额外内存的情况下尝试上述示例,并且由于描述中缺少可用内存,strcat()函数将给出错误。
C - Command Line Arguments
执行时,可以将一些值从命令行传递给C程序。 这些值称为command line arguments ,很多时候它们对您的程序很重要,尤其是当您想从外部控制程序而不是在代码中对这些值进行硬编码时。
命令行参数使用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