C 语言最初是用于系统开发工作,特别是组成操作系统的程序。由于 C 语言所产生的代码运行速度与汇编语言编写的代码运行速度几乎一样,所以采用 C 语言作为系统开发语言。使用 C 的实例:操作系统,语言编译器,汇编器,文本编辑器,打印机,网络驱动器,现代程序,数据库,语言解释器,实体工具
C11(也被称为C1X)指ISO标准ISO/IEC 9899:2011,是当前最新的C语言标准。在它之前的C语言标准为C99。
命令行gcc hello.c 生成可执行文件,键入可执行文件执行程序
数据类型
变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。
1.基本类型(算术类型):整数,浮点数
2.枚举类型(算术类型)
3.void
4.派生类型:指针,数组,结构,共用体,函数
数组类型和结构类型统称为聚合类型。函数的类型指的是函数返回值的类型。
sizeof(type)得到对象或类型的存储字节大小
float.h FLT_MIN FLT_MAX FLT_DIG(精度)
变量
程序可操作的存储区的名称。
不带初始化的定义:静态变量会被隐式初始化为 NULL(所有字节的值都是 0),其他所有变量的初始值是未定义的。
变量声明向编译器保证变量以指定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。
变量的声明有两种情况:
1、一种是需要建立存储空间的。例如:int a
在声明的时候就已经建立了存储空间。
2、另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。 例如:extern int a
其中变量 a 可以在别的文件中定义的。
除非有extern关键字,否则都是变量的定义。
左值Lvalues(指向内存位置,可被赋值) 右值Rvalues(存储在内存中某些地址的数值,不可被赋值)
常量 字面值
前缀0x 0X十六 0八
078非法
后缀(不分大小写)U无符号整数 L长整数
浮点常量314159E-5L
定义常量
1.#define 预处理器 #define identifier value(没有分号) e.g.#define LENGTH 10
2.const type variable = value;
C存储类
定义变量/函数的范围(可见性)和生命周期
auto
:局部变量默认的存储类,且只能用于修饰局部变量
register
:定义存储在寄存器中而非RAM中的局部变量
static
:指示编译器在程序的生命周期中保持局部变量的存在,不需要在每次它进入或离开作用时进行创建或销毁,使用static修饰的局部变量可以在函数调用之间保持局部变量的值。也可修饰全局变量
extern:有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。
e.g.
//第一个文件 main.c
#include <stdio.h>
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
//第二个文件 support.c
#include <stdio.h>
extern int count;
void write_extern(void)
{
printf("count is %d\n", count);
}
运算符
sizeof(a)
返回变量的大小
&a
返回变量的地址
*a
指向一个变量
?:
(不仅仅可被用作赋值)
Exp1 ? Exp2 : Exp3;
(num%2==0)?printf("偶数"):printf("奇数");
switch语句
switch(expression){
case constant-expression :
statement(s);
break; /* 可选的 */
case constant-expression :
statement(s);
break; /* 可选的 */
/* 您可以有任意数量的 case 语句 */
default : /* 可选的 */
statement(s);
}
expression 是一个常量表达式,必须是一个整型或枚举类型。
constant-expression 必须与 switch 中的变量具有相同的数据类型,且必须是一个常量或字面量。
两种向函数传递参数的方式
传值调用(默认),引用调用
e.g.
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 temp 赋值给 y */
return;
}
swap(&a, &b);
全局变量与局部变量在内存中的区别
全局变量保存在内存的全局存储区中,占用静态的存储单元;
局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。
当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动对其初始化
数组
所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
};
与int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
等同
数组名是一个指向数组中第一个元素的常量指针
传递数组给函数
三种声明方式使其接收指针
void myFunction(int *param)
void myFunction(int param[10])
void myFunction(int param[])
e.g.
声明double getAverage(int arr[], int size);
调用
int balance[5] = {1000, 2, 3, 17, 50};
avg = getAverage( balance, 5 ) ;
被调函数内的使用sum += arr[i];
从函数返回数组
->返回一个指向数组的指针
int * getRandom( )
{
static int r[10];
int i;
/* 设置种子 */
srand( (unsigned)time( NULL ) );
for ( i = 0; i < 10; ++i)
{
r[i] = rand();
printf( "r[%d] = %d\n", i, r[i]);
}
return r;
}
调用
int *p = getRandom();
for ( i = 0; i < 10; i++ )
{
printf( "*(p + %d) : %d\n", i, *(p + i));
}
enum(枚举)
声明枚举类型
enum 枚举名 {枚举元素1,枚举元素2,……};
e.g.
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。
定义枚举变量enum DAY day;
也可定义枚举类型的同时定义枚举变量
enum DAY(枚举名称可省)
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
day=WED; //->day=3
枚举类型可用于switch语句
整数不可以直接赋值给枚举类型要进行类型转换
int a=1;
enum DAY weekend=(enum DAY)a;
指针
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。
type *var-name;
int *ptr = NULL; //不指向任何一个可访问地址
if(ptr) //检查是否为空
指针的算数运算
假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数
执行ptr++
后ptr为1004
因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 个字节
对指针进行递减运算,即把值减去其数据类型的字节数
指针数组
int *ptr[MAX];
用一个指向字符的指针数组来存储一个字符串列表
const char *names[] = {
"Zara Ali",
"Hina Ali",
"Nuha Ali",
"Sara Ali",
};
//Value of names[1] = Hina Ali
指向指针的指针
int **var;
传递指针给函数
通过引用或地址传递参数,使传递的参数在调用函数中被改变。
从函数返回指针
C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量
因为局部变量是存储在内存的栈区内,当函数调用结束后,局部变量所占的内存地址便被释放了,因此当其函数执行完毕后,函数内的变量便不再拥有那个内存地址,所以不能返回其指针。
除非将其变量定义为 static 变量,static 变量的值存放在内存中的静态数据区,不会随着函数执行的结束而被清除,故能返回其地址。
函数指针
int max(int x, int y)
{
return x > y ? x : y;
}
声明:int (* p)(int, int) = & max; // &可以省略
使用:
/* 与直接调用函数等价,d = max(max(a, b), c) */
d = p(p(a, b), c);
回调函数
函数指针作为某个函数的参数
B函数执行时调用A函数
e.g.
#include <stdlib.h>
#include <stdio.h>
// 回调函数
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}
// 获取随机值
int getNextRandomValue(void)
{
return rand();
}
int main(void)
{
int myarray[10];
populate_array(myarray, 10, getNextRandomValue);
for(int i = 0; i < 10; i++) {
printf("%d ", myarray[i]);
}
printf("\n");
return 0;
}
C字符串
字符串实际上是使用null 字符 '\0'终止的一维字符数组
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
等价于
char greeting[] = "Hello";
对于第二个,不需要把 null 字符放在字符串常量的末尾。C 编译器会在初始化数组时,自动把 '\0' 放在字符串的末尾。
对于第一个如果不把null放在末尾的话会一直输出字符直至遇到某个内存位置上是null为止
printf("Greeting message: %s\n", greeting );
1 strcpy(s1, s2);
复制字符串 s2 到字符串 s1。
2 strcat(s1, s2);
连接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1);
返回字符串 s1 的长度。
4 strcmp(s1, s2);
如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5 strchr(s1, ch);
返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2);
返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。
C结构体
struct tag {
member-list
member-list
member-list
...
} variable-list ;
struct tag tag1,tag2;
也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明
e.g.
struct B; //对结构体B进行不完整声明
//结构体A中包含指向结构体B的指针
struct A
{
struct B *partner;
//other members;
};
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
struct A *partner;
//other members;
};
结构作为函数参数
void printBook( struct Books book );
指向结构的指针
struct Books *struct_pointer;
访问成员
struct_pointer->title;
位域
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。
所谓"位域"是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
典型的实例
用 1 位二进位存放一个开关量时,只有 0 和 1 两种状态。
读取外部文件格式——可以读取非标准的文件格式。例如:9 位的整数。
struct 位域结构名
{
位域列表(类型说明符 位域名: 位域长度)
};
e.g.
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;
一个位域存储在同一个字节中,如一个字节所剩空间不够存放另一位域时,则会从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
struct bs{
unsigned a:4;
unsigned :4; /* 空域 */
unsigned b:4; /* 从下一单元开始存放 */
unsigned c:4
}
由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。如果最大长度大于计算机的整数字长,一些编译器可能会允许域的内存重叠,另外一些编译器可能会把大于一个域的部分存储在下一个字中。
位域在本质上就是一种结构类型,不过其成员是按二进位分配的。
e.g.
main(){
struct bs{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.b=7; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
bit.c=15; /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
printf("%d,%d,%d\n",bit.a,bit.b,bit.c); /* 以整型量格式输出三个域的内容 */
pbit=&bit; /* 把位域变量 bit 的地址送给指针变量 pbit */
pbit->a=0; /* 用指针方式给位域 a 重新赋值,赋为 0 */
pbit->b&=3; /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
pbit->c|=1; /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c); /* 用指针方式输出了这三个域的值 */
}
共用体
不同数据类型使用相同的内存位置
定义一个带有多成员的共用体,但任何时候只能有一个成员带有值
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
e.g.
union Data
{
int i;
float f;
char str[20];
} data;
Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。
共用体占用的内存应足够存储共用体中最大的成员。
在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。
用.来访问成员
typedef
为类型取一个新名字
e.g.
typedef unsigned char BYTE;
typedef与#define
#define
是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
typedef
仅限于为类型定义符号名称,#define
不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。e.g. #define ONE 1
typedef
是由编译器执行解释的,#define
语句是由预编译器进行处理的。
输入&输出
C 语言把所有的设备都当作文件。所以设备(比如显示器)被处理的方式与文件相同。以下三个文件会在程序执行时自动打开,以便访问键盘和屏幕。
标准输入 stdin
键盘
标准输出 stdout
屏幕
标准错误 stderr
屏幕
文件指针是访问文件的方式
int getchar(void)
从屏幕读取下一个可用字符
int putchar(int c)
把字符输出到屏幕上
char *gets(char *s)
从stdin读取一行到s所指向的缓冲区,直到一个终止符或EOF
int puts(const char *s)
把字符串s和一个尾随的换行符写入stdout
````int scanf(const char *format, ...)函数从标准输入流 stdin 读取输入,并根据提供的 format 来浏览输入。
int printf(const char *format, ...) ```函数把输出写入到标准输出流 stdout ,并根据提供的格式产生输出。
e.g.
#include <stdio.h>
int main( ) {
char str[100];
int i;
printf( "Enter a value :");
scanf("%s %d", str, &i);
printf( "\nYou entered: %s %d ", str, i);
printf("\n");
return 0;
}
在读取字符串时,只要遇到一个空格,scanf() 就会停止读取,所以 "this is test" 对 scanf() 来说是三个字符串。
文件读写
打开文件
使用 fopen( )
函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。
FILE *fopen( const char * filename, const char * mode );
r 打开一个已有的文本文件,允许读取文件。
w 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。
a 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。
r+ 打开一个文本文件,允许读写文件。
w+ 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。
a+ 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。
如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
关闭文件
int fclose( FILE *fp );
如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。
写入文件
int fputc( int c, FILE *fp );
函数 fputc() 把参数 c 的字符值写入到 fp 所指向的输出流中。如果写入成功,它会返回写入的字符,如果发生错误,则会返回 EOF。
int fputs( const char *s, FILE *fp );
把一个以 null 结尾的字符串写入到流中,如果写入成功,它会返回一个非负值,如果发生错误,则会返回 EOF。
int fprintf(FILE *fp,const char *format, ...)
读取文件
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, ...)
函数来从文件中读取字符串,但是在遇到第一个空格字符时,它会停止读取。
二进制I/O函数
size_t fread(void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
size_t fwrite(const void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
这两个函数都是用于存储块的读写 - 通常是数组或结构体。
预处理器
C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。我们将把 C 预处理器(C Preprocessor)简写为 CPP。
所有的预处理器命令都是以井号(#)开头。它必须是第一个非空字符,为了增强可读性,预处理器指令应从第一列开始。
#define 定义宏
#include 包含一个源代码文件
#undef 取消已定义的宏
#ifdef 如果宏已经定义,则返回真
#ifndef 如果宏没有定义,则返回真
#if 如果给定条件为真,则编译下面代码
#else #if 的替代方案
#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中
预定义宏
ANSI C 定义了许多宏。在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏。
__DATE__
当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。
__TIME__
当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。
__FILE__
这会包含当前文件名,一个字符串常量。
__LINE__
这会包含当前行号,一个十进制常量。
__STDC__
当编译器以 ANSI 标准编译时,则定义为 1。
预处理器运算符
C 预处理器提供了下列的运算符来帮助您创建宏:
宏延续运算符(\)
一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。例如:
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
字符串常量化运算符(#)
在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。例如:
message_for(Carole, Debra); //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
defined() 运算符
预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定义过。如果指定的标识符已定义,则值为真(非零)。如果指定的标识符未定义,则值为假(零)。下面的实例演示了 defined() 运算符的用法:
#include <stdio.h>
#if !defined (MESSAGE)
#define MESSAGE "You wish!"
#endif
参数化的宏
#define square(x) ((x) * (x))
#define MAX(x,y) ((x) > (y) ? (x) : (y))
头文件
头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和编译器自带的头文件
建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件。
只引用一次头文件
如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:
#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif
这种结构就是通常所说的包装器#ifndef
。当再次引用头文件时,条件为假,因为 HEADER_FILE 已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它。
有条件引用
有时需要从多个不同的头文件中选择一个引用到程序中。例如,需要指定在不同的操作系统上使用的配置参数。您可以通过一系列条件来实现这点,如下:
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
但是如果头文件比较多的时候,这么做是很不妥当的,预处理器使用宏来定义头文件的名称。这就是所谓的有条件引用。它不是用头文件的名称作为 #include 的直接参数,您只需要使用宏名称代替即可:
#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H
C强制类型转换
(type_name) expression
常用的算术转换是隐式地把值强制转换为相同的类型。编译器首先执行整数提升,如果操作数类型不同,则它们会被转换为下列层次中出现的最高层次的类型:
char、short->int->unsigned int->long->unsigned long->long long->unsigned long long->float->double->long double
错误处理
错误代码errno
,全局变量,应在程序初始化时设置为0表示程序中没有错误
C 语言提供了 perror()
和 strerror()
函数来显示与 errno 相关的文本消息。
perror()
函数显示您传给它的字符串,后跟一个冒号、一个空格和当前 errno 值的文本表示形式。
strerror()
函数,返回一个指针,指针指向当前 errno 值的文本表示形式。
让我们来模拟一种错误情况,尝试打开一个不存在的文件。您可以使用多种方式来输出错误消息,在这里我们使用函数来演示用法。另外有一点需要注意,您应该使用 stderr 文件流来输出所有的错误。
e.g.
#include <stdio.h>
#include <errno.h>
#include <string.h>
extern int errno ;
int main ()
{
FILE * pf;
int errnum;
pf = fopen ("unexist.txt", "rb");
if (pf == NULL)
{
errnum = errno;
fprintf(stderr, "错误号: %d\n", errno);
perror("通过 perror 输出错误");
fprintf(stderr, "打开文件错误: %s\n", strerror( errnum ));
}
else
{
fclose (pf);
}
return 0;
}
错误号: 2
通过 perror 输出错误: No such file or directory
打开文件错误: No such file or directory
程序退出状态
通常情况下,程序成功执行完一个操作正常退出的时候会带有值 EXIT_SUCCESS
。在这里,EXIT_SUCCESS
是宏,它被定义为 0。
如果程序中存在一种错误情况,当您退出程序时,会带有状态值 EXIT_FAILURE
,被定义为 -1。
exit(EXIT_FAILURE);
函数带有可变数量的参数
int func(int, ... )
{
.
.
.
}
int main()
{
func(2, 2, 3);
func(3, 2, 3, 4);
}
省略号之前的那个参数是 int,代表了要传递的可变参数的总数。为了使用这个功能,您需要使用 stdarg.h
头文件,该文件提供了实现可变参数功能的函数和宏。
定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
在函数定义中创建一个 va_list
类型变量,该类型是在 stdarg.h 头文件中定义的。
使用 int 参数
和 va_start
宏来初始化va_list
变量为一个参数列表。
宏 va_start 是在 stdarg.h 头文件中定义的。
使用 va_arg
宏和va_list
变量来访问参数列表中的每个项。
使用宏va_end
来清理赋予 va_list
变量的内存。
e.g.
#include <stdio.h>
#include <stdarg.h>
double average(int num,...)
{
va_list valist;
double sum = 0.0;
int i;
/* 为 num 个参数初始化 valist */
va_start(valist, num);
/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);
return sum/num;
}
int main()
{
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}
动态内存管理
<stdlib.h>
1 void *calloc(int num, int size);
在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。
2 void free(void *address);
该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
3 void *malloc(int num);
在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
4 void *realloc(void *address, int newsize);
该函数重新分配内存,把内存扩展到 newsize。
void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。
e.g.
char *description;
/* 动态分配内存 */
description = (char *)malloc( 200 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy( description, "Zara ali a DPS student in class 10th");
}
当程序退出时,操作系统会自动释放所有分配给程序的内存,但是,建议您在不需要内存时,都应该调用函数 free() 来释放内存。
或者,您可以通过调用函数 realloc() 来增加或减少已分配的内存块的大小。
e.g.
/* 动态分配内存 */
description = (char *)malloc( 30 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy( description, "Zara ali a DPS student.");
}
/* 假设您想要存储更大的描述信息 */
description = (char *) realloc( description, 100 * sizeof(char) );
if( description == NULL )
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcat( description, "She is in class 10th");
}
/* 使用 free() 函数释放内存 */
free(description);
命令行参数
执行程序时,可以从命令行传值给 C 程序。这些值被称为命令行参数,它们对程序很重要,特别是当您想从外部控制程序,而不是在代码内对这些值进行硬编码时,就显得尤为重要了。
命令行参数是使用 main() 函数参数来处理的,其中,argc 是指传入参数的个数,argv[] 是一个指针数组,指向传递给程序的每个参数。
e.g.
#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");
}
}
应当指出的是,argv[0] 存储程序的名称,argv[1] 是一个指向第一个命令行参数的指针,*argv[n] 是最后一个参数。如果没有提供任何参数,argc 将为 1,否则,如果传递了一个参数,argc 将被设置为 2。
多个命令行参数之间用空格分隔,但是如果参数本身带有空格,那么传递参数的时候应把参数放置在双引号 "" 或单引号 '' 内部。
网友评论