C语言语法大纲(1遍)


C语言语法
@author 李金鑫
@reference 菜鸟教程

C语言语法大纲

c程序结构

c程序主要包括以下部分

  • 函数
  • 变量
  • 语句&表达式
  • 注释

让我们看一段简单的代码,可以输出单词“Hello World”:

#include//预处理器的指令,告诉C编译器在实际编译之前要包含stdio.h文件
int main()//主函数,代码程序从这里开始执行
{
  /*我的第一个C程序*/
  prinf("Hello,World!\n");
  
  return 0;//终止main()函数,并且返回值0
}

所有的c语言程序都需要包含main()函数。代码从main()函数开始执行

//用于注释

printf()用于格式化输出到屏幕。printf()函数在"stdio.h"头文件中声明。

stdio.h是一个头文件(标准输入输出到头文件)

头文件

  • 头文件的主要作用在于多个代码文件全局变量(函数)的重用、防止定义的冲突,对各个被调用函数给出一个描述,其本身不需要包含程序的逻辑实现代码,它只起描述性作用,用户程序只需要按照头文件中的接口声明来调用相关函数或变量,链接器会从库中寻找相应的实际定义代码。
  • 头文件是用户应用程序和函数库之间的桥梁和纽带。在整个软件中,头文件不是最重要的部分,但它是C语言家族中不可缺少的组成部分。编译时,编译器通过头文件找到对应的函数库,进而把已引用函数的实际内容导出来代替原有函数。进而在硬件层面实现功能。
  • 头文件一般以“.h”为后缀

#include是一个预处理命令,用来引入头文件。当编译器遇见printf()函数时,如果没有找到stdio.h头文件,会发生编译错误。

return 0;用于表示退出程序

C 简介

C语言时一门通用的高级语言。

由丹尼斯/里奇在贝尔实验室为开发unix操作系统而设计的。

最开始在1972年DEC PDP-11计算机实现

容易学习

结构化语言

产生高效率程序

处理底层的活动

在多种计算机平台上编译

C 基本语法

c的令牌

关键字,标识符,常量,字符串值

分号

语句结束符号

注释

//单行注释 /*多行注释

标识符

C标识符是用来标示变量,函数或者其他自定义项目的

标识符以字母A-Z或a-z或下划线_开始,后跟零个或者多个字母,下划线和数字

关键字

关键字不能作为常量名,变量名,或者其他标识符的名称

auto//声明自动变量---------
break//跳出当前循环
case//开关语句分支
char//声明字符型变量或者函数返回值类型
const//定义常量,如果一个变量被const修饰,那么他的值就不能被改变
continue//结束当前循环,开始下一轮循环
default//开关语句中的“其他”分支,弃权,违约--------
do//循环语句的循环体
double//声明双精度浮点型变量或函数返回值类型
else//条件语句否定分支与if连用
enum//声明枚举类型
extern//声明变量或者函数是在其他文件或者本文件的其他位置定义-----
float//声明浮点型变量或者函数返回值类型
for//一种循环语句
goto//无条件跳转语句
if//条件语句
int//声明整数变量或函数
long//声明长整型变量或者函数返回值类型
register//声明寄存器变量---------
return//子程序返回语句(可以带参数,也可不带参数)
short//声明短整数型变量或者函数
signed//声明有符号类型变量或函数
sizeof//计算你数据类型或者变量长度(即所占字节数)
static//声明静态变量---------
struct//声明结构体变量---------
switch//用于开关语句
typedef//用于给数据类型取别名
unsigned//声明无符号类型变量或函数
union//声明共用体类型----------
void//声明函数无返回值或无参数,声明无类型指针-------
volatile//说明函数无返回值或无参数,声明无类型指针-------
while//循环语句的循环条件

C中的空格

只包含空格的行,被称为空白行,可能带有注释,C编译器会完全忽视。

空格用于描述空白符,制表符,换行符和注释

C 数据类型

C中的类型可以分为以下几种

  • 基本类型
  • 枚举类型
  • void类型
  • 派生类型

基础知识拓展//一个字节(Byte)为1B=8bit(比特),一个位相当于一个0或者1个1。一个字节为00-FF

整数类型

类型 储存大小 值范围
char 1 Byte 28(±) or 0-2^8-1
unsigned char 1 Byte 0-28-1
signed char 1 Byte -128-127
int 2 or 4 Byte 216(±) or 232(±)
unsigned int 2 or 4 Byte 0-(216-1) or (0-232-1)
short 2 Byte 216(±)
Unsigned short 2 Byte 0-(216-1)
long 4 Byte 0-232(±)
Unsigned long 4 Byte 0-(232-1)

存储大小跟系统位数有关,但目前通用的以64位系统为主。

为了得到某个类型或某个变量在特定准确大小,可以使用sizeof运算符。

#include
#include
int main()//int声明变量或者函数
{
  printf("int 存储大小: %lu",sizeof(int));//%lu为32位无符号数
  return 0;
}

浮点类型

类型 存储的大小 值范围 精度
float 4 Byte 1.2E-38—3.4E+38 6位小数
double 8 Byte 2.3E-308—1.7E+308 15位小数
long double 16 Byte 3.4E-4932—1.1E+4932 19位小数
#include
#include

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

%E以指数形式输出单、双精度实数

void类型

void类型指定没有可用的值,它通常用于以下三种情况:

  • 函数返回为空 。

    C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status)

  • 函数参数为空 。

    C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void);

  • 指针指向 void 类型为 void 的指针代表对象的地址,而不是类型。

    例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。

C变量

变量其实只不过是程序可操作的存储区的名称。C中的每个变量都有特定的类型,类型决定了变量存储的大小跟布局,改范围内的值都可以存储在内存中,运算符可应用于变量上。

C是一门大小写敏感的语言

以下几种为基本的变量的类型

类型 描述
char 通常是一个字节,这是一个整数类型
int 对机器而言,整数的是最自然的大小
float 单精度浮点值。单精度是1位符号8位指数,23位小数
double 双精度浮点值,双精度是1位符号,11位指数,52位小数
void 表示类型的缺失

C中变量的定义

type variable_list

在这里,type必须是一个有效C数据类型,可以是char,w_char,int,float,double或者任何用户自定义的对象,variable_list可以由一个或多个标识符名称组成, 。下面列出几个有效的声明:

int i,j,k;
char c,ch;
float f,salary;
double d;

变量可以在声明的时候被初始化

type varible_name = value;

下面举几个实例

extern int d = 3, f = 5int d = 3, f = 5;
byte z = 22;
char x = 'x'

C中变量的声明

变量声明向编译器保证变量以指定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步编译。变量声明值在编译时在有它的意义,在程序连接时编译器需要实际的变量声明。

  • 一种是需要建立存储空间。例如:int a 在声明的时候就已经建立了存储空间。
  • 另一种不需要建立存储空间,通过使用extern关键字声明变量名而不是定义它。例如:extern int a 其中变量a可以在别的文件中定义的。
  • 除非有extern关键字,否则都是变量的定义
extern int i;//声明,不是定义
int i;//声明,也是定义

实例

#include
int x;
int y;//在函数外定义变量x与y
int addtwonum()
{
  extern int x;
  extern int y;//函数内声明变量x和y为外部变量
  x = 1;
  y = 2;//给外部变量(全局变量)x和y赋值
  return x+y;
}

int main()
{
  int result;//调用函数 addtwonum
  result = addtwonum();
  
  printf("result is: %d", result);
  return 0;
}

如果需要在一个源文件中引用另外一个源文件中定义的变量,我们只需要在引用的文件中将变量加上extern关键字的声明即可。

//addtwonum.c
#include
extern int x;
extern int y;
int addtwonum()
{
  return x+y;
}
//test.c
#include
int x=1;
int y=2;
int addtwonum();
int main(void)
{
  int result;
  result = addtwonum();
  printf("result is: %d\n",result);
  return 0;
}

代码结果

$gcc addtwonum.c test.c -0 main
$./main
result is:3

C中的左值(Lvalues)和右值(Rvalues)------

左值C 中有两种类型的表达式:

  • 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
  • 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。

变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边,下面是一个有效的语句:

int g = 20//可以赋值
10 = 20//数值型不能是左值

C 常量

常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量

常量可以是任何的基本数据类型,比如整数常量,浮点常量,字符常量或字符串字面值,也有枚举常量。

常量就是像是常规的变量,只不过常量的值在定义后不能进行修改。

整数常量

整数常量可以是十进制,八进制或16进制的常量。前缀指定基数:0X表示十六进制,0表示八进制,不带前缀则默认表示十进制。

整数常量也可以带一个后缀,后缀是U与L的组合,U表示无符号数(unsigned),L表示长整数(long)。后缀可以是大写,也可以是小写,U和L的顺序任意。

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

212//合法
215U//合法的,U表示无符号数
0xFeeL//合法的
078//非法的,8不是八进制的数字
032UU//非法,无符号数不能重复后缀
85//十进制
0213//八进制
0x4b//十六进制
30//整数
30U//无符号整数
30l//长整数
30ul//无符号长整数,大小写无所谓

浮点常量

浮点常量由整数部分,小数点,小数部分和直属部分组成。

您可以使用小数形式或者指数形式来表示浮点常量。

当使用小数形式表示时,必须包含整数部分,小数部分,或者同时包含两者。

当使用指数形式表示时。必须包含小数点。指数或同时包含两者。带符号的指数是用e或E引入的。

//实例
3.14159//合法
314159E-5L//合法
510E//非法:不完整的指数
210f//非法:没有小数或指数
.e55//非法:缺少整数或者分数

字符常量

字符常量是括在单引号中,例如’x’可以存储在char类型的简单变量中。

