C语言Word文档格式.docx
《C语言Word文档格式.docx》由会员分享,可在线阅读,更多相关《C语言Word文档格式.docx(18页珍藏版)》请在冰豆网上搜索。
从以上分析可以看出,把局部变量改变为静态变量后是改变了他的存储方式,即改变了他的生存期。
把全局变量改变为静态变量后是改变了他的作用域,限制了他的使用范围,因此static这个说明符在不同的地方起的作用是不同的。
TIPS:
1、若全局变量仅在单个文件中访问,则可以讲这个变量修改为静态全局变量。
2、若全局变量仅在单个函数中使用,则可以将这个变量修改为该函数的静态局部变量。
3、全局变量、静态局部变量、静态全局变量都存放在静态数据存储区。
4、函数中必须要使用static变量的情况:
当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。
Stack/heap/static(存储区)
一、预备知识—程序的内存分配
一个由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)—
由编译器自动分配释放
,存放函数的参数值,局部变量的值等。
其
操作方式类似于数据结构中的栈。
2、堆区(heap)
—
一般由程序员分配释放,
若程序员不释放,程序结束时可能由OS回
收
。
注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的
全局变量和静态变量在一块区域,
未初始化的全局变量和未初始化的静态变量在相邻的另
一块区域。
-
程序结束后由系统释放。
4、文字常量区
—常量字符串就是放在这里的。
程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int
a
=
0;
全局初始化区
char
*p1;
全局未初始化区
main()
{
b;
栈
s[]
"
abc"
;
*p2;
*p3
123456"
123456/0在常量区,p3在栈上。
static
c
=0;
全局(静态)初始化区
p1
(char
*)malloc(10);
p2
*)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1,
);
123456/0放在常量区,编译器可能会将它与p3所指向的"
优化成一个地方。
}
内存释放之后
既然使用free函数之后指针变量p本身保存的地址并没有改变,那我们就需要重新把p的值变为NULL:
?
1
p=NULL;
这个NULL就是我们前面所说的“栓野狗的链子”。
如果你不栓起来迟早会出问题的。
比如:
在free(p)之后,你用if(NULL!
=p)这样的校验语句还能起作用吗?
例如:
2
3
4
5
6
7
8
9
char*p=(char*)malloc(100);
strcpy(p,“hello”);
free(p);
/*p所指的内存被释放,但是p所指的地址仍然不变*/
⋯
if(NULL!
=p)
{
/*没有起到防错作用*/
strcpy(p,“world”);
/*出错*/
}
释放完块内存之后,没有把指针置NULL,这个指针就成为了“野指针”,也有书叫“悬垂指针”。
这是很危险的,而且也是经常出错的地方。
所以一定要记住一条:
free完之后,一定要给指针置NULL。
realloc申请内存
1.分配内存空间函数malloc
调用形式:
(类型说明符*)malloc(size)功能:
在内存的动态存储区中分配一块长度为"
size"
字节的连续区域。
函数的返回值为该区域的首地址。
“类型说明符”表示把该区域用于何种数据类型。
(类型说明符*)表示把返回值强制转换为该类型指针。
“size”是一个无符号数。
pc=(char*)malloc(100);
表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc。
2.分配内存空间函数calloc
calloc也用于分配内存空间。
调用形式:
(类型说明符*)calloc(n,size)功能:
在内存动态存储区中分配n块长度为“size”字节的连续区域。
(类型说明符*)用于强制类型转换。
calloc函数与malloc函数的区别仅在于一次可以分配n块区域。
ps=(struetstu*)calloc(2,sizeof(structstu));
其中的sizeof(structstu)是求stu的结构长度。
因此该语句的意思是:
按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。
realloc(void*__ptr,size_t__size):
更改已经配置的内存空间,即更改由malloc()函数分配的内存空间的大小。
如果将分配的内存减少,realloc仅仅是改变索引的信息。
如果是将分配的内存扩大,则有以下情况:
1)如果当前内存段后面有需要的内存空间,则直接扩展这段内存空间,realloc()将返回原指针。
2)如果当前内存段后面的空闲字节不够,那么就使用堆中的第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,返回新的内存块位置。
3)如果申请失败,将返回NULL,此时,原来的指针仍然有效
Free释放内存
5.内存已经被释放了,但是继续通过指针来使用
只是释放了内存的使用权,内存仍可通过指针来调用
这里一般有三种情况:
第一种:
就是上面所说的,free(p)之后,继续通过p指针来访问内存。
解决的办法就是给p置NULL。
第二种:
函数返回栈内存。
这是初学者最容易犯的错误。
比如在函数内部定义了一个数组,却用return语句返回指向该数组的指针。
解决的办法就是弄明白栈上变量的生命周期。
链表
#include<
stdio.h>
string.h>
stdlib.h>
typedefstructDNode
{
inti;
intdata;
structDNode*prior;
structDNode*next;
}DNode,*DoubleNode;
structDNode*Listinit(structDNode*top)
top=(structDNode*)malloc(sizeof(DNode));
top->
prior=NULL;
next=NULL;
i=1;
printf("
%d$\n"
top->
i);
returntop;
voidListadd(structDNode*head)
structDNode*top;
intAdd_Data;
while(head->
next!
=NULL)
head=head->
next;
}
pleaseinputdata_num:
"
scanf("
%d"
&
Add_Data);
head->
next=top;
prior=head;
data=Add_Data;
data=%d\n"
data);
%d:
#\n"
head->
i=head->
i+1;
\n"
voidListdel(structDNode*head)
intnum;
璇疯緭鍏ラ渶瑕佸垹闄よ妭鐐圭殑缂栧彿:
num);
i!
=num&
&
head=head->
if(head->
next==NULL&
=num)
{
exit(0);
prior->
next=head->
next->
prior=head->
prior;
free(head);
voidrelease(structDNode*head)
:
structDNode*top;
while(head)
top=head;
%d&
head=head->
free(top);
intmain()
structDNode*head=Listinit(NULL);
Listadd(head);
Listdel(head);
release(head);
C语言优先级
一共有十五个优先级:
从上到下降低
1
()
[]
.
->
2
!
~
-(负号)++
--
&
(取变量地址)*
(type)(强制类型)
sizeof
3
*/%
4
+-
5
>
>
<
6
=<
=
7
==!
=
8
9
^
10
|
11
12
||
13
14
=
+=
-=
*=
/=
%=
|=
^=
<
=
15
就着多吧
结合性:
13
是从右至左
其他都是
从左至右有问题可以在交流的
待续
exit()是一个函数
结束一个进程,它将删除进程使用的内存空间,同时把错误信息返回父进程,在父进程中wait系统调用将接受到此返回信息。
return返回函数值,是关键字,返回后改程序结束;
在main函数中我们通常使用return(0);
这样的方式返回一个值。
但这是限定在非void情况下的也就是voidmain()这样的形式。
exit()通常是用在子程序中用来终结程序用的,使用后程序自动结束跳回操作系统。
但在如果把exit用在main内的时候无论main是否定义成void返回的值都是有效的,并且exit不需要考虑类型,exit
(1)等价于return
(1)
exit(0);
//正常退出
非0即是非正常退出
数字0,1,-1会被写入环境变量ERRORLEVEL,其它程序可以由此判断程序结束状态。
一般0为正常推出,其它数字为异常,其对应的错误可以自己指定。
#ifndef与#endif
这个是C语言或C++语言条件编译的表示方法。
并不一定用于头文件。
其形式为
#ifndefMACRO_NAME
codes;
#endif
其含义为,当MACRO_NAME这个宏没有被定义的时候,codes部分的代码才会被编译,否则codes部分将被忽略。
为了避免头文件被重复引用,在头文件中一般会加入类似于
#ifndefXXXX
#defineXXXX
codes
这样的代码。
其中XXXX这个宏名由头文件名衍生而来。
如a.h,XXXX可以定义为_A_H_。
当头文件被第一次引用时,XXXX未定义,codes部分被编译,同时定义宏XXXX。
当同一源文件第二次引用该头文件时,XXXX已经被定义了,codes部分不会被二次编译,从而避免重复引用。
条件编译
预处理程序提供了条件编译的功能。
可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。
这对于程序的移植和调试是很有用的。
条件编译有三种形式,下面分别介绍:
1.
第一种形式:
#ifdef
标识符
程序段1
#else
程序段2
#endif
它的功能是,如果标识符已被
#define命令定义过则对程序段1进行编译;
否则对程序段2进行编译。
如果没有程序段2(它为空)
2.
第二种形式:
#ifndef
与第一种形式的区别是将“ifdef”改为“ifndef”。
它的功能是,如果标识符未被#define命令定义过则对程序段1进行编译,
这与第一种形式的功能正相反。
3.
第三种形式:
#if
常量表达式
它的功能是,如常量表达式的值为真(非0),则对程序段1
进行编译,否则对程序段2进行编译。
因此可以使程序在不同条件下,完成不同的功能
pthread_mutex_t
1.互斥锁创建
有两种方法创建互斥锁,静态方式和动态方式。
POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下:
pthread_mutex_tmutex=PTHREAD_MUTEX_INITIALIZER;
在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。
动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下:
intpthread_mutex_init(pthread_mutex_t*mutex,constpthread_mutexattr_t*mutexattr)
其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。
pthread_mutex_destroy()用于注销一个互斥锁,API定义如下:
intpthread_mutex_destroy(pthread_mutex_t*mutex)
销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。
由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
2.互斥锁属性
互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
当前(glibc2.2.3,linuxthreads0.9)有四个值可供选择:
*PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。
当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。
这种锁策略保证了资源分配的公平性。
*PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。
如果是不同线程请求,则在加锁线程解锁时重新竞争。
*PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。
这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
*PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
3.锁操作
锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。
对于普通锁和适应锁类型,解锁者可以是同进程内任何线程;
而检错锁则必须由加锁者解锁才有效,否则返回EPERM;
对于嵌套锁,文档和实现要求必须由加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。
在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。
intpthread_mutex_lock(pthread_mutex_t*mutex)
intpthread_mutex_unlock(pthread_mutex_t*mutex)
intpthread_mutex_trylock(pthread_mutex_t*mutex)
pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。
#define
#include<
#define
A(x)
x+x
B(x)
(x+x)
intmain()
intx=5;
%d\n"
A(x)*A(x));
B(x)*B(x));
return0;
运行下你就明白了。
因为类函数宏只是简单的替代而不是函数:
A(x)*A(x)=x+x*x+x=35;
B(x)*B(x)=(x+x)*(x+x)=100;
typedef常见用法
1.常规变量类型定义
typedefunsignedcharuchar
描述:
uchar等价于unsignedchar类型定义ucharc声明等于unsignedcharc声明
2.数组类型定义
typedefintarray[2];
array等价于int[2]定义;
arraya声明等价于inta[2]声明
扩展:
typedefintarray[M][N];
array等价于int[M][N]定义;
arraya声明等价于inta[M][N]声明
3.指针类型定义
typedefint*pointer;
pointer等价于int*定义;
pointerp声明等价于int*a声明
typedefint*pointer[M];
pointer等价于int*[M]定义pointerp声明等价于int*a[M]声明明
4.函数地址说明
C把函数名字当做函数的首地址来对待,我们可以使用最简单的方法得到函数地址
函数:
intfunc(void);
unsignedlongfuncAddr=(unsignedlong)func,funcAddr的值是func函数的首地址
5.函数声明
typedefintfunc(void);
func等价于int(void)类型函数
描述1:
funcf声明等价于intf(void)声明,用于文件的函数声明
描述2:
func*pf声明等价于int(*pf)(void)声明,用于函数指针的生命,见下一条
6.函数指针
typedefint(*func)(void)
func等价于int(*)(void)类型
funcpf等价于int(*pf)(void)声明,pf是一个函数指针变量
7.识别typedef的方法:
a).第一步。
使用已知的类型定义替代typdef后面的名称,直到只剩下一个名字不识别为正确
如typedefu32(*fun