《head first c》学习笔记及C语言有用的函数
动态库及静态库
动态库是在运行时链接程序,静态库一旦链接,就不能改变。
#include <>查找包括/usr/include在内的标准目录
gcc -fPIC 令目标代码位置无关,例如: gcc -c -fPIC test.c -o test.o(因为头文件在标准目录中,所以不需要加上-I选项)
gcc -i<路径名> 会链接标准目录( 例如/usr/lib)下的文件
gcc -I<路径> 表示头文件在<路径>下
gcc -L<路径名> 在标准lib目录下添加目录
gcc -shared 把目标文件转化为动态库
gcc -s 生成汇编文件
ar 命令创建目标文件的存档(静态库)
动态库的后缀名有.so(linux系统) .dylib(Mac系统) .dll.a(windows下的cygwin)
有用的函数
stdlib.h
getenv() 获取环境变量
system() 调用其他程序,接受一个字符串参数,这个函数容易出错。
unistd.h
exec() 函数运行其他程序替换当前进程,例如:execl(<程序>,<命令行参数>,<命令行参数>,NULL),命令行参数可以有很多,最后必须有NULL结尾列表。
列表函数execl()、execlp()、execle() 数组函数execv()、execvp()、execve()
errno.h
errno 变量定义标准错误码, EPERM=1 不允许操作
ENOENT=2 没有该文件或目录
ESRCH=3 没有该进程
string.h
strerror() 函数查询标准错误消息,例如:puts(strerror(errno)); 通过标准错误的标号,获得错误的描述字符串 ,将单纯的错误标号转为字符串描述,方便用户查找错误。
fork() 克隆进程,例如pid_t pid = fork();然后在新的进程里exec(),如果fork返回0,说明代码运行在子进程中,如果返回-1说明克隆出现问题还是处于父进程。
如果子进程需要修改储存器中的数据,系统会为他复制一份,这叫写时复制(copy on time)。pid_t 是不同系统保存的进程ID,在不同系统用不同的整型。
strstr(char* s1, char * s2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL
char *strtok(char s[], const char *delim); 分解字符串为一组字符串。s为要分解的字符,delim为分隔符字符(如果传入字符串,则传入的字符串中每个字符均为分割 符)。首次调用时,s指向要分解的字符串,之后再次调用要把s设成NULL。
char *strcpy(char* dest, const char *src) 把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间
int strcmp(const char *s1,const char *s2)当s1
char *fgets(char *buf, int bufsize, FILE *stream) *buf: 字符型指针,指向用来存储所得数据的地址。bufsize: 整型数据,指明存储数据的大小。*stream: 文件结构体
指针,将要读取的文件流。
int fputs(char *str,FILE *fp) str是字符型指针,可以是字符串常量,或者存放字符串的数组首地址。fp通过打开文件函数fopen()获得的。
stdio.h
int sscanf(const char *buffer,const char *format,[argument ]...)
参数buffer:储存的数据, format:格式控制字符串, argument:选择性设定字符串。
进程间通信
可以用">"运算符吧标准输出重定向到文件。"2>" 重定向错误输出。"2>&1" 把标准输出和标准错误重定向到一个地方,
进程用文件描述符来表示数据流(就是一个数字 - -),数字0 代表标准输入-键盘, 1 代表标准输出-屏幕, 2 代表标准错误-屏幕,3 代表数据库连接。
fileno()函数返回文件描述符,例如:FILE *my_file = fopen("guitar.mp3", "r"); int descriptor =fileno(my_file);
dup2()函数修改一个注册的描述符重新指向另一个,例如:dup2(4, 3); //将4号注册的文件指针同时连接到3号指针。
exit() 函数系统调用结束进程,例如:exit(1);//把程序结束,并且退出码为1。
waitpid()函数会等到子进程结束以后才返回。例如:waitpid(pid, &pid_status, 0); //pid是父进程fork后得到的子进程号;pid_status是进程信息,因为waitpid()需要修改他,所以是指针;0是选项可以使用man waitpid()查询,当为0时,函数等待进程结束。pid_status的值传给WEXITSTATUS()宏来处理,因为pid_status只有前8位表示进程的退出状态。
pipe()函数创建管道,例如:int fd[2]; piple(fd); //fd[0]是管道读取端,fd[1]是管道写入端。
close(fd[0])关闭管道读取端。
中断
sigaction是一个结构体,用于告诉系统收到某个信号时应该调用那个函数,sigaction包装的函数叫处理器。sigaction(signal_no, &new_action, &old_action)函数用来注册sigaction。
中断信号有一个信号映射表,信号SIGINT 的处理函数调用exit(), SIGINT的值是2。当在输入名字时,使用Ctrl-C,操作系统自动向进程发送中断信号(SIGINT),我们注册的sigaction结构体来处理这个信号,sigaction中国年有一个指向diediedie函数的指针,程序通过调用这个函数,显示消息并调用exit。
#include
#include
#include
void diediedie(int sig)//信号处理器,返回void
{
puts ("Goodbye bob!");
exit(1);
}
int catch_signal(int signal, void (*handler)(int))
{
struct sigaction action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = 0;
return sigaction(signal, &action, NULL);
}
int main(){
if (catch_signal(SIGINT, diediedie)== -1) {
fprintf(stderr, "Can't map the handler\n" );
exit(2);
}
char name[20];
printf("Enter your name: \n" );
fgets(name, 30, stdin);
printf("Hello %s\n", name);
return 0;
}
kill的命令能够结束程序,并发送信号,需要相关进程的pid,kill -KILL一定能终止进程。
raise()函数能够发送信号,例如:raise(SIGINT);
alarm(120)函数能够在120秒后发出SIGALRM信号,不要同时使用sleep()和alarm()函数,他们使用间隔计数器。
可变参数函数
va_start、va_arg、va_end是宏,在编译前预处理器根据这些指令插入相关代码。
#include
void print_ints(int args, ...)//...表示省略符号
{
va_list ap;//va_list用来保存传给函数的其他参数
va_start(ap,args);//需要把最后一个普通参数告诉从哪里开始
int i;
for(i = 0; i < args; i++){
printf("argument: %i\n", va_arg(ap, int));//va_arg可以读取参数
}
va_end(ap);
}
网络与套接字
网络连接四部曲:绑定(Bind)、监听(Listen)、接受(Accept)、开始(Begin)
getaddrinfo()获得域名地址,需要头文件
使用send()向套接字发送数据,用recv()从套接字读数据,服务器连接本地端口,客户端连接远程端口,客户端和服务器使用套接字通信。
线程
对于共享的变量有些时候需要对它上锁。
pthread_mutex_t beers_lock = PTHREAD_MUTEX_INITIALIZER;//创建互斥锁
void* drink_lots(void *a){
int i;
pthread_mutex_lock(&beers_lock);//只允许一个线程通过
for (i = 0; i < 1000000; i++) {
beers = beers - 1;
}
pthread_mutex_unlock(&beers_lock);//其他线程也能使用
printf("%d\n", beers);
}
开发工具
gdb:允许在程序运行期间研究它的代码。
gprof:分许程序性能,找到程序的瓶颈。
gcov:gnu覆盖率测试工具,用来检查代码哪些部分运行,哪些部分没有运行。