字符常量可以是一个普通的字符(例如’x’),一个转义序列(例如’\t’);或一个通用的字符(例如’\u02C0’)。

在C中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符\n or 制表符\t。

\\
\'
\"
\?
\a//警报铃声
\b//退格键
\f//换页符
\n//换行符
\r//回车
\t//水平制表符,相当于横向打空格
\t//垂直制表符,相当于纵向打空格
\ooo//一到三位的八进制数
\xhh//一个多个数字的十六进制数

实例

#include
int main()
{
printf("Hello\tWorld\n\n");
return 0;
getchar();
}

字符串常量

字符串字面值或常量是括在双引号""中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。

可以使用空格键作为分隔符,把一个很长的字符串常量进行分析。

下面的实例显示了一些字符串常量。下面三种形式所显示的字符串是相同的。

"hello,dear"
"hell,\
dear"
"hello,""d""dear"

定义常量

在C中,有两种简单的定义常量的方式

  1. 使用#define预处理器
  2. 使用const关键字

#define预处理器

下面是使用#define预处理器的定义常量的形式:

#define identifier value

实例

#include
#define LENGTH 10
#define WIDTH 5
#define NEWLINE '\n'
int main()
{
  int area;
  
  area = LENGTH*WIDTH;
  printf("valule of area : %d",area);//%d用来输出十进制整型数据的实际长度输出
  printf("%c",NEWLINE);//%c以字符形式输出,只输出一个字符;
  return 0;//%f是以小数形式输出单、双精度读书,隐含输出六位小数。
}

const 关键字

您可以使用const前缀声明指定类型的常量,如下所示:

const type varible = value;

实例

#include

int main()int//声明整数变量或函数
{
  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;
}

C 存储类

存储类定义C程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。

下面列出C程序中可用的存储类:

  • auto
  • register
  • static
  • extern

auto存储类

auto存储类是所有局部变量默认的存储类。

{
  int mount;
  auto int month;//auto只能用在函数内,即auto只能修饰局部变量。
}

register存储类

register存储类用于定义存储在寄存器中而不是RAM中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的‘&’运算符(因为它没有内存位置)。

{
  register int miles;
}

寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义’register’并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。

static 存储类

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。

因此,使用static修饰局部变量可以在函数之间保持局部变量的值。

static修饰符也可以应用于全局变量。当static修饰全局变量时,会使变量的作用域限制在声明它的文件内

在声明全局变量时,static是默认的

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

//实例
#include
//函数声明
void func1(void);
static int count = 10;//全局变量static是默认的
int main()
{
  while (count--){
    func1();
  }
  return 0;
}

void func1(void)
{
  /*'thingy'是'func1'的局部变量-只初始化一次
  *每次调用函数'func1''thingy'值不会被重制。*/
  static int thingy = 5;
  thingy++;//C语言中,++与--主要是对变量值进行+1的操作
  printf("thingy is %d, count is %d\n",thingy,count);
}

static是应用在函数中,当一个函数被调用时,因为存在一些已经定义的变量的起始量,但是需要做一些如递增或者递减的操作,而定义的起始量就会逐渐改变,static的存在就是让函数中定义的量不会在每次调用的时候重置。值得注意的是,在全局中定义变量时,默认时static存储类的。

extern 存储类

external存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用extern时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。

当有多个文件且定义了一个可以在其他文件中使用的全局变量或者函数时,可以在其他文件中使用extern来得到已经定义的变量或函数的引用。

extern是用来在另一个文件中声明一个全局变量或者函数

extern 修饰符通常用于有两个或多个文件共享相同的全局变量或函数的时候

实例

//main()
#include
int count;
extern void write_extern();
 int main()
 {
   count = 5;
   write_extern();
 }
//support.c
#include
extern int count;
void write_extern(void)
{
  printf("count is %d\n", count);
}

C运算符

运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C语言内置了丰富的运算符,并提供了以下类型的运算符:

  • 算数运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 杂项运算符

算术运算符

#define A 10;

#define B 20;

运算符 描述 实例
+ 相加 A+B=30
- 相减 A-B=-10
*
/
% 取模运算符,整除后的余数 B%A=0
++ 自增运算符,整数值增加1 A++=11
自减运算符,整数值减少1 A–=9

实例

#include
int main()
{
  int a = 21;
  int b = 10;
  int c;
  
  c = a + b;
  printf("a + b = %d\n",c);
  c = a - b;
  printf("a - b = %d\n",c);
  c = a * b;
  printf("a * b = %d\n",c);
  c = a / b;
  printf("a / b = %d\n",c);
  c = a % b;
  printf("a % b = %d\n",c);
  c = a++;
  printf("a++ = %d\n",c);
  c = a--;
  printf("a-- = %d\n",c);
}

a++与++a的区别

#include
int main()
{
  int c;
  int a = 10;
  c = a++;
  printf("c = %d\n",c);
  printf("a = %d\n",a);
  c = a--;
  printf("c = %d\n",c);
  printf("a = %d\n",a);
  return 0;
}

从实例中我们可以总结,当从10开始计数时,执行第一个c = a–时,第一个c = 10,意为第一个考虑项也会陷入循环。

平时在编写程序时肯定要注意++a与a++的区别,这是一个容易混淆的错误

实例1:单独使用a++时会让a+1,使用++a也是同样的效果

#include
int main()
{
  int c;
  int a = 0;
  a++;//此时a已经变为1
  printf("%d\n",a);
}//程序输出结果为1

实例2:使用a++进行赋值时,如果是c=a++,从c=a开始计c;如果是c=++a,从c=a+1开始计c。

#include
int main()
{
  int c;
  int a = 0;
  c=a++;//此时a已经变为1
  printf("%d\n",c);
}//输出c为0,若是++a,则输出c=

关系运算符

int A = 10; int B = 20;

运算符 描述 实例
== 检查两个操作数的值是否相等,如果相等则条件为真。 (A==B)为假
!= 检查两个操作数的值是否相等,如果不相等则条件为真 (A!=B)为真
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 A>B为假
< 检查左操作数的值是否小于右操作值,如果是则条件为真。 A
>= 检查大于等于 A>=B
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真 A<=B

实例

#include
char a;
char b;//char范围一个字节-128-127
printf("请输入字符a:");
a = getchar();
printf("请输入字符b:");
b = getchar();

int main()
{
  extern int a;
  extern int b;
  
  if (a==b)
  {
    printf("a 等于 b\n");
  }
  else
  {
    printf("a 不等于 b\n");
  }
  if (a<b)
  {
    printf("a 小于 b\n");
  }
  else
  {
    printf("a 大于 b\n");
  }
  return 0;
}

逻辑运算符

下表显示了C语言支持的所有关系逻辑运算符

假设变量A为1,变量B的值为0,则:

运算符 描述 实例
&& 称为逻辑 与运算符,都非零,则真 (A&&B)为假
|| 称为逻辑 或运算符。任意一个非零,则真 (A||B)为真
称为逻辑 非运算符。反真为假 !(A&&B)为真

实例

#include

int main()
{
  int a = 0;
  int b = 1;
 
  if (a&&b)
  {
    printf("right\n");
  }
  else
  {
    printf("false\n");
  }
  if (a||b)
  {
    printf("right\n");
  }
  else
  {
    printf("false\n");
  }
}

位运算符

位运算符用于位,并逐位执行操作。&、|和^的真值表如下所示:

p q p&q(有0位0) p|q(有1为1) p^q(相同为0)
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

赋值运算符

下表为C语言支持的赋值运算符

运算符 描述 实例
= 赋值,右值给左值 C = A + B
+= 加赋值,右值加左值赋值左值 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
#include
main()
{
   int a = 21;
   int c ;
 
   c =  a;
   printf("c赋值 %d\n", c );
 
   c +=  a;
   printf("c加赋值 %d\n", c );
 
   c -=  a;
   printf("c减赋值 %d\n", c );
 
   c *=  a;
   printf("c乘赋值 %d\n", c );
 
   c /=  a;
   printf("c除赋值 %d\n", c );
 
   c  = 200;
   c %=  a;
   printf("取余赋值 %d\n", c );
 
   c <<=  2;
   printf("左移赋值 %d\n", c );
 
   c >>=  2;
   printf("右移赋值 %d\n", c );
 
   c &=  2;
   printf("与且赋值 %d\n", c );//2
 
   c ^=  2;
   printf("异或且赋值,%d\n", c );//0
 
   c |=  2;
   printf("或且赋值 %d\n", c );//2
}

杂项运算符

关于输出格式

  • **d **以十bai进制形式输出带符号整数(正数不输出符号)
  • **o **以八du进制形式zhi输dao出无符号整数(不输出前缀0)
  • **x,X **以十六进制形式输出无符号整数(不输出前缀Ox)
  • u 以十进制形式输出无符号整数
  • **f **以小数形式输出单、双精度实数
  • **e,E **以指数形式输出单、双精度实数
  • g,G 以%f或%e中较短的输出宽度输出单、双精度实数
  • c 输出单个字符
  • **s **输出字符串
  • **p **输出指针地址//pointer
  • **lu **32位无符号整数//long unsigned
  • **llu **64位无符号整数
运算符 描述 实例
sizeof() 返回变量的大小 sizeof(a),其中a是整数
& 返回变量的地址 &a:将给出变量的实际地址
* 指向一个变量 *a;将指向一个变量。
?: 条件表达式 如果条件为真?则值为X;否则值为Y
#include
int main()
{
  int a = 4;
  short b;
  double c;
  int* ptr;

  //sizeof 运算符实例
  printf("a = %lu\n", sizeof(a) );
  printf("b = %lu\n", sizeof(b) );
  printf("c = %lu\n", sizeof(c) );
  //*&和*运算符实例
  ptr = &a;//ptr现在包含'a'的地址
  printf("a = %d\n",a);
  printf("*ptr = %d\n",*ptr);
  //三元运算符实例
  a = 10;
  b = (a == 1) ? 20:30;//条件表达式?:
  printf("b = %d\n",b);
  
  b = (a == 10) ? 20:30;
  printf("b = %d\n",b);
}

