C语言高级编程

时间:2019-03-15
本文章向大家介绍C语言高级编程,主要包括C语言高级编程使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、gcc和gdb

GNU工具

编译工具:把一个源程序编译为一个可执行程序

调试工具:能对执行程序进行源码或汇编级调试

软件工程工具:用于协助多人开发或大型软件项目的管理,如make、CVS、Subvision

其他工具:用于把多个目标文件链接成可执行文件的链接器,或者用作格式转换的工具。

1、GCC编译器

全称为GNU CC ,GNU项目中符合ANSI C标准的编译系统

编译如C、C++、Object C、Java、Fortran、Pascal、Modula-3和Ada等多种语言

GCC是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%

一个交叉平台编译器,适合在嵌入式领域的开发编译

a、gcc所支持后缀名解释

b、编译器的主要组件

c、GCC的基本用法和选项

Gcc最基本的用法是∶gcc [options] [filenames]

-c,只编译,不连接成为可执行文件,编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件,通常用于编译不包含主程序的子程序文件。

-o output_filename,确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的可执行文件a.out。

-g,产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项。

-O,对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是,编译、连接的速度就相应地要慢一些。

-O2,比-O更好的优化编译、连接,当然整个编译、连接过程会更慢。

-I dirname,将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数。

-L dirname,将dirname所指出的目录加入到程序函数档案库文件的目录列表中,是在链接过程中使用的参数。

d、GCC编译过程

e、“hello”的演变历程

2、GDB调试工具

a、Gdb调试流程

二、条件编译

编译器根据条件的真假决定是否编译相关的代码。

常见的条件编译有两种方法:

a、根据宏是否定义,其语法如下:

#ifdef <macro>

……

#else

……

#endif

实例:

#define _DEBUG_

#ifdef _DEBUG_

printf(“The macro _DEBUG_ is defined\n”);

#else

printf(“The macro _DEBUG_ is not defined\n”);

#endif

b、根据宏的值,其语法如下:

#if <macro>

……

#else

……

#endif

实例:

#define _DEBUG_ 1

#if _DEBUG_

printf(“The macro _DEBUG_ is defined\n”);

#else

printf(“The macro _DEBUG_ is not defined\n”);

#endif

三、结构体与共用体

1、结构体

a、简述

在实际的处理对象中,有许多信息是由多个不同类型的数据组合在一起进行描述,而且这些不同类型的数据是互相联系组成了一个有机的整体。此时,就要用到一种新的构造类型数据——结构体(structure),简称结构。结构体的使用为处理复杂的数据结构(如动态数据结构等)提供了有效的手段,而且,它们为函数间传递不同类型的数据提供了方便。

b、概念

结构体是用户自定义的新数据类型,在结构体中可以包含若干个不同数据类型和不同意义的数据项(当然也可以相同),从而使这些数据项组合起来反映某一个信息。例如,可以定义一个职工worker结构体,在这个结构体中包括职工编号、姓名、性别、年龄、工资、家庭住址、联系电话。这样就可以用一个结构体数据类型的变量来存放某个职工的所有相关信息。并且,用户自定义的数据类型worker也可以与int、double等基本数据类型一样,用来作为定义其他变量的数据类型。

c、定义

定义一个结构体类型的一般形式为:

struct 结构体名

{

数据类型   成员名1;

数据类型   成员名2;

数据类型   成员名n;

};

d、结构体类型变量的定义方法

先定义结构体类型再定义变量名,这是C语言中定义结构体类型变量最常见的方式,这种形式的定义的一般形式为:

struct 结构体名

{

成员列表;

};

struct 结构体名 变量名;

在定义类型的同时定义变量,这种形式的定义的一般形式为:

struct 结构体名

{

成员列表;

}变量名;

直接定义结构类型变量,其一般形式为:

struct //没有结构体名

{

成员列表;

}变量名;

e、结构体的大小

一个结构体变量占用内存的实际大小,也可以利用sizeof求出。它的运算表达式为:sizeof(运算量)//求出给定的运算量占用内存空间的字节数;其中运算量可以是变量、数组或结构体变量,可以是数据类型的名称。

f、注意事项

(1)不能将一个结构体类型变量作为一个整体加以引用,而只能对结构体类型变量中的各个成员分别引用。

(2)如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级成员。只能对最低级的成员进行赋值或存取以及运算。例如,可以这样访问各成员:

worker1.age

worker1.name

worker1.birthday.year

worker1.birthday.month

worker1.birthday.day

