600 likes | 727 Views
C 语言补充内容. 内容. 命令行 参数 可变长的实参列表 输入输出 重定向 动态内存分配 c 预处理 gcc 编译参数 编程中的注意事项 编程 举例:事务处理程序. 内容. 命令行 参数 可变长的实参列表 输入输出 重定向 动态内存分配 c 预处理 gcc 编译参数 编程中的注意事项 编程 举例:事务处理程序. 命令行 参数. 以命令行运行程序时所带的参数 int main( int argc , char* argv [ ]); int main( int argc , char ** argv );
E N D
内容 • 命令行参数 • 可变长的实参列表 • 输入输出重定向 • 动态内存分配 • c预处理 • gcc编译参数 • 编程中的注意事项 • 编程举例:事务处理程序
内容 • 命令行参数 • 可变长的实参列表 • 输入输出重定向 • 动态内存分配 • c预处理 • gcc编译参数 • 编程中的注意事项 • 编程举例:事务处理程序
命令行参数 • 以命令行运行程序时所带的参数 • int main( intargc, char*argv[ ]); • int main( intargc, char **argv); • 几乎所有的实际应用程序都需要处理命令行参数 • 举例: • notepad.exe a.txt • 7z
命令行参数(2) • int main( intargc, int *argv[ ]); • argc:正整数,表示命令行参数的个数。注意:可执行文件名本身也是一个参数 • argv:指针数组,数组中的元素分别指向一个字符串,即命令行参数的各个字段 • 例如在命令行终端下输入: notepad.exe a.txt argc的值是2,argv[0], argv[1]的值分别是“notepad.exe”和“a.txt”
下面的程序将一个文件中的内容逐字符的复制到另一个文件中下面的程序将一个文件中的内容逐字符的复制到另一个文件中 假如这个程序的可执行文件是mycopy,运行:mycopy input.txt output.txt /* Fig. 14.3: fig14_03.c Using command-line arguments */ #include <stdio.h> int main( intargc, char *argv[] ) { FILE *inFilePtr; /* input file pointer */ FILE *outFilePtr; /* output file pointer */ int c; /* define c to hold characters input by user */ /* check number of command-line arguments */ if ( argc != 3 ) { printf( "Usage: mycopyinfileoutfile\n" ); } /* end if */ else { /* if input file can be opened */ if ( ( inFilePtr = fopen( argv[ 1 ], "r" ) ) != NULL ) { /* if output file can be opened */ if ( ( outFilePtr = fopen( argv[ 2 ], "w" ) ) != NULL ) {
/* read and output characters */ while ( ( c = fgetc( inFilePtr ) ) != EOF ) { fputc( c, outFilePtr ); } /* end while */ } /* end if */ else { /* output file could not be opened */ printf( "File \"%s\" could not be opened\n", argv[ 2 ] ); } /* end else */ } /* end if */ else { /* input file could not be opened */ printf( "File \"%s\" could not be opened\n", argv[ 1 ] ); } /* end else */ } /* end else */ return 0; /* indicates successful termination */ } /* end main */
内容 • 命令行参数 • 可变长的实参列表 • 输入输出重定向 • 动态内存分配 • c预处理 • gcc编译参数 • 编程中的注意事项 • 编程举例:事务处理程序
可变长的实参列表 • 函数接受的实参个数可以是不确定的 • 例:intprintf( const char * format, …); • 其中…表示这个函数可以接受可变数目的实参,省略号必须放在形参列表的末尾 • include <stdarg.h> • stdarg.h中的宏和定义 • va_list: 为了访问可变长实参列表中的实参必须声明一个类型为va_list的对象 • va_start: 在一个可变长实参列表中的实参被访问前,先要调用这个宏。其功能:初始化用va_list声明的对象,以便让宏va_arg和va_end来使用 • va_arg: 宏,展开成一个表示可变长实参列表中下一个实参的值和类型的表达式。 • va_end: 正常返回
/* Fig. 14.2: fig14_02.c Using variable-length argument lists */ #include <stdio.h> #include <stdarg.h> double average( inti, ... ); /* prototype */ int main( void ) { double w = 37.5; double x = 22.5; double y = 1.7; double z = 10.2; printf( "%s%.1f\n%s%.1f\n%s%.1f\n%s%.1f\n\n", "w = ", w, "x = ", x, "y = ", y, "z = ", z ); printf( "%s%.3f\n%s%.3f\n%s%.3f\n", "The average of w and x is ", average( 2, w, x ), "The average of w, x, and y is ", average( 3, w, x, y ), "The average of w, x, y, and z is ", average( 4, w, x, y, z ) ); return 0; /* indicates successful termination */ } /* end main */
/* calculate average */ double average( inti, ... ) { double total = 0; /* initialize total */ int j; /* counter for selecting arguments */ va_listap; /* stores information needed by va_start and va_end */ va_start( ap, i ); /* initializes the va_list object */ /* process variable length argument list */ for ( j = 1; j <= i; j++ ) { total += va_arg( ap, double ); } /* end for */ va_end( ap ); /* clean up variable-length argument list */ return total / i; /* calculate average */ } /* end function average */
内容 • 命令行参数 • 可变长的实参列表 • 输入输出重定向 • 动态内存分配 • c预处理 • gcc编译参数 • 编程中的注意事项 • 编程举例:事务处理程序
输入输出重定向 • 对于标准输入输出的程序允许将输入重定向为从一个文件中输入,或者将输出重定向到一个文件中输出 • > : 输出重定向符 • < : 输入重定向符 • | : 管道
内容 • 命令行参数 • 可变长的实参列表 • 输入输出重定向 • 动态内存分配 • c预处理 • gcc编译参数 • 编程中的注意事项 • 编程举例:事务处理程序
动态内存分配 • 静态内存分配 • 在编译时完成分配,自动释放——方便、效率高 • 栈空间,大小有限制(默认情况下,windows系统1M,Linux下8M) • 动态内存分配 • 数据结构的存储空间不是在程序运行前事先分好,而是在程序运行中根据需要临时分配 • 程序员自己申请并指明大小,自己释放——灵活 • 堆空间,空间大小几乎没有什么限制(只受到计算平台存储空间资源和操作系统对存储空间资源分配策略的限制)
c/c++程序内存分配 • 栈(stack) • 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。程序结束时由编译器自动释放。 • 堆(heap) • 在内存开辟另一块存储区域。一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事。 • 全局区(静态区)(static) • 编译器编译时即分配内存。全局变量和静态变量的存储是放在一块的,初始化的 全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 • 程序结束后由系统释放 • 文字常量区 • 程序代码区
堆栈实例代码比较 • 实例: • #include <stdlib.h> #include <string.h> • int a = 0; //全局初始化区 • char *p1; //全局未初始化区 • void main(){ • int b=1; // 栈 • char s[] = "abc"; //栈 • char *p2; //栈 • char *p3 = "123456"; //"123456\0"在常量区,p3在栈上。 • static int c =0; //全局(静态)初始化区 • p1 = (char *)malloc(10); • p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。 • strcpy(p1, “123456”); //123456\0放在常量区,编译器可能会将它与p3 //所指向的"123456"优化成一个地方 • system("pause"); • }
动态内存分配 • malloc函数和free函数 • C • void * malloc( size_tsize); • int *gradeArray = malloc(sizeof(int)); • void free( void * ptr ); • free( gradeArray); • new/delete • C++ • 运算符 • 调用类的构造函数(new)或者析构函数(delete) int *gradeArray = new int [10]; delete [ ] gradeArray;
堆栈的不同 • 堆和栈的主要区别由以下几点:1、管理方式不同;2、空间大小不同;3、能否产生碎片不同;4、生长方向不同;5、分配方式不同;6、分配效率不同;
堆栈的不同 • 管理方式不同; • 管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。 • 空间大小不同; • 空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M。当然,这个值可以修改。
堆栈的不同 • 能否产生碎片不同; • 碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构。
堆栈的不同 • 生长方向不同; • 生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。 • 分配方式不同; • 分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由malloc函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
堆栈的不同 • 分配效率不同; • 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
函数调用的栈操作 • 寄存器ebp用做帧指针(frame pointer)esp用作栈指针(stack pointer) • 函数调用操作包括从一块代码到另一块代码之间的双向数据传递和执行控制转移。 • 数据传递通过函数参数和返回值来进行。 • 在进入函数时为函数的局部变量分配存储空间,并且在退出函数时收回这部分空间
函数调用的栈操作 • void swap(int * a, int *b) {int c; c = *a; *a = *b; *b = c; }int main(){int a, b; a = 16; b = 32; swap(&a, &b); return (a - b);}
内容 • 命令行参数 • 可变长的实参列表 • 输入输出重定向 • 动态内存分配 • c预处理 • gcc编译参数 • 编程中的注意事项 • 编程举例:事务处理程序
C预处理 • #include预处理命令 • 文件包含 • #define预处理命令 • 符号常量和宏 • 条件编译 • #error和#pragma预处理命令 • assert
#include 文件 • #include“文件名“ 或 #include <文件名> 文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件。 • 尖括号与双引号 差别:目录查找次序不同 • 一个include包含一个文件 • 允许嵌套包含
条件编译 • #define符号常量和宏 #undef
条件编译 • 条件编译的三种形式 如果标识符已被 #define命令定义过则对程序段1进行编译;否则对程序段2进行编译。 ,如果标识符未被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译。 如常量表达式的值为真(非0),则对程序段1 进行编译,否则对程序段2进行编译。
条件编译 • #ifdef /#else/ #endif • #define NUM okmain(){ps=(structstu*)malloc(sizeof(structstu));ps->num=102;ps->name="Zhang ping";ps->sex='M';ps->score=62.5; #ifdef NUMprintf("Number=%d\nScore=%f\n",ps->num,ps->score); #elseprintf("Name=%s\nSex=%c\n",ps->name,ps->sex); #endif free(ps);} structstu{int num;char *name;char sex;float score;} *ps;
条件编译 • #ifndef /#define/ #endif • 避免头文件的重定义 #ifndef _TEST_H #define _TEST_H//一般是文件名的大写头文件结尾写上一行:#endif 这样一个工程文件里同时包含两个test.h时,就不会出现重定义的错误了。
条件编译 • #ifdef /#else/ #endif判断表达式真假的条件编译 #define R 1main(){ float c,r,s;printf ("input a number: ");scanf("%f",&c); #if R r=3.14159*c*c;printf("area of round is: %f\n",r); #else s=c*c;printf("area of square is: %f\n",s); #endif}
预处理命令 • #error • 主要的作用是在编译的时候输出编译错误信息token-sequence,从方便程序员检查程序中出现的错误。 该指令用于程序的调试, 当编译中遇到#error指令就停止编译。 #define CONST_NAME1 "CONST_NAME1"printf("%s\n",CONST_NAME1);#undef CONST_NAME1#ifndef CONST_NAME1#error No defined Constant Symbol CONST_NAME1#endif
预处理命令 • #pragma • 主要的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。 其格式一般为: #pragmapara 其中para为参数, 常用参数组合: #pragma message :编译信息输出窗口中输出相应的信息。 例如 #pragma message(“消息文本”)
预处理命令 • 当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86 这个宏可以用下面的方法 #ifdef _X86 #Pragma message(“_X86 macro activated!”) #endif • 当我们定义了_X86 这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_X86 macro activated!”。
预处理命令 • #pragmacode_seg #pragmacode_seg( ["section-name"[,"section-class"] ] ) 它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。 • #pragma once (比较常用) 只要在头文件的最开始加入这条指令就能够保证头文件被编译一次。
预处理命令 • 其他pragma的参数 • #pragmahdrstop • #pragma once • #pragma resource • #pragma warning
assert宏 断言 • void assert( int expression) • assert宏的原型定义在<assert.h>中,其先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。 • 其优势是利于调试, 缺点是频繁的调用会极大的影响程序的性能,增加额外的开销。 • 调试结束后,可以通过在包含#include <assert.h>的语句之前插入 #define NDEBUG 来禁用assert调用。 • #include <stdio.h>#define NDEBUG#include <assert.h>
assert()使用实例 #include <stdio.h>#include <assert.h>#include <stdlib.h> int main( void ){FILE *fp;fp = fopen( “test.txt”, “w” );//文件可创建,打开成功assert( fp ); //所以这里不会出错fclose( fp );fp = fopen( “noexitfile.txt”, “r” );//文件不存在,打开失败assert( fp ); //所以这里出错fclose( fp ); //程序永远都执行不到这里来 return 0;} 运行后,错误提示为:badptr.c:14: main: Assertion `fp' failed.
断言assert的用法 • assert用法 • 在函数开始处检验传入参数的合法性; • intresetBufferSize(intnNewSize){ assert(nNewSize >= 0);assert(nNewSize <= MAX_BUFFER_SIZE); } • 每个assert只检验一个条件,多个条件分别断言; • assert(nOffset>=0 &&nOffset+nSize <=m_nInfomationSize); 劣 • assert(nOffset >= 0);assert(nOffset+nSize <= m_nInfomationSize); 优 • 断言内不可使用改变环境的语句; • assert(i++ < 100);劣-调试结束,取消断言后程序出错
断言定义文件 #include "cvidef.h" #include "cvirte.h" #ifndef _ASSERT_H_ #define _ASSERT_H_ #ifdef __cplusplus extern "C" { #endif #undef assert #ifdef NDEBUG #define assert(exp) ((void) 0) #else void CVIANSI _assert(char *, char *, int); #define assert(exp) ((exp) ? (void) 0 : _assert(#exp, __FILE__, __LINE__)) #endif #ifdef __cplusplus } #endif #endif /* _ASSERT_H_ */
内容 • 命令行参数 • 可变长的实参列表 • 输入输出重定向 • 动态内存分配 • c预处理 • gcc编译器 • 编程中的注意事项 • 编程举例:事务处理程序
gcc编译器 • C/C++程序的编译运行 • gcc/g++是GNC的开源编译器,用来对c/c++程序进行编译、调试 • 后缀为.c的程序,gcc当成c程序,g++当成c++程序;而后缀为.cpp的程序两者都当成c++程序 //hello.c #include<stdio.h> int main() { printf("hello world\n"); return 0; }
gcc编译过程 • 预处理 • 对源代码文件中的文件包含、预编译语句进行分析 • gcc -o hello.i-E hello.c • 编译及优化 • 将预处理文件进行汇编,生成汇编语言程序 • gcc -o hello.s -S hello.i • 汇编 • 将汇编的代码进一步进行处理,生成相应的目标文件,以.o为扩展名 • gcc -o hello.o -c hello.s • 链接 • 将生成的目标文件与其他目标文件(或库文件)连接成可执行的二进制代码文件 • gcc -o hello hello.o gcchello.c–o hello
多个文件的编译链接 • 对于一个程序的多个源文件进行编译连接时,可以使用如下格式: • gcc –o test first.csecond.cthird.c • 该命令将同时编译3个源文件,将它们连接成一个可执行文件,名为test
常用编译选项(1) • -c • 仅把源程序编译为目标代码而不做链接 • 不生成最终的可执行程序,只生成一个与源程序文件名相同的以.o为后缀的目标文件。 • -o<file> • 将编译结果写入文件<file>中 • -v • Gcc的版本信息 • -x language • 强制编译器指定的语言编译器来编译某个源程序,例如gcc -x c++test.c • -I<DIR> • 库依赖选项,指定库及头文件路径 • l<library> • 在函数库<library>中查找需要链接的函数 • -L<DIR> • 将目录<dir>加入到搜索链接函数库的目录集合中
常用编译选项(2) • -w • 禁止输出警告信息 • -Wall • 在标准输出上显示所有的警告信息 • -O 或-O<n> • 指定编译优化级别,<n>可以为1,2,3和s,-O等于-O1 • -g • 生成调试辅助信息,以便使用GDB等调试工具进行调试 • -pg • 加入运行剖面生成代码,以便生成可以被gprof解读的程序运行剖面数据
gcc实例 Gcc -o factorial main.cfactorial.c /factorial 5 Factorial of 5 is 120.
内容 • 命令行参数 • 可变长的实参列表 • 输入输出重定向 • 动态内存分配 • c预处理 • gcc编译参数 • 编程中的注意事项 • 编程举例:事务处理程序