C中的运算符优先级

运算符的优先级确定表达式中项的组合。这会影响一个表达式如何计算。一些运算符比其他运算符有更高的优先级。

C 判断

C 语言把任何的非零或者非空的值假定为True,把零或null假定为false。

判断语句

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

语句 描述
if 语句 一个if语句由一个布尔表达式后跟一个或多个语句组成
if…else语句 一个if语句后可跟一个可选的else语句,else语句在布尔表达式为假时执行。
嵌套if语句 你可以在一个if或else if语句内使用另一个if or else if语句
switch 一个switch语句允许测试一个变量等于多个值的情况
嵌套switch语句 可以在一个switch中使用另一个switch语句

?:运算符(三元运算符)

b = (a==1)? 10:20;
//实例通过输入一个数字来判断它是否为奇数或偶数
#include

int main()
{
  int num;
  printf("请输入一个数字");
  scanf("%d",&num);//scanf是获取一个数字,然后存储在该地址中
  (num % 2 == 0 )?printf("偶数"):printf("奇数");
}

Switch语句

switch(表达式)
{
  case 常量表达式1:语句1;
  case 常量表达式2:语句2;
  ...
  default:语言n+1;
}

先计算表达的值,再逐个和case后的常量表达式比较,若不等则继续往下比较,若一直不等,则执行default后的语句;若等于某一个常量表达式,则从这个表达式后的语句开始执行,并执行后面所有的case后的语句。

与if语句的不同,swicth语句不会在执行判断为真后的语句之后跳出循环,使用break防止错误。

#include
int main()
{
  int a;
  printf("input integer number: ");
  scanf("%d",&a);
  switch(a)
  {
    case 1:printf("Monday\n");
    break;
    case 2:printf("Tuesday\n");
    break;
    case 3:printf("Wednesday\n");
    break
    case 4:printf("Thursday\n");
    break;
    case 5:printf("Friday\n");
    break
    case 6:printf("Saturday\n");
    break;
    case 7:printf("Sunday\n");
    break
  }
}

C 循环

循环类型。

循环类型 描述
while循环
for循环
do…while循环
嵌套循环

循环控制语句

控制语句 描述
break语句 终止循环或switch语句
continue语句 告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代
goto语句 将控制转移到被标记的语句。但是不建议在程序中使用goto语句

无限循环

如果条件永远不为假,则循环将变为无限循环。

for循环在传统意义上可用于实现无限循环。由于构成循环的三个表达式中任何一个都是必需的,您可以将某些条件表达式留空来构成一个无限循环

#include
int main()
{
  for(;;)//C程序偏向使用for(;;)结构来表示一个无限循环
  {
    printf("该循环会永远执行下去!\n");
  }
  return 0;
}

实例

使用while、for分别输1~100以内的所有的奇数和偶数的和:

#include
int main()
{
  int sumj = 0;
  int sumo = 0;
  int step = 0;
  while(step < 100)
  {
    step++;
    if(step%2 == 1)//这里注意使用if可以用括号,也可以不用
    sumj+=step;
    else
    sumo+=step;
  } 
  printf("奇数和为:%d,偶数和为:%d\n",sumj,sumo);
}

while循环

while(condition)
{
  statement(s);
}
#include
int main()
{
  //局部变量定义
  int a = 10;
  while( a < 20 )
  {
    printf("a 的值:%d\n", a);
    a++;
  }
  return 0;
}

If…else 语句

if(expression)
{
  //true执行
}
else
{
  //false执行
}

If…else if…else语句

#include
int main()
{
  int a = 100;
  if(a == 10)
  {
    printf("true=10");
  }
  else if(a == 20)
  {
    printf("true=20")
  }
  else
  {
    printf("false");
  }
  printf("%d\n",a);
  
  return 0;
}

for循环

for循环允许您编写一个执行指定次数的循环控制结构

for (init;condition;increment)
{
  statement(s);
}

实例

#include

int main()
{
  for(int a = 10; a < 20; a = a + 1)//第一个为起始值,第二个为判断,第三个为执行程序
  {
    printf("a 的值: %d\n", a);
  }
  return 0;
}

有些编译器会发生报错,如下:

[Error] 'for' loop initial declarations are only allowed in C99 or C11 mode

意思是“不允许在for内部定义变量”

int a=10;
for(; a<20; a=a+1){
}

C 函数

在c语言中,函数由一个函数头和一个函数主体组成。下面列出一个函数所有的组成部分:

  • 返回类型:一个函数可以返回一个值。return_type。无返回值时return_void
  • 函数名称:是函数的实际名称。函数名和参数列表一起构成了函数签名。
  • 参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
  • 函数主体:函数主体包含一组定义函数执行任务的语句。

函数声明

函数声明会告诉编译器名称及如何调用函数。函数的实际主体可以单独定义。

函数声明包括以下的部分

return_type function_name(parameter list)

针对定义的函数max()

int max(int num1;int num2)
int max(int;int)//在函数声明中,参数的名称并不重要,只有参数的类型是必须的,因此下面也是有效的声明:

实例

以下是max()函数的源代码,该函数有两个参数num1与num2,会返回这两个数中较大的那个数:

int max(int num1,int num2)
{
  int result;
  if (num1>num2)
    result = num1;
  else
    result = num2;
  return result;

调用函数

#include
int max(int,int);//函数声明

int main()
{
  int a = 10;
  int b = 100;
  int result;
  result = max( a , b );
  printf("the max value is : %d\n",result);
}

int max( a , b )
{
  int result;
  if ( a > b )
  {
    result = a;
  }
  else if (a = b)
  {
    result = a;
  }
  else
  {
    result = b;
  }
  return result;
}

函数参数

形式参数

如果函数使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。

形式参数就如同函数内其他局部变量,在进入函数时被创建,退出函数时被销毁。

调用函数时,有两种向函数传递参数的方式:

传值方式调用函数

意为默认情况下,副函数(被调用的函数)的作用是用来传递计算数值,而不是用来辅助主程序步骤的。因此副函数的代码不能改变主函数本来的已经设定好的参数。

实例

//传递调用函数
#include

void change (int,int);//函数说明

void change (int x,int y)
{
  int temp;
    
    temp = x;
    x = y;
    y = temp;
    
    return;//此处没有返回值
}

int main()
{
  int a = 100;
  int b = 200;
  printf("a: %d\n",a);
  printf("b: %d\n",b);
  change(a,b);
  printf("a: %d\n",a);
  printf("b: %d\n",b);
   
  return 0;
}//打印出的a,b的值没有改变

引用调用

#include

//函数声明
void swap(int *x,int *y);
int main()
{
  //局部变量定义
  int a = 100;
  int b = 200;
 
  printf("交换前,a的值: %d\n", a);
  printf("交换前,b的值: %d\n", b);
  
  swap(&a,&b);//这里为什么是地址
  
  printf("交换后,a的值: %d\n", a);
  printf("交换后,b的值: %d\n", b);
  return 0;
}

void swap(int *x,int *y)
{
  int temp;
  temp = *x;
  *x = *y;
  *y = temp;
  
  return; 
}

形式参数

通过引用传递方式,形参为指向实参地址的指针,当对形参进行指向操作时,就相当于对实参本身进行操作。

传递指针可以让多个函数访问指针所引用的对象,而不用把对象声明为全局可访问。

C 作用域规则

局部变量

在某个函数或块的内部生命的变量称为局部变量,他们只能被该函数或代码块内部的语句使用。局部变量在函数外部是不可知的。

#include

int main()
{
  //局部变量声明
  int a,b;
  int c;
  //实际初始化
  a = 10;
  b = 20;
  c = a+b;
  printf("value of a = %d, b = %d, c = %d\n",a, b, c);
  
  return 0;
}

形式参数

被当作该函数的局部变量

初始化局部变量与全局变量

当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动对其初始化。

数据类型 初始化默认值
int 0
char ‘\0’
float 0
double 0
pointer NULL

C 的形式参数与实际参数

从字面上理解,所需形式参数即仅仅是声明了一个作为参数的变量,并未直接进行赋值使用,而实际参数则相反。

#include 

int test(int,int); // 形参,只声明

int main()
{
  int a,b;
  printf("%d",test(5,3)); // 实参,已赋值
}

int test(int a,int b) // 形参
{
  a=a+b;
  return a;
}
  • 实际参数可以是变量,变量与表达式
  • 实际参数与形式参数类型相同或赋值兼容
  • 在调用函数时发生实际参数与形式参数的之间的数据传递
  • 在定义函数中制定的形参,在没有出现函数调用时不占用内存中的存储单元。在函数调用时才分配内存
  • 将实参的值传给形参
  • 在执行函数时,由于形参已经有数值,可以用形参进行运算
  • 通过return语句将函数值返回,若无返回值,则无return
  • 调用结束后。形参被释放掉。实参保留原值(单项传值)

C 数组

所有的数组都是由连续的内存位置组成,最低的地址对应一个元素,最高的质地对应最后一个元素。

声明数组

在C中要声明一个数组,需要制定元素的类型和元素的数量,如下所示:

type arrayName [arraySize];
double balance[10];

初始化数组

在C中,你可以逐个初始化数组,也可以使用一个初始化语句

double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

大括号**{}之间值的数目不能大于我们在数组声明时在方括号[]**中指定的元素数目

当我们给**{}**里面的元素赋值时,如果只给前面的部分元素赋值,则后面的元素会自动初始化为0

不同类型的元素自动初始化值说明如下:

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

如果您省掉了数组的大小,数组的大小则为初始化时元素的个数

double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0}
balance[4] = 50;//把上述第五个元素赋值为50