注意:不能用worker1.birthday来访问worker1变量中的成员birthday,因为birthday本身是一个结构体变量。

(3)对成员变量可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。例如:

worker2.age=worker1.age;

sum=worker1.age+worker2.age;

worker1.age++;

(4)在数组中,数组是不能彼此赋值的,而结构体类型变量可以相互赋值。

在C程序中,同一结构体类型的结构体变量之间允许相互赋值,而不同结构体类型的结构体变量之间不允许相互赋值,即使两者包含有同样的成员。

2、结构体数组

具有相同结构体类型的结构体变量也可以组成数组,称它们为结构体数组。结构体数组的每一个数组元素都是结构体类型的数据,它们都分别包括各个成员(分量)项。

3、结构体指针

可以设定一个指针变量用来指向一个结构体变量。此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。

结构体指针与前面介绍的各种指针变量在特性和方法上是相同的。与前述相同,在程序中结构体指针也是通过访问目标运算“*”访问它的对象。结构体指针在程序中的一般定义形式为:

struct 结构体名 *结构指针名;其中的结构体名必须是已经定义过的结构体类型。

当表示指针变量p所指向的结构体变量中的成员时,“(*结构体指针名).成员名”这种表示形式总是需要使用圆括号,显得很不简炼。因此,对于结构体指针指向的结构体成员项,给出了另外一种简洁的表示方法,如下表示:结构体指针名->成员名;它与前一种表示方法在意义上是完全等价的。例如,结构体指针p指向的结构体变量中的成员name可以表示如下:(*p).name 或 p->name

4、共用体

a、共用体的概念

在C语言中,不同数据类型的数据可以使用共同的存储区域,这种数据构造类型称为共用体,简称共用,又称联合体。共用体在定义、说明和使用形式上与结构体相似。两者本质上的不同仅在于使用内存的方式上。 定义一个共用体类型的一般形式为:

union 共用体名

{

成员表列;

};

例如:

union gy

{

int i;

char c;

float f;

};

这里定义了一个共用体类型union gy,它由三个成员组成,这三个成员在内存中使用共同的存储空间。由于共用体中各成员的数据长度往往不同,所以共用体变量在存储时总是按其成员中数据长度最大的成员占用内存空间。在这一点上共用体与结构体不同,结构体类型变量在存储时总是按各成员的数据长度之和占用内存空间。

5、typedef

在C语言中,允许使用关键字typedef定义新的数据类型

其语法如下:

typedef <已有数据类型> <新数据类型>;

如:

typedef int INTEGER;

这里新定义了数据类型INTEGER, 其等价于int

INTEGER i; <==> int i;

在C语言中经常在定义结构体类型时使用typedef,例如

typedef struct _node_

{

int data;

struct _node_ *next;

} listnode, *linklist;

这里定义了两个新的数据类型listnode和linklist。其中listnode等价于数据类型struct _node_ 而 linklist等价于struct _node_ *。

四、内存管理

C/C++定义了4个内存区间:

代码区/全局变量与静态变量区/局部变量区即栈区/动态存储区即堆区。

1、静态存储分配

通常定义变量,编译器在编译时都可以根据该变量的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。

2、在栈上创建

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

3、动态存储分配

有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为动态存储分配。

所有动态存储分配都在堆区中进行。

从堆上分配,亦称动态内存分配。程序在运行的时候用malloc申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

注意:当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源;堆区是不会自动在分配时做初始化的(包括清零),所以必须用初始化式(initializer)来显式初始化。

4、malloc/free

void * malloc(size_t num)

void free(void *p)

malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。

malloc申请到的是一块连续的内存,有时可能会比所申请的空间大。其有时会申请不到内存,返回NULL。

malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。

如果free的参数是NULL的话,没有任何效果。

释放一块内存中的一部分是不被允许的。

a、删除一个指针p

free(p);,实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p就成了空悬指针;

b、动态分配失败

返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败;

c、malloc与free是配对使用的

free只能释放堆空间。如果malloc返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存malloc返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间;

d、动态分配的变量或对象的生命期

无名对象的生命期并不依赖于建立它的作用域,比如在函数中建立的动态对象在函数返回后仍可使用。我们也称堆空间为自由空间(free store)就是这个原因。但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错。

e、野指针

不是NULL指针,是指向“垃圾”内存的指针。“野指针”是很危险的。 “野指针”的成因主要有两种:指针变量没有被初始化;指针p被free之后,没有置为NULL,让人误以为p是个合法的指针。指针操作超越了变量的作用范围。这种情况让人防不胜防。