(二四)内存分配
最后更新于:2022-04-01 20:18:19
**引言:**
对于C语言程序,了解它运行时在内存中是如何分配的对于我们理解它的运行机制是非常有用的。下面就总结一下C语言程序的一些内存分配知识。
### 一
一段C程序,编译连接后形成的可执行文件一般有代码段、数据段、堆和栈等几部分组成。其中数据段又包括只读数据段、已初始化的读写数据段和未初始化的BSS段。如下图所示:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-23_57bc1a8a7a75c.jpg)
**文本段:**存放程序执行的代码。
**数据段:**
**1>只读数据段:**
只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。一般是const修饰的变量以及程序中使用的文字常量一般会存放在只读数据段中。
**2>已初始化的读写数据段:**
已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并且有初值,以供程序运行时读写。在程序中一般为已经初始化的全局变量,已经初始化的静态局部变量(static修饰的已经初始化的变量)
**3>未初始化段(BSS):**
未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。与读写数据段类似,它也属于静态数据区。但是该段中数据没有经过初始化。未初始化数据段只有在运行的初始化阶段才会产生,因此它的大小不会影响目标文件的大小。在程序中一般是没有初始化的全局变量和没有初始化的静态局部变量。
**堆:需程序员自己申请(调用malloc,realloc,calloc),并指明大小,并由程序员进行释放。**
**栈:由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间。**
### 二
根据上面的理论知识,分析示例片段的内存分配:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-23_57bc1a8a97de4.jpg)
### 三
**栈与堆的区别:**
**1.申请方式**
(1)栈(satck):由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间。
(2)堆(heap):需程序员自己申请(调用malloc,realloc,calloc),并指明大小,并由程序员进行释放。容易产生memory leak.
eg:char p;
p = (char *)malloc(sizeof(char));
但是,p本身是在栈中。
**2.申请大小的限制**
(1)栈:栈是向底地址扩展的数据结构,是一块连续的内存区域(它的生长方向与内存的生长方向相反)。栈的大小是固定的。如果申请的空间超过栈的剩余空间时,将提示overflow。
(2)堆:堆是高地址扩展的数据结构(它的生长方向与内存的生长方向相同),是不连续的内存区域。这是由于系统使用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由底地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。
3.系统响应:
(1)栈:只要栈的空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
(2)堆:首先应该知道操作系统有一个记录空闲内存地址的链表,但系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的free语句才能正确的释放本内存空间。另外,找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
说明:对于堆来讲,频繁的malloc/free势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,
4.申请效率
(1)栈由系统自动分配,速度快。但程序员是无法控制的
(2)堆是由malloc分配的内存,一般速度比较慢,而且容易产生碎片,不过用起来最方便。
5.堆和栈中的存储内容
(1)栈:在函数调用时,第一个进栈的主函数中后的下一条语句的地址,然后是函数的各个参数,参数是从右往左入栈的,然后是函数中的局部变量。注:静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续执行。
(2)堆:一般是在堆的头部用一个字节存放堆的大小。
6.存取效率
(1)堆:char *s1=”hellow tigerjibo”;是在编译是就确定的
(2)栈:char s1[]=”hellow tigerjibo”;是在运行时赋值的;用数组比用指针速度更快一些,指针在底层汇编中需要用edx寄存器中转一下,而数组在栈上读取。
补充:
栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
7.分配方式:
(1)堆都是动态分配的,没有静态分配的堆。
(2)栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的。它的动态分配是由编译器进行释放,无需手工实现。
### 四
参考1:[http://blog.csdn.net/tigerjibo/article/details/7423728](http://blog.csdn.net/tigerjibo/article/details/7423728)
参考2:[http://blog.csdn.net/to_be_it_1/article/details/31420549](http://blog.csdn.net/to_be_it_1/article/details/31420549)
参考3:[http://blog.csdn.net/lovecodeless/article/details/24384093](http://blog.csdn.net/lovecodeless/article/details/24384093)
参考4:[http://blog.csdn.net/lovecodeless/article/details/21084513](http://blog.csdn.net/lovecodeless/article/details/21084513)
';
(二三)errno变量
最后更新于:2022-04-01 20:18:17
引言:
在C编程中,errno是个不可缺少的变量,特别是在网络编程中。如果你没有用过errno,那只能说明你的程序不够健壮。 为什么会使用errno呢?这是系统库设计中的一个无奈之举,他更多的是个技巧,而不是架构上的需要。我们观察下函数结构,可以发现,函数的参数返回值只有一个,这个返回值一般可以携带错误信息,比如负数表示错误,而正数表述正确的返回值,比如recv函数。但是对于一些返回指针的函数,如:char *get_str();这个方法显然没有用的。NULL可以表示发生错误,但是发生什么错误却毫无办法。于是,errno就诞生了。全局变量errno可以存放错误原因,当错误发生时,函数的返回值是可以通过非法值来提示错误的发生。
问题:
很多库函数,当执行失败时会通过一个名称为errno的外部变量,通知程序该函数调用失败。该如何利用errno这个外部变量检查错误发生的原因呢?
解决办法:
错误的方法一:
~~~
/*调用库函数*/
if(errno)
/*处理错误*/
~~~
错误原因:在库函数调用没有失败的情况下,并没有强制要求库函数一定要设置errno为0,这样errno的值可能是前一个执行失败的库函数设置的值。程序器启动的似乎errno的初始值是零。许多库函数在遭遇一些确定的错误以后将保证设置它为某个确定的非零值。当这些函数运行成功的时候它将不会修改 errno的值;因此,当一个成功的调用以后errno的值不必要是零,而且你不能用errno来判断一次调用是否失败了。正确的做法是检查每个函数报道的结果,如果调用失败了,再来检查errno。
错误的方法二:
~~~
errno = 0;
/*调用库函数*/
if(errno)
/*处理错误*/
~~~
错误原因:库函数在调用成功时,既没有强制要求对errno清零,但同时也没有禁止设置errno。
正确方法:
~~~
/*调用库函数*/
if(返回的错误值)
检查errno
~~~
参考:
1、《C陷阱与缺陷》
2、[http://blog.csdn.net/wang_517766334/article/details/7561495](http://blog.csdn.net/wang_517766334/article/details/7561495)
3、[http://blog.csdn.net/romandion/article/details/2036975](http://blog.csdn.net/romandion/article/details/2036975)
附录:errno变量可设置为的值及每个值代表的意义。
~~~
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Arg list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Function not implemented */
#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
#define EL2HLT 51 /* Level 2 halted */
#define EBADE 52 /* Invalid exchange */
#define EBADR 53 /* Invalid request descriptor */
#define EXFULL 54 /* Exchange full */
#define ENOANO 55 /* No anode */
#define EBADRQC 56 /* Invalid request code */
#define EBADSLT 57 /* Invalid slot */
#define EDEADLOCK EDEADLK
#define EBFONT 59 /* Bad font file format */
#define ENOSTR 60 /* Device not a stream */
#define ENODATA 61 /* No data available */
#define ETIME 62 /* Timer expired */
#define ENOSR 63 /* Out of streams resources */
#define ENONET 64 /* Machine is not on the network */
#define ENOPKG 65 /* Package not installed */
#define EREMOTE 66 /* Object is remote */
#define ENOLINK 67 /* Link has been severed */
#define EADV 68 /* Advertise error */
#define ESRMNT 69 /* Srmount error */
#define ECOMM 70 /* Communication error on send */
#define EPROTO 71 /* Protocol error */
#define EMULTIHOP 72 /* Multihop attempted */
#define EDOTDOT 73 /* RFS specific error */
#define EBADMSG 74 /* Not a data message */
#define EOVERFLOW 75 /* Value too large for defined data type */
#define ENOTUNIQ 76 /* Name not unique on network */
#define EBADFD 77 /* File descriptor in bad state */
#define EREMCHG 78 /* Remote address changed */
#define ELIBACC 79 /* Can not access a needed shared library */
#define ELIBBAD 80 /* Accessing a corrupted shared library */
#define ELIBSCN 81 /* .lib section in a.out corrupted */
#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
#define ELIBEXEC 83 /* Cannot exec a shared library directly */
#define EILSEQ 84 /* Illegal byte sequence */
#define ERESTART 85 /* Interrupted system call should be restarted */
#define ESTRPIPE 86 /* Streams pipe error */
#define EUSERS 87 /* Too many users */
#define ENOTSOCK 88 /* Socket operation on non-socket */
#define EDESTADDRREQ 89 /* Destination address required */
#define EMSGSIZE 90 /* Message too long */
#define EPROTOTYPE 91 /* Protocol wrong type for socket */
#define ENOPROTOOPT 92 /* Protocol not available */
#define EPROTONOSUPPORT 93 /* Protocol not supported */
#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT 96 /* Protocol family not supported */
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET 102 /* Network dropped connection because of reset */
#define ECONNABORTED 103 /* Software caused connection abort */
#define ECONNRESET 104 /* Connection reset by peer */
#define ENOBUFS 105 /* No buffer space available */
#define EISCONN 106 /* Transport endpoint is already connected */
#define ENOTCONN 107 /* Transport endpoint is not connected */
#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS 109 /* Too many references: cannot splice */
#define ETIMEDOUT 110 /* Connection timed out */
#define ECONNREFUSED 111 /* Connection refused */
#define EHOSTDOWN 112 /* Host is down */
#define EHOSTUNREACH 113 /* No route to host */
#define EALREADY 114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTALE 116 /* Stale NFS file handle */
#define EUCLEAN 117 /* Structure needs cleaning */
#define ENOTNAM 118 /* Not a XENIX named type file */
#define ENAVAIL 119 /* No XENIX semaphores available */
#define EISNAM 120 /* Is a named type file */
#define EREMOTEIO 121 /* Remote I/O error */
#define EDQUOT 122 /* Quota exceeded */
#define ENOMEDIUM 123 /* No medium found */
#define EMEDIUMTYPE 124 /* Wrong medium type */
~~~
';
(二二)位操作
最后更新于:2022-04-01 20:18:15
**C的位运算符**
1、二进制反码或按位取反:~
~(10011010) = (01100101)。
假设val是一个unsigned char,~val不改名原来val的值。
2、位与:&
二进制运算符&通过对两个操作数逐位进行比较产生一个新值。
(10010011)&(00111101)=(00010001)。
C中的一个组合的位与赋值运算符:&=。
3、位或:|
二进制运算符|通过对两个操作数逐位进行比较产生一个新值。
(10010011)|(00111101)=(10111111)。
C中的一个组合的位或赋值运算符:|=。
4、位异或:^
二进制运算符^对两个操作数逐位进行比较。对于每个位,如果操作数中对应位有一个为1,结果为1。
(100100011)^(00111101)=(10101110)。
C中有一个组合的位异或赋值运算符:^=。
5、掩码:“位与”运算符通常跟掩码一起使用。掩码是某些位设为开(1)而某些位设置为关(0)的位组合。
flags = flags & MASK。
6、打开位。如,对于flags,想要打开它的第二位,可以使用flags = flags | (1<<2).
7、关闭位。如,对于flags,想要关闭它的第二位,可以使用flags = flags & (~(1<<2))。
8、转置位。转置一个位表示如果该位打开,则关闭该位;如果该位关闭,则打开该位。
如,对于flags,想要转置它的第二位,可以使用flags = flags ^ (1<<2)。
9、移位运算符。左移:<<。右移:>>。左移运算符<<将其左侧操作数的值的每位向左移动,移动的位数由其右侧操作数指定。空出的位用0填充,并且丢弃移出左侧操作数末端的位。右侧位运算符>>将其左侧操作数的值的每位向右移动,移动的位数由其右侧操作数指定。丢弃移出左侧操作数右端的位。
10、移位运算符:移位运算符能够提供快捷、高效的对2的幂的乘法和除法。
number << n : number乘以2的n次幂。
number >> n : 如果number非负,则用number除以2的n次幂。
';
(二一)内联函数
最后更新于:2022-04-01 20:18:12
引言:调用函数时,通常会因为建立调用、传递参数、跳转到函数代码并返回等花费掉一些时间,C语言的解决办法是使用类函数宏。在C99中,还提出了另外一种方法:内联函数。
内联函数:把函数变为内联函数将建议编译器尽可能快速地调用该函数,至于建议的效果则由实现来定义。因此,使函数变为内联函数可能会简化函数的调用机制,但也可能不起作用。内联函数是通过编译器来实现的,而宏则是在预编译的时候替换。
**创建内联函数方法:在函数声明中使用函数说明符inline。**
**内联函数的特点:**
1、类似于宏,编译器在看到内联函数声明时,就会在使用内联函数时用函数体代替函数调用,其效果就如同在此处键入了函数体的代码。如:
**源码:**
~~~
#include
inline void eatline() //内联函数的定义
{
while(getchar() != '\n')
continue;
}
int main()
{
eatline(); //函数调用
}
~~~
**通过编译器编译其实的效果如下:**
~~~
#include
inline void eatline() //内联函数的定义
{
while(getchar() != '\n')
continue;
}
int main()
{
while(getchar() != '\n')
continue;
}
~~~
2、内联函数没有预留给它的单独代码块,所以无法获得内联函数的地址。
3、内联函数不会在调试器中显示。比如上面的函数即使使用了gcc -g 选项进行了编译,通过gdb调试时,也不会有eatline函数。
4、内联函数应该是比较短小。对于很长的函数,调用函数的时间少于执行函数主体的时间;此时,使用内联函数不会节省多少时间。
5、内联函数的定义和对该函数的调用必须在同一文件中,即内联函数具有内部链接。在多个文件程序中,每个调用内联函数的文件都要对该函数进行定义。达到这个目标的简单方法为:在头文件中定义内联函数,并在使用该函数的文件中包含该头文件。
6、与C++不同,C允许混合使用内联函数定义和外部函数定义。因为定义的内联函数只能在本文件中使用,而定义的外部函数,却可以通过extern进行外部声明,在其他文件中使用。如:
~~~
//file1.c
inline double square(double);
double square (double x) { return x * x; }
//file2.c
extern double square(double);
double square (double x) { return x * x; }
//file3.c
extern double square(double);
int main()
{
double kw = square(w);
}
~~~
其中file1.c中使用的double函数时本文件中定义的内联函数,而在file2.c和file3.c中使用的double函数确实file2.c中定义的外部函数。
';
(二十)scanf函数详解
最后更新于:2022-04-01 20:18:10
引言:scanf函数虽然是学习C语言时比较早就接触的一个函数,但在使用过程中,发现真正掌握它却并不容易。本文就通过各种例子来详细的总结一下该函数的各种用法,假设它的调用格式为 scanf("<格式化字符串>",<地址表>)。
**1、一般使用scanf函数时都是为某个变量赋值,不考虑它的返回值。但是任何函数都是需要返回的(即使返回类型用void,也可以认为只是调用了return语句,只是并没有返回什么东西而已),同样的scanf函数也是有返回的,它的返回值是成功读取变量的个数。如果有一个输入与变量格式不匹配,那么返回值为0。如:**
~~~
scanf("%d %d", &num1, &num2);
~~~
如果输入两个中间有空格隔开的数字(如2 3),那么它的返回值是2。如果输入一个浮点数一个整数,则返回值是1。如果输入一个字符一个整数,则返回值是0。
**2、scanf函数的<格式化字符串>与后面的<地址表>是必须严格匹配的。注意,是严格匹配,可以说不能有丝毫差别,但对于连续多个空格可以等同于一个空格。如:**
~~~
scanf("%d, %d", &num1, &num2);
~~~
要想输入正确,必须输如一个整数,然后输入一个逗号(,),之后是第二个整数。最后是回车结束。
~~~
scanf("%d,%d", &num1, &num2);
~~~
该条语句中的<格式字符串>中的两个%d之间没有空格,如果此时输入:12 ,13回车(12后面先有一个空格后由逗号),那么num2并不等于13。反过来,输入:“12”、“,”、“空格”、“13”,则不会出现错误。
3、scanf函数用%s读取一个字符串时,其实它只能读取一个单词,因为遇到空格时,它会认为输入已结束。因此一般使用fgets来读取一个字符串。如果想用scanf函数读取带有空格的字符串时,需要使用参数%[ ]来完成,它的意思是读入一个字符集合。[ ]是个集合的标志,%[ ]特指读入此集合所限定的那些字符,比如%[A-Z]是输入大写字母,一旦遇到不在此集合的字符便停止。如果集合的第一个字符是“^”,这说明读取不在"^"后面集合的字符,既遇到"^"后面集合的字符便停止(这就是scanf函数里的正则表达式应用)。注意:此时读取的字符串是可以含有空格的。如:
~~~
scanf("1123%s",&str);
~~~
输入:1123aaabb 时str为 aaabb,但是,输入 24aabbdd时, 会出错,因为1123必须进行严格匹配。
~~~
scanf("%[^\n]", &str);
~~~
此时输入fdjkf fkdjf jdkf,然后输入回车,就给str赋值为fdjkf fkdjf jdkf。
**scanf("%[A-Z]",&str);输入除A到Z的任何字符(包括空格、回车)都会停止。**
**4、对于下面两条语句**
~~~
scanf("%d ", &num);/*scanf("%d\n", &num);*/
printf("%d",num);
~~~
我们输入一个整数后,无论在输入多少个空格、回车、Tab,都没有输出;但是当再次输入非空白字符时,如输入2 然后输入空格然后输入4,最后输入回车,则会有输出。
**5、对于scanf函数的%c格式转换符,可以接受任何的非空白字符或空白字符(包括空格、回车、Tab甚至是F2这样的字符)。**
~~~
char str;
scanf("%c", &str);
printf("str = %c\n", str);
~~~
如果输入:空格……/*……代表任意空白字符或非空白字符*/,则str被赋值为空格。
**如果输入:回车,则str被立即赋值为换行字符‘\n’。**
**如果输入:fjdkfj,则str被赋值为f,f后面的jdkfj丢弃。**
';
(十九)restrict关键词
最后更新于:2022-04-01 20:18:08
**引言:在内核的系统调用函数里,经常遇到函数的参数使用restrict限定词限定的情况,下面就对该关键词做个总结。**
1、restrict关键词是C99特性才添加的,因此在编译使用含有该限定词的程序时,一定要在后边添加-std=c99的标志,使得gcc能够支持c99标准。
2、restrict既然是个限定词,那么它限定什么变量呢?它只能限定指针变量!经过它限定的数据对象,表明指针时访问该数据对象的唯一且初始的方式。注意:这里的唯一表明了,由它限定的指针所指向的数据块,只能由该指针访问,不能由除它之外的任何方式访问。初始指的是它必须在初始化的时候声明,不能再以后声明。后面给出例子解释。
3、restrict其实可以看成只有两个读者。一个是编译器,它告诉编译器可以自由地做一些有关优化的假定。另一个读者是用户,它告诉用户仅适用满足restrict要求的参数。比如,下列两个函数声明:
void *memcpy(void *restrict s1, const void *restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n);
上面两个函数都是从位置s2把n个字节复制到位置s1。函数memcpy()要求两个位置之间不重叠,但memmove()没有这个要求。把s1和s2声明为restrict意味着每个指针都是相应数据的唯一访问方式,因此他们不能访问同一数据块。这满足了不能有重叠的要求。
**程序示例restrict.c:**
~~~
#include
#include
int
main(int argc, char **argv)
{
int n;
int ar[5];
int *restrict restar = (int *)malloc(5 * sizeof(int));/*将该指针声明为了restrict,使得这块内存的数据只能由restar访问,不能由下面的par指针访问*/
int *par = ar;
for(n = 0; n < 5; n++){
par[n] += 5;
restar[n] += 5;
ar[n] *= 2;
par[n] += 3;
restar[n] += 3;
printf("ar[%d] = %d\n", n, restar[n]);
printf("ar[%d] = %d\n", n, ar[n]);
}
return -1;
}
~~~
编译:gcc restrict.c -std=c99
**执行编译结果:./a.out**
**ar[0] = 8
ar[0] = 4851221
ar[1] = 8
ar[1] = 4849653
ar[2] = 8
ar[2] = 269027981
ar[3] = 8
ar[3] = 2147391485
ar[4] = 8
ar[4] = 2614935**
从结果可以看出,通过par指针以及ar所访问的restar指针所指向的数据并没有起任何作用。能够改变所指向内存数据的只是restar[n] += 5;与 resatr[n] += 3;这两条语句。其实编译器会对这两条语句做优化,将它们合并成一条语句:restar[n] += 8;
';
(十八)字符/字符串输入函数fgetc、fgets、getc、getchar和gets
最后更新于:2022-04-01 20:18:06
~~~
#include
intfgetc(FILE *stream);
char *fgets(char *s, int size, FILE *stream);
int getc(FILE *stream);
int getchar(void);
char *gets(char *s);
~~~
fgetc()读取文件指针stream所指向文件的下一个字符,返回值是所读取字符强制类型转换成整数的值,如果到达文件尾部或者出错,则返回EOF。
getc()与fgetc()函数相同,只是它是一个宏实现。
getchar()等同于getc(stdin)。
gets()从标准输入读取一行到字符串指针s所指向的缓冲区,直到行结束或遇到一个EOF,然后用'\0'替代它。注意:该函数不检查缓冲区是否够大,是否有溢出。
fgets()从文件指针stream所指向的文件中,最多取出size个字符存放到s所指向的换中去中。遇到EOF或一行结束时,读取停止。如果读取一行,它将该行存放到缓冲区,在最后一个字符的后边添加'\0'并放到缓冲区。
返回值:
fgetc(), getc() 和getchar()成功时返回读取字符的ASCII码值,失败时返回EOF。
gets() 和fgets() 成功时返回字符串的指针s,失败时返回NULL指针。
~~~
#include
int
main(int argc, char **argv)
{
int tmp;
FILE *fp;
char ptr[10];
char *p;
fp = fopen("/work/tmp/c/txt", "r");
if(fp == NULL){
printf("open txt fail\n");
return -1;
}
/*fgetc function test*/
if((tmp = fgetc(fp)) != EOF){
printf("tmp = %d, the character we get is %c\n", tmp, (char)tmp);
}
/*fgets function test*/
if((p = fgets(ptr, 5, fp)) != NULL){
printf("the string we get is %s\n", ptr);
}
/*getc function test*/
if((tmp = getc(fp)) != EOF){
printf("tmp = %d, the character we get is %c\n", tmp, (char)tmp);
}
/*getchar function test*/
if((tmp = getchar()) != EOF){
printf("tmp = %d, the characeter we get is %c\n", tmp, (char)tmp);
}
return 0;
}
~~~
';
(十七)字符/字符串输出函数fputc、fputs、putc、putchar和puts
最后更新于:2022-04-01 20:18:03
~~~
#include
int fputc(int c, FILE *stream);
int fputs(const char *s, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
int puts(const char *s);
~~~
**fputc()写一个字符c,强制转换成一个unsigned char类型字符,到文件stream。**
**fputs()写一个字符串到stream,省略字符串尾部的‘\0’。**
**putc()与fputc()相同,只是putc()是作为一个宏实现的。**
**putchar(c)等同于putc(c,stdout)。**
**puts()写一个字符串到标准输出stdout,并自动在字符串的尾部添加一个换行符"\n"。**
**返回值:fputc()、putc()和putchar()成功时返回要写的字符,失败返回EOF。**
**puts()和fputs()成功时返回一个非负数,失败返回EOF。**
示例程序如下:
~~~
#include
#include
#include
#include
int
main(int argc, char **argv)
{
int tmp;
char a = 'a';
char space = '\n';
char *str = "libing";
FILE *fp;
fp = fopen("/work/tmp/c/txt", "w+");/*打开文件txt,并将文件指针赋值给fp,fp用于下面各个函数的参数*/
if(fp == NULL){
printf("open txt fail\n");
return -1;
}
/*fputc function test*/
tmp = fputc(a, stdout);/*字符a输出到标准输出,并将返回值赋值给tmp*/
fputc(space, stdout);/*fputc的输出只是输出字符,并不在后面添加换行符,我们需要自己手工添加*/
fputc(a, fp);/*字符a输出到文件指针fp指向的文件*/
fputc(space, fp);
fputc(a, fp);
fputc(space, fp);
printf("tmp = %d\n", tmp);/*输出fputs函数的返回值,可以看出,它输出的是字符a的ASCII码*/
/*fputs function test*/
fputs(str, stdout);/*将str所指向的字符串输出到标准输出,注意,它不会在后面添加换行符*/
fputc(space, stdout);
tmp = fputs(str, fp);
fputc(space, fp);/*将str所指向的字符串输出到文件指针fp所指向的文件*/
printf("tmp = %d\n", tmp);
/*putc function test*/
tmp = putc(a, stdout);/*putc()与fputc()相同*/
putc(space, stdout);
putc(a, fp);
putc(space, fp);
putc(a, fp);
putc(space, fp);
printf("tmp = %d\n", tmp);
/*puchar function test*/
tmp = putchar(a);/*putchar(c)等同于putc(c,stdout)*/
putchar(space);
printf("tmp = %d\n", tmp);
/*puts function test*/
puts(str);/*将str指针所指向的字符串输出到标准输出,并且会在字符串后自动添加换行符*/
return 0;
}
~~~
';
(十六)字符串输出函数puts、fputs和printf
最后更新于:2022-04-01 20:18:01
C有三个用于输出字符串的标准库函数puts()、fputs()和printf()。
1、puts()函数只需要给出字符串参数的地址。
~~~
#include
int puts(const char *s);
~~~
示例:
~~~
#include
#define DEF "I am libing"
int
main(int argc, char **argv)
{
char str1[30] = "I am libing.";
const char *str2 = "I am libing";
puts("I am libing.");
puts(DEF);
puts(str1);
puts(str2);
return 0;
}
~~~
运行结果测试:
~~~
I am libing.
I am libing
I am libing.
I am libing
~~~
注意:上面的所有字符串都是单行显示,但并没有在后面添加换行符。这就是,与printf()不同,puts()显示字符串时自动在其后添加一个换行符。
2、fputs()函数时puts()的面向文件版本。两者的区别是:
a、fputs()需要第二个参数来说明要写的文件。
b、与puts()不同,fputs()并不为输出自动添加换行符。
~~~
#include
int fputs(const char *s, FILE *stream);
~~~
注意:gets()丢掉输入里的换行符,但puts()为输出添加换行符。另一方面,fgets()存储输入中的换行符,而fputs()也不为输出添加换行符。
技巧:假定写一个循环,读取一行并把它回显在下一行,可以这么写:
~~~
char line[80];
while(gets(line))
puts(line);
~~~
3、printf()与puts()的区别之一就是printf()并不自动在新行上输出每一个字符串。必须指明需要另起一行的地方。
pintf("%s \n", string);等同于 puts(string);
';
(十五)字符串输入函数fgets、gets和scanf
最后更新于:2022-04-01 20:17:59
**引言:如果想把一个字符串读到程序中,必须首先预留存储字符串的空间,然后使用输入函数来获取这个字符串。**
**读取字符串输入的第一件事是建立一个空间以存放读入的字符串。**
char *name;
scanf("%s", name);
这段代码虽然可能通过编译,但因为name可以指向任何地方,所以它的输入值可能覆盖以前name所指位置的值。
解决办法是声明一个固定大小的字符数组,或者使用C库里的分配存储空间的函数。
1、gets函数从系统标准输入获得一个字符串,读取字符串直到遇到一个换行符(\n),它读取换行符之前的所有字符,在这些字符后添加一个空字符(\0),然后把这个字符串交给调用它的程序。它把读取的换行符直接丢弃,而不是把它放入字符串中,这与下面讨论的fgets函数不同,下面再给出例子证明这一点。
~~~
#include
char *gets(char *s);
~~~
**示例:**
~~~
#include
#define MAX 18
int
main(int argc, char **argv)
{
char name[MAX];
char *ptr;
printf("what't your name?\n");
ptr = gets(name); /*注意:gets()返回的指针与传递给它的是同一个指针*/
printf("%s? Ah ! %s\n", ptr, name);
return 0;
}
~~~
**注意,如果gets出错时它返回一个空地址,因此在使用时一般使用如下技巧:**
~~~
while((ptr = gets(name)) != NULL){
……
}
~~~
2、gets函数的一个不足是它不检查预留存储区是否能够容纳实际输入的数据。多出来的字符简单地溢出到相邻的内存区。fgets()函数改进了这个问题。它可以指定最大读入字符数。fgets()和gets()有3点不同:
a、它需要第二个参数来说明组大读入字符数。如果这个参数值为n,fgets()就会读取最多n-1个字符或者读完一个换行符为止。
b、如果fgets读取到换行符,就会把它存放在字符串里,而不是像gets()那样丢弃它。也就是说它在字符串的最后存放的是换行符(\n),而不是空字符(\0)。
c、它还需要第三个参数来说明读哪一个文件。
~~~
#include
char *fgets(char *s, int size, FILE *strem);
~~~
**示例**
~~~
#include
#define MAX 18
int
main(int argc, char **argv)
{
char name[MAX];
char *ptr;
printf("what't your name?\n");
ptr = fgets(name, MAX, stdin);
printf("%s? Ah ! %s\n", ptr, name);
return 0;
}
~~~
**编译测试结果显示:**
~~~
what't your name?
libing
libing
? Ah ! libing
~~~
它显示了fgets()的一个不足之处,fgets()把换行符存储到字符串里了,这样每次显示字符串时就会显示换行符。
下面对fgets()和gets()两个函数对于换行符不同处理的示例证明:
~~~
#include
#define MAX 18
int
main(int argc, char **argv)
{
char name1[MAX];
char name2[MAX];
char *ptr;
printf("what't your name?\n");
ptr = fgets(name1, MAX, stdin);
ptr = gets(name2);
if(strcmp(name1, name2)){ /*比较两个字符串,因为gets会在最后将\n改为\0存入字符串,而fgets会直接将\n存入字符串*/
printf("name1 is not equal name2\n");
}
return 0;
}
~~~
3、scanf()和gets()主要的差别在于它们如果决定字符串何时结束。scanf()更给予获取单词而不是获取字符串;而gets()函数,会读取所有的字符,直到遇到第一个换行符为止。scanf()使用两种方法决定输入结束,如果使用%s格式,字符串读到下一个空白字符。如果指定了字段宽度,比如%10s,scanf()就会读入10个字符或直到遇到第一个空白字符,由二者中最先满足的哪一个终止输入。
~~~
#include
int
main(void)
{
char name1[11], name2[11];
int count;
while(1){
printf("please input 2 names.\n");
count = scanf("%5s %10s", name1, name2);
printf("I read the %d names %s and %s.\n"
,count, name1, name2);
}
return 0;
}
~~~
**运行执行测试:**
~~~
please input 2 names.
Jesse Jukes
I read the 2 names Jesse and Jukes.
please input 2 names.
Liza Applebottham
I read the 2 names Liza and Applebotth.
please input 2 names.
Portensia Callowit
I read the 2 names am and Portensia.
please input 2 names.
I read the 2 names Callo and wit.
~~~
';
(十四)const关键字详解
最后更新于:2022-04-01 20:17:57
const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程度上可以提高程序的安全性和可靠性,另外,了解const的作用,在看别人的代码时,对理解对方的程序有一定帮助。
1、const可以理解成是”只读变量“的限定词,从这里可以看出,const修饰的是变量,跟常量是不同的,常量是被编译器放在内存中的只读区域,当然也就不能够去修改它。而”只读变量“则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。const就是用来限定一个变量不允许被改变的修饰符。因为const声明的变量是只读变量,所以它不能通过赋值、增量或减量运算来修改该变量的值,只能够初始化一个const变量,初始化完成后,不能再改变它。
**const int nochange;nochange = 12;/*这是不允许的*/**
**const int nochange = 12;/*这是可以的*/**
2、在声明指针时使用关键字const,一定要区分让指针本身成为const与让指针指向的值成为const区分开来。
const float *pf;/*pf指向一个常量浮点数值,pf指向的值必须是不变的,但pf本身的值可以改变*/
float *const pt;/*pt是一个常量指针,它必须总是指向同一个地址,但所指向的值可以改变*/
const float *const ptr;/*ptr必须总是指向同一个位置,并且它所指位置存储的值也不能改变*/
**float const *pfc;等同于const float *pfc;**
把const放在类型名的后边和*的前边,意味着指针不能用来改变它所指向的值。总之,一个位于*左边任意位置的const使得数据成为常量,而一个位于*右边的const使得指针自身成为常量。
**常见用法是声明作为函数形式参量的指针。**
3、对全局数据使用const。
首先遵循外部变量的惯用规则:在一个文件中进行定义声明,在其他文件中进行引用声明(使用extern关键字)。
~~~
/*file.c——定义一些全局变量*/
const doubule PI = 3.141;
/*file2.c——使用在其他文件中定义的全局变量*/
extern const double PI;
~~~
其次是将常量放在一个include文件中。这时还必须使用静态外部存储类:
~~~
/*constant.h——定义一些全局变量*/
static const double PI = 3.141;
/*file1.c——使用在其他文件中定义的全局变量*/
#include "constant.h"
/*file2.c——使用在其他文件中定义的全局变量*/
#include "constant.h"
~~~
如果不使用关键字static,在文件file1.c和file2.c中包含constant.h将导致每个文件都有统一标示符的定义声明。
';
(十三)printf、fprintf、sprintf和snprintf函数
最后更新于:2022-04-01 20:17:54
~~~
#include
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
~~~
**printf是标准的输出函数。**
fprintf传送格式化输出到一个文件中。根据指定的format(格式)发送信息(参数)到由stream(流)指定的文件,fprintf只能和printf一样工作。若成功则返回值是输出的字符数,发生错误时返回一个负值。第一个参数是文件指针stream,后面的参数就是printf中的参数,其功能就是把这些输出送到文件指针指定的文件中,如果想要像printf一样将输出送到标准输出,只需要将文件指针FILE指定为stdout即可。
**示例程序如下:**
~~~
#include
FILE *stream;
int
main(void)
{
char s[] = "this is a string.\n";
char c = '\n';
stream = fopen("fprintf.out", "w");
fprintf(stream, "%s", s);
fprintf(stdout, "abc\n");
return 0;
}
~~~
该程序的运行结果是在fprintf.out文件中存入了this is a string.字符串,在标准输出输出了abc字符串。
sprintf,字符串格式化命令,主要功能是把格式化的数据写入某个字符串中。第一个参数str是char型指针,指向将要写入的字符串的缓冲区。后面第二个参数是格式化字符串。
**示例程序:**
~~~
#include
int
main(void)
{
char s[100];
sprintf(s, "%%sfjdksfj" );
printf("%s\n", s);
return 0;
}
~~~
执行后输出结果是
~~~
%sfjdksfj
~~~
snprintf函数与sprintf函数类似。它也是将可变个参数按照format格式化成字符串,然后将其复制到str中。
**(1) 如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符('\0');**(2) 如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符('\0'),返回值为格式化后的字符串的长度。
**示例程序:**
~~~
#include
int
main(void)
{
char s[10];
snprintf(s, 4, "%%sfjdksfj" );
printf("%s\n", s);
snprintf(s, sizeof(s), "%%sfjdksfj" );
printf("%s\n", s);
return 0;
}
~~~
运行结果:
**%sf
%sfjdksfj**
';
(十二)命令行参数
最后更新于:2022-04-01 20:17:52
**C程序的main函数具有两个形参。第1个通常称为argc,它表示命令行参数的数目。第2个通常称为argv,它指向一组参数值。**
指针数组:这个数组的每个元素都是一个字符指针,数组的末尾是一个NULL指针。argc的值和这个NULL值都用于确定实际传递了多少个参数。argv指向数组的第1个元素,这就是它为什么被声明为一个指向字符的指针的指针的原因。注意:通常第1个参数就是程序的名称。
**示例:**
~~~
#include
int
main(int argc, char **argv)
{
while(*++argv != NULL){
printf("%s ", *argv);
}
printf("\n");
return 0;
}
~~~
上面的例子用于显示命令行的参数。
**处理命令行参数示例:**
~~~
#include
int
main(int argc, char **argv)
{
while(*++argv != NULL && **argv == '-'){
switch(*++*argv){//检查横杠后面的字母
case 'a':
printf("option is a.\n");
break;
case 'b':
printf("option is b.\n");
break;
}
}
return 0;
}
~~~
';
(十一)深入理解指针
最后更新于:2022-04-01 20:17:50
**引言:在C语言中,指针的地位是不言而喻的,要想很好的掌握C语言,掌握指针是必须的,这也是C语言不同于其他语言的地方。**
**(一)指针的指针**
**例子:**
~~~
int i;
int *pi;/*把pi初始化为指向变量i,pi = &i*/
int **ppi;/*把ppi初始化为指向变量pi,ppi = &pi*/
~~~
初始化指针后,就可以使用它们了。例如i = 'a'; *pi = 'a'; **ppi = 'a';具有相同的效果。
在一条简单的对i赋值的语句可以完成的任务情况下,为什么还要使用更为复杂的涉及间接访问的方法呢?这是因为简单赋值并不总是可行,例如链表的插入。
**(二)高级声明**
~~~
int f; /*声明一个整型变量*/
int *f; /*一个指向整型的指针*/
intf();/*把f声明为一个函数,它的返回值是一个整数*/
int *f(); /*f是一个函数,它的返回值类型是一个指向整型的指针*/
int (*f)(); /*使f成为一个函数指针,它所指向的函数返回一个整型值*/
int *(*f)(); /*f是一个函数指针,只是所指向的函数的返回值是一个整型指针*/
int f[]; /*f是个整型数组*/
int *f[]; /*f是数组,它的元素类型是指向整型的指针*/
int (*f[])(); /*括号内的表达式*f[]首先进行求值,所以f是一个元素为某种类型的指针的数组。表达式
末尾的()是函数调用操作符,所以f肯定是一个数组,数组元素的类型是函数指针,
它所指向的函数的返回值是一个整型值*/
int *(*f[])(); /*f是一个指针数组,指针所指向的类型是返回值为整型指针的函数*/
~~~
';
(十)结构体
最后更新于:2022-04-01 20:17:48
**引言:数据经常以成组的形式存在。在C中,使用结构可以把不同类型的值存放在一起。**
**结构的声明有两种**
1、struct SIMPLE{
int a;
char b;
float c;
};然后用标签SIMPLE去声明结构体变量。
2、typedef struct{
int a;
char b;
float c;
}Simple;然后用Simple去声明结构体变量。此时Simple是个类型名,而不像上面的SIMPLE是个标签。
结构成员可以是标量、数组、指针甚至是其他结构。
结构成员的直接访问用点操作符(.)访问。例如Simple a;a.a或a.b或a.c。
结构成员的间接访问用->操作符访问,它是针对结构指针使用的。例如Simple *a;a->a或a->b或a->c。
结构的自引用是结构的成员里包含结构本身,但这种包含不能是直接包含,而应该包含的是指向该结构的指针。
结构作为函数参数时,一般使用指向结构的指针。向函数传递指针的缺陷在于函数现在可以对调用程序的结构变量进行修改。如果不希望如此,可以在函数使用const关键字来防止这类修改。而且,传递结构指针比传递结构本身更有效率。
**总结:**
1、具有相同成员列表的结构声明产生不同类型的变量。
2、使用typedef为一个子引用的结构定义名字时应该小心。
3、向函数传递结构参数是低效的。
4、把结构标签声明和结构的typedef声明放在头文件中,当源文件需要这些声明时可以通过#include指令包含。
5、结构成员的最佳排列形式并不一定就是考虑边界对齐而浪费内存空间最少的那种排列形式。
';
(九)动态内存分配
最后更新于:2022-04-01 20:17:45
引言:数组的元素存储于内存中连续的位置上。当一个数组被声明时,它所需要的内存在编译时就被分配。但是,我们可以使用动态内存分配在运行时为它分配内存。
一块内存的生命周期可以分为四个阶段:分配、初始化、使用、释放。
内存的分配一般使用C函数库里的malloc函数(原型:void *malloc(size_t size))。关于malloc函数应该注意一下几点:
1、malloc的参数就是需要分配的内存的字节数。
2、malloc所分配的是一块连续的内存。
3、分配成功,则返回指向分配内存起始地址的指针。分配失败,返回NULL指针。
4、对每个malloc返回的指针都进行检查,是好的习惯。
内存的初始化一般要在使用之前手动进行,也可以在分配时由calloc函数一同完成:将分配内存初始化为0。使用就是使用分配所返回的指向内存的指针。
释放内存是为了防止内存泄露,一般使用free函数(原型:void free(void *pointer))完成。它的参数必须是先前从malloc、calloc或realloc返回的值。向free传递一个NULL参数不会产生任何效果。向free传递其他参数会出错。
**注意:常见的动态内存错误有以下几种**
1、忘记对NULL指针进行解引用操作,即忘记对分配返回的值做判断。
2、对分配的内存进行操作时越过边界。
3、释放并非动态分配的内存,传递给free的必须是一个从malloc、calloc或realloc返回的指针。
4、试图释放一块动态分配的内存的一部分。
5、一块动态内存被释放后被继续使用。
使用动态内存的编程总结:
1、动态内存分配有助于消除程序内部存在的限制。
2、使用sizeof计算数据类型的长度,调高程序的可移植性。
补充:calloc和realloc函数
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);
calloc与malloc的区别一是前者返回指向内存的指针之前把它初始化为0.二是calloc的参数包括所需元素的数量和每个元素的字节数。根据这些值,能够计算出总共需要分配的内存。
realloc函数用于修改一个原先已经分配的内存卡的大小。使用该函数,可以使一块内存扩大或缩小。如果原先的内存卡无法改变大小,realloc将分配另一块正确大小的内存,并把原先那块内存的内容复制到新的块上。
';
(八)字符串
最后更新于:2022-04-01 20:17:43
字符串是一种重要的数据类型,但C语言中没有显式的字符串数据类型。头文件string.h中包含了大多数对字符串的操作函数。因此,有字符串的操作时,一般会包含string.h头文件。
1、字符串的长度:对字符串进行的操作中,经常要对它进行求长度的操作。字符串的长度就是它所包含的字符个数。**size_t strlen(char const *string);
注意:strlen返回一个类型为size_t的值,它是一个无符号整数类型。在表达式中使用无符号数可能导致不可预料的结果。
**如下面两条表达式并不相等:**
**if(strlen(x) >= strlen(y)) .... /*按照预想的那样工作*/**
**if(strlen(x) - strlen(y) >= 0).../*永远为真,strlen的结果是个无符号数,所以操作符>=左边的表达式也将是无符号数*/**
~~~
size_t
strlen(char const *string)
{
int length;
for(length = 0; *string++ != '\0';)
length++;
return length;
}
~~~
2、复制字符串:用于复制字符串的函数时strcpy,原型如下:char *strcpy(char *dst, char const *src); 它把参数src字符串复制到dst参数。dst参数的以前内容将被覆盖掉并丢失。即使新的字符串比dst原先的内存更短,由于新字符串以NUL字节结尾,所以老字符串最后剩余的几个字符也会被有效地删除,(其实并为被删除,可以使用地址访问)。
我们必须确保目标字符数组的空间足以容纳需要复制的字符串。如果字符串比数组长,多余的字符仍被复制,它们将覆盖原先存储于数组后面的内存空间的值。在使用该函数前确保目标参数足以容纳源字符串,就可以避免大量的调试工作。
~~~
#include
#include
int
main(int argc, char **argv)
{
char message[] = "Original message";
printf("%c.\n", message[2]);//结果是Original中的i
strcpy(message, "Different");
printf("%s.\n", message);/*结果是Different,后面的message因为NUL的原因未显示*/
printf("%c.\n", message[2]);//结果是Different中的f
printf("%c.\n", message[12]);//结果是message中的s
return 0;
}
~~~
3、连接字符串:把一个字符串添加(连接)到另一个字符串的后面。原型:char *strcat(char *dst, char const *src); 该函数要求dst参数原先已经包含了一个字符串,它找到这个字符串的末尾,并把src字符串的一份拷贝添加到这个位置。同样应该确保目标字符数组剩余的空间足以保存整个源字符串。
~~~
#include
#include
int
main(int argc, char **argv)
{
char message[100];
strcpy(message, "Hello ");
strcat(message, "World.\n");
printf("message = %s",message);
return 0;
}
~~~
注意:strcpy和strcat函数都返回它们第1个参数的一份拷贝,就是指向目标字符数组的指针。在实际应用中,它们的返回值经常只是简单的被忽略。
4、字符串比较:比较两个字符串涉及对两个字符串对应的字符逐个进行比较,直到发现不匹配为止。
原型:int strcmp(char const *s1, char const *s2);
如果s1小于s2,返回值小于0;
如果s1大于s2,返回值大于0;
如果s1等于s2,返回值等于0;
5、其他字符串函数
char *strncpy(char *dst, char const *src, size_t len);向dst写入len个字符。如果strlen(src)的值小于len,dst数组就用额外的NUL字节填充到len长度。如果strlen(src)的值大于或等于len,那么只有len个字符被复制到dst中。注意,它的结果将不会以NUL字节结尾。
char *strncat(char *dst, char const *src, size_t len);
intstrncmp(char const *s1, char const *s2, size_t len);
';
(七)回调函数
最后更新于:2022-04-01 20:17:41
**想要全面的了解一个概念,必须至少了解它的三点:它是什么、它有什么用、它在什么时候用。对于回调函数,同样从三个方面了解它。**
**1、什么是回调函数?**
**2、回调函数该如何使用?**
**3、回调函数在什么情况下使用?**
**答案一:**
回调函数:见名知意,首先肯定的一点是它是一个函数,修饰词回调指的是程序员自己定义一个函数并实现它的程序内容,然后把这个函数的指针作为参数传递给其他函数(如系统函数)中,由其他函数(如系统函数)在运行时调用所实现的函数。函数是程序员自己实现的,但却是由其他函数(如系统函数)在运行时通过参数传递的方式调用的,这就是回调函数。简单一句话就是:由别人的函数运行期间来回调你实现的函数。
**答案二:**
**示例1:**
~~~
#include
#include
int fun1(void)
{
printf("hello world.\n");
return 0;
}
void callback(int (*Pfun)())
{
Pfun();
}
int
main(void)
{
callback(fun1);
}
~~~
**callback回调定义的函数fun1,传递给callback的是函数fun1的地址。fun1是一个不含参数返回值为整型的函数,如果fun含有参数,还想使用回调函数则可用下面的示例2。**
示例2:
~~~
#include
#include
int fun2(char *s)
{
printf("%s.\n", s);
return 0;
}
void callback(int (*Pfun)(char *), char *s)
{
Pfun(s);
}
int
main(void)
{
callback(fun2, "hello world");
return 0;
}
~~~
答案三(引用):
如果想知道回调函数在实际中有什么作用,先假设有这样一种情况,我们要编写一个库,它提供了某些排序算法的实现,如冒泡排序、快速排序、shell排序、shake排序等等,但为使库更加通用,不想在函数中嵌入排序逻辑,而让使用者来实现相应的逻辑;或者,想让库可用于多种数据类型(int、float、string),此时,该怎么办呢?可以使用函数指针,并进行回调。
';
(六)指针
最后更新于:2022-04-01 20:17:39
**引言:**
要想真正掌握C语言,掌握指针时必须的,可以说指针是C语言的灵魂。**
指针变量一定要先进行初始化,然后才能使用。初始化指要对指针变量进行赋值,将一个地址值赋值给指针变量。极为常犯的错误是:
**int *a;**
***a = 12; /*未对它进行初始化,没有办法预测12这个值将存储于什么地方*/**
**分析几个指针的例子:**
1、*d = 10 - *d; 两个间接访问操作。右边的间接访问作为右值使用,所以它的值是d所指向的位置所存储的值(a的值)。左边的简介访问作为左值使用,所以d所指向的位置(a)把赋值符右侧的表达式的计算结果作为它的新值。
2、*&a = 2;这条语句的意思是:把值2赋值给变量a。这条语句在功能上与a=2是相同的。但是,它涉及更多的操作。除非编译器知道你在干什么并丢弃额外的操作,否则它所产生的目标代码将会更大、更慢。
3、int a = 12; int *b = &a; int **c = &b;指针变量和其他变量一样,占据内存中某个特定的位置,所以用&操作符取得它的地址是合法的。
值的类型并非值本身所固有的一种特性,而是取决于它的使用方式。比如在算数运算中,char型值可以被强制转换为int型的值。一个变量的值就是分配给这个变量的内存位置所存储的数值。通过一个指针访问它所指向的地址的过程称为简介访问。用于执行简介访问的操作符是单目操作符*。
';
(五)setjmp和longjmp
最后更新于:2022-04-01 20:17:36
**引言:**
通常要在函数内部实现跳转,会用到goto语句。如果想要实现不同函数间的跳转,就要用到setjmp和longjmp语句的组合来完成。
**理论分析:**
setjmp和longjmp组合可以实现跳转,与goto语句有相似的地方。但有以下不同:
1、用longjmp只能跳回到曾经到过的地方。在执行setjmp的地方仍留有一个过程活动记录。从这个角度看,longjmp更像是“从何处来”而不是“往何处去”。longjmp接收一个额外的整型参数并返回它的值,这可以知道是由longjmp转移到这里的还是从上一条语句执行后自然而然来到这里的。
2、goto语句不能跳出C语言当前的函数,而longjmp可以跳的更远,可以跳出函数,甚至跳到其他文件中的函数中。
setjmp(jmp_buf j)必须首先被调用。它表示“使用变量j记录现在的位置”。函数返回零。
longjmp(jmp_buf j, int i)可以接着被调用。它表示“回到j所记录的位置,让它看上去像从原先的setjmp函数返回一样。函数返回i。”
setjmp/longjmp最大的用途是错误恢复。但跟goto一样,使得程序难以理解和调试。如果不是出于特殊需要,最好避免使用它们。
**使用步骤:**
1、包含头文件setjmp.h,定义jmp_buf类型的变量,如jmp_buf buf;
2、调用setjmp(buf);该函数返回0。
3、在想要跳转的地方调用longjmp(buf, i);该函数返回整数i,实现跳转。
**示例源码:**
~~~
#include
#include
jmp_buf buf;
void
fun(void)
{
printf("in fun()\n");
longjmp(buf, 1);
}
void
main(void)
{
if(setjmp(buf)){
printf("back in main.\n");
}else{
printf("first time through\n");
fun();
}
}
~~~
**编译:gcc test.c**
**执行:./a.out**
**结果:**
**first time through
in fun()
back in main.**
';