访问数组元素

double salary = balance[9];

实例

#include

int main()
{
  int n[10];
  int i,j;
  for(i=0;i<10;i++)
  {
    n[i] = i + 100;
  }
  for(j=0;j<10;j++)
  {
    printf("element[%d] = %d\n", j, n[j]);
  }
  return 0;
}

得知数组大小

使用sizeof(a)/sizeof(a[0]),前者是获得数组a的字节大小,后者是获得单个元素字节的大小。

sizeof是一个关键字,它是一个编译时运算符,用于判断变量或数据类型的字节大小

#include
 int main()
 {
   int a[] = {1,2,3,4,5};
   int b;
   b = sizeof(a)/sizeof(a[0]);
   printf("%d\n",b);
   return 0;
 }

数组和指针的关系

函数的参数中,数组类型参数int a[ ]本质是指针,可以直接换成int *a;

可以用数组的运算符[ ]进行计算,而且可以通过a[0]就直接修改了外面的数组元素

指针:也是一个变量,存储的数据是地址

数组名:代表的是该数组最开始的一个元素的地址

int a[10];
int *p;
p = &a[]

C enum(枚举)

枚举是C语言中一种基本数据类型,它可以让数据更简洁,更易读。

枚举语法的定义格式为:

enum 枚举名{枚举元素1,枚举元素2... ...};

枚举变量的定义

我们可以通过以下三种方式来定义枚举类型

  1. 先定义枚举类型,在定义枚举变量

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

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

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

枚举使用实例

#include 
enum DAY
{
  MON = 1, THU, WED, FRI, SAT, SUN
};
int main()
{
  enum DAY day;
  day = WED;
  printf("%d", day);
  getchar();
  return 0;
}

C语言中的枚举类型是被当作int或unsigened int类型来处理的,所以按照C语言规则是没有办法遍历枚举类型的。

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

以下实例使用for来遍历

#include
enum DAY
{
  MON = 1, THU, WED, THU, FRI, SAT, SUN
}day;
int main()
{
  //遍历枚举元素
  for (day = MON; day <= SUN ; day++ )
  {
    printf("枚举元素: %d\n", day);
  }
}

以下枚举类型不连续,这种枚举无法遍历。

enum
{
  ENUM_0,
  ENUM_10 = 10,
  ENUM_11
};

枚举在switch中使用:

#include
#include
int main()
{
  enum color {red = 1, green, blue};
  enum color favourite_color;
  printf("请输入你喜欢的颜色:1.red,2.green,3.blue");
  scanf("%d", &favourite_color);
  switch(favourite_color)//这里是要注意switch的拼写,容易写错
  {
    case red:
      printf("你喜欢的颜色是红色");
      break;
    case green:
      printf("你喜欢的颜色是绿色");
      break;
    case blue:
      printf("你喜欢的颜色是蓝色");
      break;
    default:
    printf("你没有选择你喜欢的颜色");
  }//括号的位置
  return 0;
}

寻找bug的三点小建议

  • 中英文标示
  • 函数拼写
  • 括号范围

C 指针

实例

#include 
int main()
{
  int var1;
  char var2[10];
  printf("var1 变量的地址: %p\n", &var1);
  printf("var2 变量的地址: %p\n", &var2);
  return 0;
}

指针是一个变量

其值为另一个变量的地址

即内存位置的直接地址

您必须在使用指针存储其他变量地址之前,对其进行声明。

type *var-name;

在这里,type是指针的基类型,var-name是指针变量的名称

用来声明指针的星号 * 与乘法中使用的星号 * 是相同的

int *ip; //整型的指针
double *dp; //一个double型的指针
float *fp; //一个浮点型的指针
char *ch; //一个字符型的指针

指针的值为代表内存地址的十六进制数

使用指针会频繁进行以下几个操作:

定义一个指针变量

把变量地址赋值给指针

访问指针变量中可用地址的值

#include
int main()
{
  int var = 20;
  int *ip;
  ip = &var;
  printf("%d\n", &var);
  printf("%d\n", ip);
  printf("%d\n", *ip);
  printf("%d\n", &*ip);//%d是十进制的形式
  return 0;
}

函数指针

函数指针是指向函数的指针变量

指针变量是指向一个整型,字符型或数组等变量

函数指针是指向函数的

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

typedef int(*fun_ptr)(int,int);

实例

以下实例声明了函数指针变量P,指向函数max;

#include
int max(int x, int y)
{
  return x>y ? x : y;//return还可以这样用啊
}

int main(void){
  //*p是函数指针
  int (*p)(int, int) = & max;
  int a, b, c, d;
  printf("please enter three numbers");
  scanf("%d %d %d ", &a, &b, &c);
  d = p(p(a,b), c);
  printf("the maximum number is : %d\n", d);
  
  return 0;
}

回调函数

函数指针作为某一个函数的参数

实例

#include 
#include 
//回调动作
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
  for (size_t i=0; i<arraySize; i++)//size_t是一种数据类型,近似于无符号数整型,但容量范围一般大于int和unsigned,这里使用 size_t 是为了保证 arraysize 变量能够有足够大的容量来储存可能大的数组。
    array[i] = getNextValue();
}
//获取随即值,回调函数
int getNextRandomValue(void)
{
  return rand()%100+1;
}

int main(void)
{
  int myarray[10];
  populate_array(myarray, 10, getNextRandomValue);
  for(int i = 0; i < 10; i++)
  {
    printf("%d\n", myarray[i]);
  }
  printf("\n");
  return 0;
}

C语言中的rand函数

rand函数会产生一个随机数


C字符串

字符串使用null字符“\0“终止字符数组,因此,一个以null结尾的字符串包含了组成字符串的字符

创建字符串

char greeting[6] = {'H','e','l','l','o','\0'};
//系统会直接初始化
char greeting[] = "Hello";

实例

#include

int main()
{
  char greeting[6] = {'H','e','l','l','o','\0'};
  printf("Greeting message: %s\n", greeting);
  return 0;
}

C中大量操作字符串的函数

序号 函数&目的
1.strcpy(s1,s2) string copy复制字符串s2到s1
2.strcat(s1,s2) string catenate将字符串s2连接到s1的末尾
3.strlen(s1,s2) string length返回字符串s1的长度
4.strcmp(s1,s2) string compare如果 s1 和 s2 是相同的,则返回 0;如果 s1s2 则返回大于 0。
5.strchr(s1,ch) 返回一个指针,指向字符串s1中字符ch的第一次出现的位置
6.strstr(s1,s2) 返回一个指针,指向字符串s1中字符串s2的第一次出现的位置

字符串中值得注意的一些事项

//''与“ ”的区别
'a'//里面只能放一个字符
"a"//这是字符串,相当于'a'+\0

C 结构体

C数组允许定义可存储相同类型数据项的变量,结构是C编程中另一种可用的数据类型,它允许您存储不同类型的数据项

定义结构

使用struct语句,struct语句定义了一个包含多个成员的新的数据类型

struct tag
{
  number-list
  number-list
  number-list
  ...
}variable-list ;

tag是结构体标签

number-list是标准的变量的定义,比如int i or float f,或者其他有效的变量定义

varibale-list 是结构变量,定义在结构的末尾,最后一个分支之前,您可以指定一个或者多个结构变量

struct Books
{
  char titles[50];
  char authors[50];
  char subjects[100];
  int book_ID;
}book;

在一般情况下,tag、member-list、variable-list这3部分至少要出现2个

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

//此声明声明了三个成员的结构体,分别为整型的a,字符串的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
  int a;
  char b;
  double c;
};
//用SIMPLE标签的结构体,另外声明了变量的t1、t2、t3
struct SIMPLE t1,t2[20],*t3;


//也可以用typedef创建新类型
typedef struct
{
  int a;
  char b;
  double c;
}Simple2;
//现在也可以用SImple2作为类型声明新的结构体变量
Simple2 u1,u2[20], *u3;

在上面的声明中,第一个声明和第二个声明被编译器当作两个完全不同的类型,即使他们的成员列表一样的。

结构体的成员可以包含其他结构体。也可以包含指向自己的结构体类型的指针。

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

//此结构体的声明包含了指向自己类型的指针

struct NODE
{
  char string[100];
  struct NODE *next_node;
};

如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明

struct B;//对结构体B进行不完整声明
struct A
{
  struct B *partner;
  //other members;
};

//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
  struct A *partner;
    //other memebers;
};

结构体变量的初始化

#include

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

int main()
{
  printf("title: %s\nauthor: %s\nsubject: %s\nbook_ID: %d\n", book.title, book.author, book.subject, book.book_ID);
}

访问结构成员

为了访问结构成员,使用成员访问运算符==.==

#include
#include//头文件

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

int main()
{
  struct Books Book1;  //声明 Book1,类型为 Books
  struct Books Book2;  //声明Book2,类型为Books
  
  //Book1详述
  strcpy(Book1.title, "C Programmming");
  strcpy(Book1.author, "Nuha Ali");
  strcpy(Book1.subject, "C Programming Tutorial");
  Book1.book_ID = 5684847;
  
  //Book2详述
  
  strcpy(Book2.title, "Telecom Billing");
  strcpy(Book2.author, "Zara Ali");
  strcpy(Book2.subject, "Telecom Billing Tutorial");
  Book2.book_ID = 3848484;
  
  //Book1 信息
  printf( "Book 1 title : %s\n", Book1.title);
  printf( "Book 1 author : %s\n", Book1.author);
  printf( "Book 1 subject : %s\n", Book1.subject);
  printf( "Book 1 book_id : %d\n", Book1.book_ID);
 
   //Book2 信息
  printf( "Book 2 title : %s\n", Book2.title);
  printf( "Book 2 author : %s\n", Book2.author);
  printf( "Book 2 subject : %s\n", Book2.subject);
  printf( "Book 2 book_id : %d\n", Book2.book_ID);
  
  return 0;
}

结构作为函数参数

可以把结构作为函数参数,传递方式与其他类型的变量或指针类似

实例

#include 
#include 

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

//函数声明
void printBook(struct Books book);//定义函数

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_ID : %d\n", book.book_ID);
}

int main()
{
  struct Books Book1;//声明book1 类型为Books
  struct Books Book2;//声明book2 类型为Books
  
  //book1的详述
  strcpy(Book1.title, "C Programming");
  strcpy(Book1.author, "Nuha Ali");
  strcpy(Book1.subject, "C Programming Thtorial");
  Book1.book_ID = 692334;
  
  //book2的详述
  strcpy(Book2.title, "Telecom Billing");
  strcpy(Book2.author, "Zara Ali");
  strcpy(Book2.subject, "Telecom Billing Tutorial");
  Book2.book_ID = 6923234;
  
  printBook(Book1);
  printBook(Book2);
  
  return 0;
}

指向结构的指针

你可以定义指向结构的指针 ,方式与定义指向其他类型变量的指针相似

struct Books *struct_pointer;

现在,您可以在上述定义的指针变量中的存储结构变量的地址

为了查询结构变量的地址,请把&运算符放在结构名称的前面

struct_pointer = &Book1;

为了使用该结构的指针访问结构的成员,您必须使用->运算符

struct_pointer->title;

让我们使用结构指针来重新写上面的实例,这有助于您理解结构指针的概念

实例

#include 
#include 

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

//函数声明
void printBook( struct Books *book);

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);
}

int main()
{
  struct Books book1;
  struct Books book2;
  
  strcpy( book1.title, "C Programming");
  strcpy( book1.author, "Nuha Ali"); 
  strcpy( book1.subject, "C Programming Tutorial");
  book1.book_id = 6495407;
 
   /* Book2 详述 */
  strcpy( book2.title, "Telecom Billing");
  strcpy( book2.author, "Zara Ali");
  strcpy( book2.subject, "Telecom Billing Tutorial");
  book2.book_id = 6495700;
 
   /* 通过传 Book1 的地址来输出 Book1 信息 */
  printBook( &book1 );
 
   /* 通过传 Book2 的地址来输出 Book2 信息 */
  printBook( &book2 );
 
   return 0;
}

位域

有一些信息在存储时,并不不要占用一个完整的字节,而只需要占几个或一个二进制位。例如在存放一个开关量时,只有0和1两种状态,用1位二进位即可。

为了节省存储空间 C 语言又提供了一种数据结构,称为"位域"或“位段”

位域是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域都有一个域名,允许在程序中按域名进行操作。

这样就可以把几个不同的对象用一个字节的二进制位域来表示

实例

  • 用1位二进位存放一个开关量时,只有0与1两种状态
  • 读取外部文件格式,可以读取非标准的文件格式。例如:9位的整数

位域的定义和位域变量的说明

位域定义与结构定义相仿

struct 位域结构名
{
  位域列表
}

其中的位域列表的形式

类型说明符 位域名;位域长度

例如

struct bs
{
  int a:8;
  int b:2;
  int c:6;
}data;

说明data 为bs变量,共占有两个字节。其中位域a占8位,位域b占2位,位域c占6位

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-f4,一个4位的type和一个9位的my_int。

对于位域的定义尚有以下几点说明

  • 一个位域存储在同一个字节中,如一个字节所剩下的空间不够存放另一个位域时,则会从下一单元存放改位域。也可以有意使某位域从下一单元开始

  • struct bs
    {
      unsigned a:4;
      unsigned  :4;//空域
      unsigned b:4;//从下一单元开始存放
      unsigned c:4;
    }
  • 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进制位

  • 如果最大长度大于计算机的整数字长,一些编译器可能会允域的内存重叠

  • 另外一些编译器可能会把大于一个域的部分存储在下一个字中

  • 位域可以是无名位域,只能用来填充或调整位置。无名的位域是不能使用的

  • struct K{
      int a:1;
      int  :2;
      int b:3;
      int c;2;
    }

位域在本质上就是一种结构类型,不过其成员是按二进位分配的

位域的使用

位域使用和结构成员的使用相同,其一般形式为:

位域变量名.位域名
位域变量名->位域名

位域允许各种格式输出

实例

mian()
{
  struct bs
  {
    unsigned a : 1;
    unsigned b : 3;
    unsigned c : 4;
  }bit,*pbit;
  bit.a = 1;
  bit.b = 7;
  bit.c = 15;
  printf("%d,%d,%d\n", bit.a, bit.b, bit.c);//以整型量格式输出三个域的内容
  pbit =&bit; //把位域变量bit的地址送给指针变量
  pbit -> a = 0;//用指针方式给位域a重新赋值 赋为0
  pbit ->b&=3; //使用了复合的运算符“&=”,相当于:pbit -> b = pbit -> b&3,位域中b中原有值为7,与3作按位与运算的结果为3(111&011 = 011,十进制为3)
  pbit->c|=1;//使用了复合运算符“|=”,相当于:pbit->c = pbit -> c|1,其结果位15
  printf("%d,%d,%d\n", pbit->a, pbit->b, pbit->c);
}

共用体

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型

您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。

共用体提供了一种使用相同的内存位置的有效方式

定义共用体

union [union tag]
{
  member definition;
  member definition;
  ...
  member definiton;
}
[one or more union variables];

union tag 是可选的

每个 member definition 是标准的变量定义

比如 int i; 或者 float f; 或者其他有效的变量定义。

在共用体定义的末尾,最后一个分号之前

您可以指定一个或多个共用体变量,这是可选的。

下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str

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

现在,Data类型的变量可以存储一个整数,一个浮点数或者一个字符串。

这意味着一个变量(相同的内存位置)可以存储多个类型的数据

您可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型

共用体占用的内存应足够存储共用体中最大的成员

例如,在上面的实例中,Data将占用20个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。

下面的实例将显示上面的共用体所占用的总内存大小

实例

#include 
#include

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;
}

访问共用体成员

为了访问共用体的成员,使用成员访问运算符==.==

#include 
#include 

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 
#include 

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

int main()
{
  union Data data;
  data.i = 0;
  printf("%d\n", data.i);
  
  data.f = 220.5;
  printf("%f\n", data.f);
  
  strcpy(data.str, "C Programming");//字符串赋值
  printf("%s\n", data.str);
  
  return 0;
}

C 位域

如果程序的结构中包含多个开关量,只有TRUE/FALSE

struct
{
  unsigned int widthValidated;
  unsigned int heightValidated;
} status;

这种结构需要8字节的内存空间

实际上,在每个变量中,我们只存储0或者1

C 语言提供了一种更好的利用内存空间的方式

如果您在结构内使用这样的变量

您可以定义变量的宽度来告诉编译器

您将只使用这些字节

改写上面

struct
{
  unsigned int widthValidated : 1;//int占用4个字节
  unsigned int widthValidated : 1;
}status;

上面的结构中,status变量将占用4个字节的内存空间

但是只有2位被用来存储值

如果您使用了32个变量,每一个变量宽度为1位

那么status结构将使用4个字节

如果您在多用一个变量,比如33个变量

那么它将分配内存的下一段来存储33个变量

这个时候就开始使用8个字节

#include 
#include 

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

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

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

位域声明

在结构内声明位域的形式如下:

struct
{
  type [member_name] : width ;
};

下面是有关位域中变量元素的描述:

元素 描述
type 只能为int(整型),unsigned int(无符号数整型),signed int(有符号数)三种类型,决定了如何解释位域的值。
member_name 位域的名称
width 位域中位的数量。宽度必须小于或等于指定类型的位宽度。

带有预定义宽度的变量被称为位域。

位域可以存储多于一位的数

需要一个变量来存储从0到7的值,您可以定义一个宽度位3位的位域

000 001 010 011 100 101 111

如下

struct
{
  unsigned int age : 3;
}Age;

上面的结构定义指示C 编译器,age变量将只使用3位来存储这个值,如果您试图使用超过3位,则无法完成

实例

#include 
#include 

struct
{
  unsigned int age : 3;
}Age;

int main()
{
  Age.age = 4;
  printf("Sizeof(Age) : %d\n", sizeof(Age));
  printf("Age.age : %d\n", Age.age);
  
  Age.age = 7;
  printf("Age.age : %d\n", Age.age);
  
  Age.age = 8; //二进制表示为1000有四位,超出
  printf("Age.age : %d\n", Age.age);
  
  return 0;
}

结果

Sizeof(Age) : 4
Age.age : 4
Age.age : 7
Age.age : 0//超出丢失,左移

C typedef

C 语言提供了typedef关键字

使用它为类型取一个新的名字

typedef unsigned char BYTE;//char为单字节

在这个类型之后,标识符BYTE可作为unsigned char的缩写,例如:

BYTE b1, b2;

按照惯例,定义时会大写字母,以便提醒用户类型名称是一个象征性的缩写

typedef unsigned char byte;

您也可以使用typedef来为用户自定义的数据类型

您可以对结构体使用typedef来定义一个新的数据类型

然后使用这个新的数据来定义结构变量

实例

#include
#include

typedef struct Books
{
  char title[50];
  char author[50];
  char subject[100];
  int book_id;
}Book;
int main()
{
  Book book;
  
  strcpy(book.title,"c 教程");
  strcpy(book.author, "runoob");
  strcpy(book.subject, "编程语言");
  book.book_id = 12345;
  
  printf("书 标题 : %s\n", book.title);
  printf("书 作者 : %s\n", book.author);
  printf("书 类目 : %s\n", book.author);
  printf("书 ID  : %d\n", book.book_id);
  
  return 0;
}

typedef vs #define

#define 是 C 指令,用于为各种数据类型定义别名,与typedef类似,但是它们有以下几点不同:

  • typedef仅限于为类型定义符号名称,#define不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义1 为 ONE
  • typedef是由编译器执行解释的,#define语句是由预编译器进行处理的

下面是#define的最简单的用法:

实例

#include 

#define TRUE 1
#define FALSE 0

int main()
{
  printf("TURE : %d\n", TURE);
  printf("FALSE : %d\n", FALSE);
  
  return 0;
}

C 输入&输出

当我们提到输入时,这意味着要向程序填充一些数据

输出时。,这意味在屏幕上,打印机上或任意文件中显示一些数据。C语言提供了一系列内置的函数来输出数据道计算机屏幕上

标准文件

C 语言把所有的设备都当作文件。所以设备被处理的方式与文件相同

以下三个文件会在程序执行时自动打开,以方便访问键盘和屏幕

标准文件 文件指针 设备
标准输入 stdin 键盘
标准输出 stdout 屏幕
标准错误 stderr 您的屏幕

文件指针是访问文件的方式,本节将讲解如何从屏幕读取值以及如何把结果输出到屏幕上

C 语言中的输入\输出 (I/O) 使用printf()和scanf()两个函数

scanf()函数用于从标准输入(键盘)读取并格式化

printf()函数发送格式化输出道标准输出(屏幕)

#include  //执行printf()函数需要该库
int main()
{
  printf("cainiaojiaocheng");
  return 0;
}

入门级

  • 所有的C语言程序都需要包含main()函数。代码从mian()函数开始执行
  • printf()用于格式化输出到屏幕。printf()函数在stdio.h头文件中声明
  • stdio.h是一个头文件(标准输入输出头文件)
  • #include是一个预处理命令,用来引入头文件。
  • 当编译器遇到printf()函数时,如果没有找到stdio.h头文件,会发生编译错误
  • return 0,语句用于表示退出程序

格式化输出

#include 
int main()
{
  int testInteger = 5;
  printf("Number = %d", testInteger);
  
  float f;
  printf("enter a number : ");
  //匹配浮点型数据
  scanf("%f", &f);
  printf("value = %f", f);
  return 0;
}

getchar()&putchar()函数

int getchar(void)函数从屏幕读取一个可用的字符,并把它返回为一个整数

C 文件读写

文件代表了一系列字节

C 语言不仅提供了访问顶层的函数,也提供了底层(os)调用的来处理存储设备上的文件

打开文件

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

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

在这里,filename是字符串,用来命名文件,访问模式mode的值可以是下列值一个

r 打开一个已有的文本文件,允许读取文件
w 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。如果文件存在,则该会被截断为零长度,重新写入
a 打开一个文件文本,以追加模式写入文件。如果文件不存在,则会创建一个新文件
r+ 打开一个文件文本,允许读写文件
w+ 打开一个文本文件,允许读写文件。如果文件已经存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件
a+ 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入的只能是追加模式

如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:

"rb","wb","ab","rb+","r+b","wb+","ab+","a+b"

const关键字

常变量//既有常量的性质,又有变量的性质

const int a = 10;
int const a = 10;//两者可以互换

用const修饰的变量,无论是全局变量还是局部变量

生存周期都是程序运行的整个过程

局部变量存储在

静态变量存储在静态存储区

而经过const修饰过的变量存储在内存中的只读数据段

只读数据段中存放着常量和只读变量等不可修改的量

系统只会给变量分配内存

关闭文件

为了关闭文件,请使用fclose()函数。函数的原型如下:

int fclose (FILE *fp);

如果成功关闭文件,fclose() 把参数c的字符值写入到fp所指向的输出流中

如果写入成功,它会返回写入的字符

如果发生错误,则会返回EOF

这个函数实际上,会清空缓冲区中的数据,关闭文件

释放用于该文件的所有内存

EOF是一个定义在头文件stdio.h的常量

写入文件

下面是把字符写入到流中的最简单的函数:

int fputc (int c, FILE *fp);

函数fputc()参数c的字符值写入到fp所指向的输出流中

如果写入成功,他会返回写入的字符

如果发生错误,则会返回EOF

您可以使用下面的函数来把一个以null结尾的字符串写入到流中

int fputs(const char * s ,FILE * fp);

函数fputs()把字符串s写入到fp所指向的输出流中

如果写入成功

它会返回一个非负值

如果发生错误

则会返回EOF

您可以使用 int fprintf(FILE *fp,const char *format,…)函数来把一个字符串写入到文件中

注意请确保您有可用的tmp目录,如果不存在该目录,则需要在您的计算机上先创建目录

/tmp一般是Linux系统上的临时目录,如果你在Windows系统上运行,则需要修改为本地环境中已存在的目录,例如:C:\tmp,D\tmp等

#include 
int main()
{
  FILE *fp = NULL;
  fp = fopen("/tmp/text.txt","w+");
  fprintf(fp, "This is testing for printf...\n");
  fputs("This is testing for fputs...\n",fp);
  fclose(fp);
}

读取文件

下面是从文件读取单个字符的最简单的函数

int fgetc(FILE *fp);

fgetc()函数从fp所指向的输入文件中读取一个字符

返回值是读取的字符

如果发生错误则返回EOF

下面的函数允许您从流中读取一个字符串

char *fgets(char *buff, int n, FILE *fp);

函数 fgets() 从 fp 所指向的输入流中读取 n-1 个字符。

它会把读取的字符串复制到缓冲区 buf

并在最后追加一个 null 字符来终止字符串。

如果这个函数在读取最后一个字符之前就遇到一个换行符 ‘\n’ 或文件的末尾 EOF,则只会返回读取到的字符,包括换行符。

您也可以使用 int fscanf(FILE *fp, const char *format, …) 函数来从文件中读取字符串

但是在遇到第一个空格和换行符时,它会停止读取

#include 
 
int main()
{
   FILE *fp = NULL;
   char buff[255];
 
   fp = fopen("/tmp/test.txt", "r");
   fscanf(fp, "%s", buff);
   printf("1: %s\n", buff );
 
   fgets(buff, 255, (FILE*)fp);
   printf("2: %s\n", buff );
   
   fgets(buff, 255, (FILE*)fp);
   printf("3: %s\n", buff );
   fclose(fp);
 
}

C 预处理器

c 预处理器不是编译器的组成部分

简言之,C预处理器只不过是一个文本替换工具而已

他们会指示编译器在实际编译之前完成所需的预处理,我们将把c预处理器(C Preprocessor)简写为CPP

所有的预处理器命令都是以井号键(#)开头,他必须是第一个非空字符

为了增强可读性

预处理器指令应从第一列开始

指令 描述
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else 如果前面的#if给定条件下不为真,当前条件为真,则编译下面代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个#if…#else条纹编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中

预处理器实例

分析下面的实例来理解不同的指令

#define MAX_ARRAY_LENGTH 20

这个指令告诉CPP把所有的MAX_ARRAY_LENGTH替换为20

#include 
#include "myheader.h"

这些指令告诉CPP从系统库中获取stdio.h

从本地目录中获取myheader.h,并添加内容到当前的源文件中

取消定义-定义

#undef FILE_SIZE//取消已经定义的FILE_SIZE
#define FILE_SIZE 42//重新定义FILE

未定义时-定义

#ifndef MESSAGE
#define MESSAGE "You wish"
#endif

如果已经定义,则执行

#ifdef DEBUG
//YOUR DEBUGGING STATEMENTS HERE
#endif

预定义宏

ANSI C定义了许多宏

在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏

描述
-DATE- 当前日期,一个以“MMM DD YYYY”格式表示的字符常量
-TIME- 当前时间,一个以“HH:MM:SS”格式表示的字符常量
-FILE- 这会包含当前文件名,一个字符串常量
-LINE- 这会包含当前行号,一个十进制常量
-STDC 当编译器以ANSI标准编译时,则定义为1
#include 
main()
{
    printf("File : %s\n",_FILE_);
    printf("File : %s\n",_DATE_);
    printf("Time : %s\n",_TIME_);
    printf("Line : %s\n",_LINE_);
    printf("ANSI : %d\n",_SIDC_);
}

预处理器运算符

C处理器提供了下列的运算符来帮助您创建宏

宏延续运算符==(\)==

如果宏通常写在一个单行上,但是如果宏太长,一个单行容纳不下,则使用宏延续运算符

#define message_for(a,b)\
printf(#a " and " #b ": We love you!\n")//#字符串常量化运算符

字符串常量化运算符(#)

在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)

在宏中使用的该运算符有一个特定的参数或参数列表。例如:

#include

#define message_for(a,b)\
printf(#a " and " #b "We love you\n")

int main()
{
    message_for(carloe,beniz);//使用define也可以定义函数
    return 0;
}

标记粘贴运算符==(##)==

宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。例如:

#include 
#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);

define()运算符

预处理器defined运算符是用在常量表达式中的,用来确定一个标识符是否已经使用#define定义过

如果指定的标识符已定义,则值为真(非零)

如果指定的标识符未定义,则值为假(灵)

下面的实例演示了defined()运算符的用法:

#include

#if !defined(MESSAGE)//判断用法
  #define MESSAGE "YOU wish"//定义宏
#endif

int main(void)
{
    printf("Here is the message : %s\n", MESSAGE);
    return 0;
}

参数化的宏

CPP中一个强大的功能是可以使用参数化的宏来模拟函数

例如,下面的代码

int square(int x) 
{
   return x * x;
}//定义普通函数

//使用宏定义函数
#define square(X) ((X)*(X))

在使用带有参数的宏之前,必须使用#define定义

#include 

#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;
}

C 头文件

头文件是扩展名为.h的文件,包含了C 函数声明了和宏定义,被多个源文件中引用共享

有两种类型的头文件:程序员编写的头文件和编译器自带的头文件

引用头文件相当于复制头文件的内容,但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,特别是在程序是由多个源文件组成的时候

建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件

引用头文件的语法

使用预处理器指令#define可以引用用户和系统头文件

#include //引用系统头文件
#include “file”

引用头文件中的操作

#include指令会指示C预处理器浏览指定的文件作为输入

预处理器的输出包含了已经生成的输出、被引用文件生成的输出以及#include指令之后的文本输出,例如您有一个头文件header.h,如下:

char *test (void);

和一个使用了头文件的主程序program.c

int x;
#include "header.h"//编译器会将此句视为char *test(void)
int main(void)
{
    puts (test());
}

只引用一次头文件

如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将会产生错误

标准的做法是把文件的整个内容放在条件编译语句中

#ifdef HEADER_FILE
#define HEADER_FILE

the entire header file
#endif

有条件引用

有时需要从多个不同的头文件中选择一个引用到程序中,例如,需要指定在不同的操作系统上使用的配置参数

#if SYSTEM_1
    #include "system_1.h"
#elif SYSTEM_2
    #include "syste,_2.h"
#enlif SYSTEM_3
...
    #endif

但是如果头文件比较多的时候,这么做是很不妥当的,预处理器使用宏来定义头文件的名称。

这就是所谓有条件引用

它不是用头文件的名称作为 #include 的直接参数,您只需要使用宏名称代替即可:

 #define SYSTEM_H "system_1.h"
 ...
 #include SYSTEM_H

强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。

例如,如果您想存储一个long类型的值到一个简单的整型中,您需要把long类型强制转换为 int类型。

您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型,如下所示:

#include 
 
int main()
{
   int sum = 17, count = 5;
   double mean;
 
   mean = (double) sum / count;
   printf("Value of mean : %f\n", mean );
 
}

这里要注意的是强制类型转换运算符的优先级大于除法

因此 sum 的值首先被转换为 double 型,然后除以 count,得到一个类型为 double 的值。

类型转换可以是隐式的,由编译器自动执行,也可以是显式的,通过使用强制类型转换运算符来指定。

在编程时,有需要类型转换的时候都用上强制类型转换运算符,是一种良好的编程习惯。

整数提升

整数提升是指把小于intunsigned int的整数类型转换为intunsigned int的过程

#include 
int main()
{
    int i = 17;
    char c = 'c';//ascii的数值为99
    int sum;
    
    sum = i + c;
    printf("Value of sum : %d\n",sum);
}

当上面的代码被编译和执行时,它会产生下列结果

Value of sum : 116

在这里,sum的值为116,因为编译器进行了整数提升,在执行实际加法运算时,把c的值转换为对应的ascii的值

常用的算术转换

常用的算术转换是隐式地把值强制转换为相同的类型。

编译器首先执行整数提升,如果操作数类型不同,则它们会被转换为下列层次中出现的最高层次的类型:

int
unsigned int
long
unsigned long
long long
unsigned long long
float
double
long double

常用的算术转移不适用于赋值运算符、逻辑运算符&&和||

让我们看看下面的实例理解这个概念:

#include 
int main()
{
    int i = 17;
    char c = 'c';
    float sum;
    
    sum = i + c;//此处应有强制类型转换
    printf("Value of sum : %f\n",sum)}

当上面代码被编译和执行时,它会产生下列结果

Value of sum : 116.000000

在这里,c首先被转换为整数

但是由于最后的值是 float型的,所以会应用常用的算术转换

编译器会把ic转换为浮点型(sum的类型)

并把它们相加得到一个浮点数。

C语言中printf输出doublefloat都可以用 %f 占位符

可以混用,而 double可以额外用 %lf

scanf输入情况下 double 必须用%lffloat 必须用 %f 不能混用。

C 错误处理

C 语言不提供对错误处理的直接支持,但是作为一种系统编程语言,它以返回值的形式允许您访问底层数据。

在发生错误时,大多数的CUNIX 函数调用返回 1NULL,同时会设置一个错误代码 errno,该错误代码是全局变量,表示在函数调用期间发生了错误。

您可以在 errno.h 头文件中找到各种各样的错误代码。

所以,C 程序员可以通过检查返回值,然后根据返回值决定采取哪种适当的动作。开发人员应该在程序初始化时,把errno设置为 0,这是一种良好的编程习惯。0 值表示程序中没有错误。

errnoperror()strerror()

C 语言提供了error()strerror() 函数来显示与 errno 相关的文本消息。

  • perror() 函数显示您传给它的字符串,后跟一个冒号、一个空格和当前 errno 值的文本表示形式。
  • strerror() 函数,返回一个指针,指针指向当前 errno 值的文本表示形式。

让我们来模拟一种错误情况,尝试打开一个不存在的文件。

您可以使用多种方式来输出错误消息,在这里我们使用函数来演示用法。

另外有一点需要注意,您应该使用 stderr 文件流来输出所有的错误。

#include 
#include 
#include 

extern int errno;

int main()
{
    FILE *pf;
    int errnum;
    pf = fopen("unexist.txt","rb");
    if (pf == NULL)
    {
        errnum = errno;
        fprintf(stderr,"错误号:%d\n",errno);
        perror("通过perror输出错误");//这种最简单
        fprintf(stderr,"打开文件错误:%s\n", strerror(errnum));
    }
    else
    {
        fclose(pf);
    }
    return 0;
}

被零除的错误

在进行除法运算时,如果不检查除数是否为零,则会导致一个运行时错误。

为了避免这种情况发生,下面的代码在进行除法运算前会先检查除数是否为零:

#include 
#include 
 
main()
{
   int dividend = 20;
   int divisor = 0;
   int quotient;
 
   if( divisor == 0)
   {
      fprintf(stderr, "除数为 0 退出运行...\n");//直接printf打印错误不香么
      exit(-1);//非正常运行导致退出程序,与1类似
   }
   quotient = dividend / divisor;
   fprintf(stderr, "quotient 变量的值为 : %d\n", quotient );
 
   exit(0);
}

当上面的代码被编译和执行时,它会产生下列结果:

程序退出状态

通常情况下,程序成功执行完一个操作正常退出的时候会带有值 EXIT_SUCCESS。在这里,EXIT_SUCCESS 是宏,它被定义为 0。

如果程序中存在一种错误情况,当您退出程序时,会带有状态值 EXIT_FAILURE,被定义为 -1。所以,上面的程序可以写成:

#include 
#include 
 
main()
{
   int dividend = 20;
   int divisor = 5;
   int quotient;
 
   if( divisor == 0){
      fprintf(stderr, "除数为 0 退出运行...\n");
      exit(EXIT_FAILURE);
   }
   quotient = dividend / divisor;
   fprintf(stderr, "quotient 变量的值为: %d\n", quotient );
    //stderr到底是个啥意思
 
   exit(EXIT_SUCCESS);
}

问题-关于stderr

  • 为什么要用stderr
  • 为什么要使用fprintf

C 递归

递归指的是在函数的定义中使用函数自身的方法

TRUE
FALSE
递归函数
执行语句
条件
执行语句
退出

C 语言支持递归,即一个函数可以调用其自身。

但在使用递归时,程序员需要注意定义一个从函数退出的条件,否则会进入死循环。

递归函数在解决许多数学问题上起了至关重要的作用,比如计算一个数的阶乘、生成斐波那契数列,等等。

数的阶乘

下面实例使用递归函数计算一个给定的数的阶乘

#include 

double factorial(unsigned int i)
{
    if (i<=1)
    {
        return 1;
    }
    return i * factorial(i - 1);
}

int main()
{
    int i = 15;
    printf("%d 的阶乘 %f\n", i, factorial(i));
    return 0;
}

C 可变参数

有时,您可能会碰到这样的情况,您希望函数带有可变数量的参数,而不是预定义数量的参数。

C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数。下面的实例演示了这种函数的定义。

int func(int,...)//int代表要传递的可变参数的总数
{
    .
        .
        .
}

int main()
{
    func(2,2,3);
    func(3,2,3,4);
}

请注意,函数func()的最后一个参数写成省略号

省略号之前的那个参数是int,代表了要传递的可变参数的总数

为了使用这个功能,您需要使用stdarg.h头文件,该文件提供了可供实现的可变参数功能的函数和宏。

具体步骤如下:

  • 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
  • 在函数定义中创建一个va_list 类型变量,该类型是在 stdarg.h头文件中定义的。
  • 使用 int 参数和 va_start宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
  • 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
  • 使用宏va_end 来清理赋予 va_list 变量的内存。

va在这里是variable-arguement(可变参数)

实例,求给出参数平均值

#include
#include

double average(int num,...)//给其命名
{
    va_list valist;
    double sum = 0.0;
    int i;
    
    //for every num 初始化valist
    va_start(valist,num);//初始化一个参数列表
    
    //访问所有赋给valist的参数
    for (i=0;i

C 内存管理

本章中讲解C中动态内存管理。C语言为内存的分配和管理提供了几个函数。这些函数可以在头文件中找到。

序号 函数跟描述
1 void*calloc(int num, int size);
在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 numsize 个字节长度的内存空间,并且每个字节的值都是0。
2 void free(void *address);
该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
3 void *malloc(int num)
堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
4 void *realloc(void *address, int newsize) ;
该函数重新分配内存,把内存扩展到 newsize

注意:void *类型表示未确定类型的指针。C、C++规定void * 类型可以通过类型强制转换为指针

动态分配内存

编程时,如果您预先知道数组的大小,那么定义数组时就比较容易。

例如,一个存储人名的数组,它最多容纳100个字符,所以您可以定义数组,如下所示:

char name[100];

但是,如果您预先不知道需要存储的文本长度,例如您想存储一个有关主题的详细描述。

在这里,我们需要顶一个指针,该指针指向未定义所需内存大小的字符,后续再根据需求来分配内存,如下所示:

实例

#include 
#include 
#include 

int main()
{
    char name[100];
    char *description;
    
    strcpy(name,"Zara Ali");
    //动态内存分配
    description = (char *)malloc(200 * sizeof(char));//此处200是什么意思,定义200个char括号是几个意思
    if (description == NULL)
    {
        fprintf(stderr,"Error - unable to allocate required memory\n");//错误警示
    }
    else
    {
        strcy(description,"Zara ali a DPS student in class 10th")
    }
    printf("Name = %s\n",name);
    printf("Description : %s\n",description);
}

上面程序也可以用calloc()来编写,只需要把malloc替换为calloc即可,如下所示:

calloc(200,sizeof(char));

当动态分配内存时,您有完全控制权,可以传递任何大小的值。而那些预先定义了大小的数组,一旦定义则无法改变大小。

重新调整内存的大小和释放内存

当程序退出时,操作系统会自动释放所有分配给程序的内存,但是建议您在不需要内存时,都应该调用函数free()来释放内存。

或者,您可以通过调用函数realloc()来增加或减少已分配的内存块的大小。

让我们使用realloc()和tree()函数,再次查看上面的实例;

实例

#include 
#include 
#include 

int main()
{
    char name[100];
    char *description;
    strcpy(name,"Zara Ali");
    //动态分配内存
    description = (char*)malloc(30*sizeof(char));
    if (description == NULL)
    {
        fprintf(stderr,"Error - unable to allocate required memeory");
    }
    else
    {
        strcpy(description,"Zara ali a DPS student");
    }
    //假如您想存储更大的描述信息
    description = (char*)realloc(description,100*sizeof(char));
    if (description == NULL)
    {
        fprintf(stderr,"Error - unable to allocate required memory\n");
    }
    else
    {
        strcat(description,"She is in class 10th");
    }
    printf("Name = %s\n", name);
    printf("Description: %s\n",description);
    //使用free释放函数内存
    free(description);
}

C 命令行参数

执行程序时,可以从命令行传值给C程序。这些值称为命令行参数,它们对程序很重要,特别是当您想从外部控制程序,而不是在代码内对这些值进行硬编码时,就显得尤为重要了。

命令行参数是使用main()函数参数来处理的,其中argc是指传入参数的个数,argv[]是一个指针数组,指向传递给程序的每个参数。

下面是一个简单的实例,检查命令行是否有提供参数,并根据参数执行相应的动作:

#include 
int main(int argc, char *argv[])//argc是指传入参数的个数。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");
    }
}

应当指出的是,argv[0]是存储程序的名称,argv[1]是一个指向第一个命令行参数的指针,没有提供参数,argc将为1,否则,如果传递了一个参数,argc将被设置为2。

##argc是指传入参数的个数

多个命令行参数之间用空格分隔,但是如果参数本身带有空格,那么传递参数的时候应把参数放置在双引号==" "’’==内部。

让我们重新编写上面的实例,有一个空间,那么你可以通过这样的观点,把它们放在双引号或单引号""""。

让我们重新编写上面的实例,向程序传递一个放置在双引号内部的命令行参数:

#include 
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");
    }
}

C 排序算法

冒泡排序

冒泡排序(Bubble Sort)是一种简单的排序算法。

它重复走访过要排序的数列,一次比较两个元素,如果他们顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。

过程演示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eNO8y3pg-1596085324488)(/…/MY_POSTS/images/Bubble_sort_animation-1594304725771.gif)]

实例

#include 
void bubble_sort(int arr[], int len)
{
    int i,j,temp;
    for (i = 0; i < len -1; i++)
        for(j = 0; j < len -1 -i; j++)
            if(arr[j]>arr[j+1])
            {
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
}

int main()
{
    int arr[] = {22,34,3,32,82,55,89,50,37,5,74,33,2,109};
    int len = (int)sizeof(arr)/sizeof(*arr);
    bubble_sort(arr, len);
    int i;
    for (i = 0; i < len; i++)
        printf("%d\n", arr[i]);
    return 0;
}

选择排序

选择排序(Selection sort)是一个简单直接的排序算法。

它的工作原因如下。首先在未排序序列中找到最大(最小)元素,存放在排序序列的起始位置,然后,再从剩余未排序的元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n4Fabd9m-1596085324490)(/…/MY_POSTS/images/Selection_sort_animation-1594304725772.gif)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MeNeBtoo-1596085324492)(/…/MY_POSTS/images/Selection-Sort-Animation-1594304725773.gif)]

实例

#include
void swap(int *a, int *b)//交换两个参数的指针
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

void selection_sort(int arr[], int len)
{
    int i, j;
    for (i=0; i<len-1; i++)//对于0-100[101]而言,0-100[99]
    {
        int min = i;
        for(j=i+1; j<len; j++)
        {
            if(arr[j] < arr[min])
                min = j;
            swap(&arr[min],&arr[i]);//交换两者的地址
        }
    }
}

int main()
{
    int arr[] = {22,34,34,556,342,43,35};
    int len = (int)sizeof(arr)/sizeof(*arr);
    selection_sort(arr,len);
    int i;
    for(i = 0; i<len; i++)
        printf("%d\n",arr[i]);
    getchar();
    return 0;
}

插入排序

插入排序(英语:Insertion Sort)是一种简单直观的排序算法。

它的工作原理是通过构建有序序列,对于未排序的数据,在已排序序列宏从后面扫描,找到相应位置并插入。

插入排序在实现上,通常采用in-place排序(即只需用到 {\displaystyle O(1)} {\displaystyle O(1)}的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后

挪位,为最新元素提供插入空间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zFC3Xn8U-1596085324494)(/…/MY_POSTS/images/Insertion_sort_animation-1594304725773.gif)]

实例

void insertion_sort(int arr[], int len)
{
    int i,j,temp;
    for (i = 1; i<len; i++)
    {
        temp = arr[i];
        for (j=i; j>0 && arr[j-1]>temp; j--)//&&与 寻找i处前面比i大的数,将其变成i
            arr[j] = arr[j-1];
        arr[j] = temp;
    }
}

希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位

过程演示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k1nQtTNu-1596085324496)(/…/MY_POSTS/images/Sorting_shellsort_anim-1594304725774.gif)]

void shell_sort(int arr[], int len)
{
    int gap, i, j;
    int temp;
    for (gap = len >> 1; gap > 0; gap = gap >> 1)
        for (i = gap; i < len; i++) 
        {
            temp = arr[i];
            for (j = i - gap; j >= 0 && arr[j] > temp; j -= gap)
                arr[j + gap] = arr[j];
            arr[j + gap] = temp;
        }
}

问题快速排序

在区间中随机挑选一个元素作基准,将小于基准的元素放在基准之前,大于基准的元素放在基准之后,再分别对小数区与大数区进行排序。

过程演示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AYd4BwtL-1596085324498)(/…/MY_POSTS/images/Sorting_quicksort_anim-1594304725774.gif)]

typedef struct _Range 
{
    int start, end;
} Range;

Range new_Range(int s, int e) 
{
    Range r;
    r.start = s;
    r.end = e;
    return r;
}

void swap(int *x, int *y) 
{
    int t = *x;
    *x = *y;
    *y = t;
}
void quick_sort(int arr[], const int len) 
{
    if (len <= 0)
        return; // 避免len等於負值時引發段錯誤(Segment Fault)
    // r[]模擬列表,p為數量,r[p++]為push,r[--p]為pop且取得元素
    Range r[len];
    int p = 0;
    r[p++] = new_Range(0, len - 1);
    while (p) 
    {
        Range range = r[--p];
        if (range.start >= range.end)
            continue;
        int mid = arr[(range.start + range.end) / 2]; // 選取中間點為基準點
        int left = range.start, right = range.end;
        do
        {
            while (arr[left] < mid) ++left;   // 檢測基準點左側是否符合要求
            while (arr[right] > mid) --right; //檢測基準點右側是否符合要求
 
            if (left <= right)
            {
                swap(&arr[left],&arr[right]);
                left++;right--;               // 移動指針以繼續
            }
        } while (left <= right);
 
        if (range.start < right) r[p++] = new_Range(range.start, right);
        if (range.end > left) r[p++] = new_Range(left, range.end);
    }
}
void swap(int *x, int *y) 
{
    int t = *x;
    *x = *y;
    *y = t;
}
void quick_sort_recursive(int arr[], int start, int end) 
{
    if (start >= end)
        return;
    int mid = arr[end];
    int left = start, right = end - 1;
    while (left < right) 
    {
        while (arr[left] < mid && left < right)
            left++;
        while (arr[right] >= mid && left < right)
            right--;
        swap(&arr[left], &arr[right]);
    }
    if (arr[left] >= arr[end])
        swap(&arr[left], &arr[end]);
    else
        left++;
    if (left)
        quick_sort_recursive(arr, start, left - 1);
    quick_sort_recursive(arr, left + 1, end);
}
void quick_sort(int arr[], int len) 
{
    quick_sort_recursive(arr, 0, len - 1);
}

你可能感兴趣的:(C语言,c语言)