函数,它们的分工和人类的分工没什么不同----小话c语言(9)

来源:互联网 发布:詹姆斯历史赛季数据 编辑:程序博客网 时间:2024/06/05 03:21

[Mac-10.7.1 Lion Intel-based x64 gcc4.2.1]


Q: 对于ctype.h中的isspace和isblank函数,一直没怎么分清楚,到底它们的不同在哪里?

A: 我们做个测试:

#include <stdio.h>#include <ctype.h>int main(){    int i;    for(i = 0; i < 128; ++i)    {        if(isspace(i))            printf("%d is space\n", i);        if(isblank(i))            printf("%d is blank\n", i);    }    return 0;}

编译运行:

可以看到ascii码9, 10, 11, 12, 13, 32是space;  9, 32是blank.

查看isspace和isblank说明:


Q: 字符串转换成浮点数可以用atof, 那浮点数转换成字符串呢?

A: 可以使用sprintf函数组装字符串,也可以用gcvt函数实现。如下例子:

#include <stdio.h>#definePRINT_D(intValue)printf(#intValue" is %lu\n", (intValue));int main(){    char buf[32];    sprintf(buf, "%lf", 2.345);    printf("%s\n", buf);        return 0;}
运行结果:


也可以用gcvt函数: 

char *gcvt(double value, int ndigit, char *buf);

其中ndigit是有效数字个数,buf为保存转换后的字符串;

如下例子:

#include <stdio.h>#include <stdlib.h>#definePRINT_D(intValue)printf(#intValue" is %lu\n", (intValue));int main(){    char buf[32];    double d = 1.234567;    gcvt(d, 6, buf);    printf("%s\n", buf);        return 0;}
运行结果:

另外,ecvt, fcvt也可以实现类似功能。


Q: 在申请内存的时候,经常申请ok了,然后调用memset将内存空间设置为0,有没有更简洁的形式?

A: 有的,calloc可以完成这个功能。

原型为:

void *calloc(size_t count, size_t size);

如下代码:

#include <stdio.h>#include <stdlib.h>#definePRINT_D(intValue)printf(#intValue" is %lu\n", (intValue));int main(){    char *p = (char *)calloc(128, 1);    if(p)    {        int i = 0;        for(; i < 128; ++i)        {            if(p[i] != 0)                printf("the calloc memory is not zero!\n");        }        free(p);    }        return 0;}
当然,正常情况下,它什么也不会输出。


Q: 经常看到,申请一块缓存,大小为4KB,它就是系统的一页大小么?系统有相关的api么?

A: 有相关api.  getpagesize即可获取它的大小。

int  getpagesize(void);
如下例子:
#include <stdio.h>#include <unistd.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));int main(){    int page_size = getpagesize();    PRINT_D(page_size)        return 0;}
运行结果:


Q: 经常听到内存映射,它的作用到底是什么?

A: mmap函数就可以实现内存映射。它的用途之一就是当需要频繁操作文件的时候,可以将文件映射到内存中,在内存中读写这些可以提高效率。原型如下:

void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);

它需要头文件sys/mman.h的支持。

示例代码如下:

addr表示被映射的地址,可以传入NULL,返回值是系统分配的内存映射地址;

len表示文件多大的数据被映射到内存;

prot表示映射的读写或可执行方式,如PROT_READ是以读的方式;

flags表示映射的一些特性,比如MAP_SHARED表示允许其它映射此文件的进程共享;

fd表示映射的文件句柄;

offset表示文件映射的偏移量,必须是分页大小的整数倍;

现在写一个测试代码:

#include <stdio.h>#include <unistd.h>#include <sys/mman.h>#include <sys/stat.h>#include <fcntl.h>#include <string.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));int main(){    int     ret;    char    *map_p;    struct  stat st;        long long     size = getpagesize();    // open file by read and write, the write property is used when modifying the mapped data    int     fd = open("test", O_RDWR);    if(fd < 0)    {        perror("open file error");        return -1;    }    ret = fstat(fd, &st);    if(ret < 0)    {        perror("fstat file error");        close(fd);        return -1;    }        // if the size is smaller than file's size, then use the file (size + 1)    if(st.st_size >= size)        size = st.st_size + 1;        // map the file fd    map_p = (char *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);    if(map_p == MAP_FAILED)    {        perror("mmap error");        close(fd);        return -1;    }    close(fd);  // when mapped ok, the fd can be closed.        printf("file content:%s\n", map_p);    // modify the first char    map_p[0] = 'a';        // sync the modify data    ret = msync(map_p, size, MS_SYNC);    if(ret < 0)    {        perror("msync error");        munmap(map_p, size);        return -1;    }        printf("modify file content:%s\n", map_p);    // unmap    munmap(map_p, size);    return 0;}

我们创建一个含有hello内容的文件:

-n参数表示不把最后的换行字符\n也写入文件;

用ls -l | grep test命令确认文件内容为5字节:

可以看到确定是5字节。

然后运行上面的程序,观察控制台输出:

用cat  test命令确认文件内容已被修改:


Q: 上面的代码中map_p用char *输出,结尾不是没设定\0么,怎么会正确结束?

A: 这是因为进行mmap内存映射的时候,会自动将映射后所有没有被映射的空间填充0,所以代码中没有显式设置。


Q: 关于时间的几个函数,感觉不容易分清楚,究竟如何理解它们?

A: 首先说下time函数:

time_t  time(time_t *tloc);
它返回的是1970年1月1日的UTC时间从0时0分0秒到现在的秒数。如果tloc非空,那么它也会存储在tloc对应的地址里。

time_t格式一般被定义为long或者unsigned long这种格式。

一个例子:

#include <stdio.h>#include <time.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));#definePRINT_L(longValue)printf(#longValue" is %ld\n", (longValue));int main(){    time_t  ret = time(NULL);    PRINT_L(ret)    return 0;}

运行结果:


而这种格式的时间当然不直观,所以有localtime来转换:

struct tm *localtime(const time_t *clock);

它返回一个tm结构的指针。可以利用time得到的返回值作为参数:

#include <stdio.h>#include <time.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));#definePRINT_L(longValue)printf(#longValue" is %ld\n", (longValue));int main(){    time_t  ret = time(NULL);    struct tm *tm_ret;        PRINT_L(ret)        tm_ret = localtime(&ret);    PRINT_D(tm_ret->tm_year)    PRINT_D(tm_ret->tm_mon)    PRINT_D(tm_ret->tm_mday)    PRINT_D(tm_ret->tm_hour)    PRINT_D(tm_ret->tm_min)    PRINT_D(tm_ret->tm_sec)        return 0;}

运行结果:


Q: 刚刚是time_t结构转换成struct tm结构,有相反的过程么?

A: 是的,可以使用mktime函数实现这个转换。

time_t mktime(struct tm *timeptr);

就利用上面的代码:

#include <stdio.h>#include <time.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));#definePRINT_L(longValue)printf(#longValue" is %ld\n", (longValue));int main(){    time_t  ret = time(NULL);    struct tm *tm_ret;        PRINT_L(ret)        tm_ret = localtime(&ret);    PRINT_D(tm_ret->tm_year)    PRINT_D(tm_ret->tm_mon)    PRINT_D(tm_ret->tm_mday)    PRINT_D(tm_ret->tm_hour)    PRINT_D(tm_ret->tm_min)    PRINT_D(tm_ret->tm_sec)        PRINT_L(mktime(tm_ret))        return 0;}

运行结果:

可以看到,mktime返回的和程序开始time函数得到的time_t的值是一样的。


Q: 使用struct tm 结构来获取年月日时分秒,感觉有点复杂,有更简单的么?

A: 有的,可以使用ctime和asctime来得到时间相关的char *字符串,这个要更简洁一点。

char *ctime(const time_t *clock);char *asctime(const struct tm *timeptr);

可以看到,这两个函数有不同之处,参数不同。示例代码:

#include <stdio.h>#include <time.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));#definePRINT_L(longValue)printf(#longValue" is %ld\n", (longValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str));int main(){    time_t  ret = time(NULL);    struct tm *tm_ret;        PRINT_L(ret)        tm_ret = localtime(&ret);    PRINT_D(tm_ret->tm_year)    PRINT_D(tm_ret->tm_mon)    PRINT_D(tm_ret->tm_mday)    PRINT_D(tm_ret->tm_hour)    PRINT_D(tm_ret->tm_min)    PRINT_D(tm_ret->tm_sec)        PRINT_L(mktime(tm_ret))        PRINT_STR(ctime(&ret))    PRINT_STR(asctime(tm_ret))        return 0;}
运行结果:



Q: 关于内存拷贝,memcpy和memmove到底有什么区别?

A: 区别在于memmove处理了重复内存区域的拷贝,memcpy没有处理内存区域重复可能导致拷贝结果错误,这种情况下结果是不确定的。所以最好使用memmove,如果可以确定内存区域不重复,也可以直接使用memcpy.


Q: 关于系统信息,如何获取系统的用户组信息?

A: getgrent函数可以实现。

group结构如下:

struct group {char*gr_name;/* [XBD] group name */char*gr_passwd;/* [???] group password */gid_tgr_gid;/* [XBD] group id */char**gr_mem;/* [XBD] group members */};

示例代码:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <grp.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));#definePRINT_L(longValue)printf(#longValue" is %ld\n", (longValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str));int main(){    struct group *data;    while(data = getgrent())    {        printf("%s:%s:%d\n", data->gr_name, data->gr_passwd, data->gr_gid);    }    endgrent();    return 0;}

运行结果(不同系统的结果会不同):

_amavisd:*:83_appowner:*:87_appserveradm:*:81_appserverusr:*:79_appstore:*:33_ard:*:67_atsserver:*:97_calendar:*:93_carddav:*:206_ces:*:32_clamav:*:82_coreaudiod:*:202_cvms:*:212_cvs:*:72_detachedsig:*:207_devdocs:*:59_developer:*:204_devicemgr:*:220_dovenull:*:227_dpaudio:*:215_guest:*:201_installassistant:*:25_installer:*:96_jabber:*:84_keytabusers:*:30_lda:*:211_locationd:*:205_lp:*:26_lpadmin:*:98_lpoperator:*:100_mailman:*:78_mcxalr:*:54_mdnsresponder:*:65_mysql:*:74_netbios:*:222_netstatistics:*:228_networkd:*:24_odchpass:*:209_pcastagent:*:55_pcastfeedadmins:*:223_pcastlibrary:*:225_pcastserver:*:56_podcastlibraryfeedadmins:*:226_postdrop:*:28_postfix:*:27_postgres:*:216_qtss:*:76_sandbox:*:60_screensaver:*:203_scsd:*:31_securityagent:*:92_serialnumberd:*:58_softwareupdate:*:200_spotlight:*:89_sshd:*:75_svn:*:73_teamsserver:*:94_timezone:*:210_tokend:*:91_trustevaluationagent:*:208_unknown:*:99_update_sharing:*:95_usbmuxd:*:213_uucp:*:66_warmd:*:224_webauthserver:*:221_windowserver:*:88_www:*:70_xgridagent:*:86_xgridcontroller:*:85access_bpf::501accessibility:*:90admin:*:80authedusers:*:50bin:*:7certusers:*:29com.apple.access_screensharing-disabled::101com.apple.access_screensharing::401com.apple.sharepoint.group.1::402com.apple.sharepoint.group.2::403consoleusers:*:53daemon:*:1dialer:*:68everyone:*:12group:*:16interactusers:*:51kmem:*:2localaccounts:*:61macports::502mail:*:6messagebus:*:500netaccounts:*:62netusers:*:52network:*:69nobody:*:-2nogroup:*:-1operator:*:5owner:*:10procmod:*:9procview:*:8smmsp:*:102staff:*:20sys:*:3tty:*:4utmp:*:45wheel:*:0nobody:*:-2nogroup:*:-1wheel:*:0daemon:*:1kmem:*:2sys:*:3tty:*:4operator:*:5mail:*:6bin:*:7procview:*:8procmod:*:9owner:*:10everyone:*:12group:*:16staff:*:20_networkd:*:24_installassistant:*:25_lp:*:26_postfix:*:27_postdrop:*:28certusers:*:29_keytabusers:*:30_scsd:*:31_ces:*:32_appstore:*:33utmp:*:45authedusers:*:50interactusers:*:51netusers:*:52consoleusers:*:53_mcxalr:*:54_pcastagent:*:55_pcastserver:*:56_serialnumberd:*:58_devdocs:*:59_sandbox:*:60localaccounts:*:61netaccounts:*:62_mdnsresponder:*:65_uucp:*:66_ard:*:67dialer:*:68network:*:69_www:*:70_cvs:*:72_svn:*:73_mysql:*:74_sshd:*:75_qtss:*:76_mailman:*:78_appserverusr:*:79admin:*:80_appserveradm:*:81_clamav:*:82_amavisd:*:83_jabber:*:84_xgridcontroller:*:85_xgridagent:*:86_appowner:*:87_windowserver:*:88_spotlight:*:89accessibility:*:90_tokend:*:91_securityagent:*:92_calendar:*:93_teamsserver:*:94_update_sharing:*:95_installer:*:96_atsserver:*:97_lpadmin:*:98_unknown:*:99_lpoperator:*:100_softwareupdate:*:200_guest:*:201_coreaudiod:*:202_screensaver:*:203_developer:*:204_locationd:*:205_detachedsig:*:207_trustevaluationagent:*:208_odchpass:*:209_timezone:*:210_lda:*:211_cvms:*:212_usbmuxd:*:213_postgres:*:216_devicemgr:*:220_webauthserver:*:221_netbios:*:222_pcastfeedadmins:*:223_warmd:*:224_pcastlibrary:*:225_podcastlibraryfeedadmins:*:226_dovenull:*:227_netstatistics:*:228
同时,getgrgid函数可以指定gid得到组信息。


Q: 获取用户信息呢?

A: 可以使用getuid或者geteuid得到需要的用户ID。

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <grp.h>#include <unistd.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));#definePRINT_UD(intValue)printf(#intValue" is %u\n", (intValue));#definePRINT_L(longValue)printf(#longValue" is %ld\n", (longValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str));int main(){    PRINT_UD(getuid())    PRINT_UD(geteuid())    return 0;}

运行结果(依赖于系统):

笔者当时系统的用户为xichen, 使用id  xichen得到此用户的信息:

可以看到uid确实是501.至于uid和euid的区别,这里就不做分析了。同理,如果要获取组id, 可以使用getgid和getegid函数。


Q: 需要获取密码信息,如何得到?

A: 可以使用getpwent来获得。

struct passwd *getpwent(void);
它的使用类似getgrent函数。


Q: 关于文件操作,creat函数和open函数有什么区别?

A: 注意,它不是create,是creat.  creat相当于特殊的open函数,它和open的关系如下:

如下示例代码:

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <grp.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/fcntl.h>#definePRINT_D(intValue)printf(#intValue" is %d\n", (intValue));#definePRINT_UD(intValue)printf(#intValue" is %u\n", (intValue));#definePRINT_L(longValue)printf(#longValue" is %ld\n", (longValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str));int main(){    int ret = creat("test", S_IRUSR);    if(ret < 0)    {        perror("creat file error");        return -1;    }    return 0;}
执行后,它会会得到一个空文件test.


Q: 关于文件操作,dup和dup2到底有什么作用?

A: 原型如下:

int  dup(int fildes);int  dup2(int fildes, int fildes2);

dup可以复制给定的文件描述符,返回新的文件描述符,两个文件描述符共享相同的系统内部数据结构;

dup2可以将第一个参数指定的文件描述符的信息恢复到第二个参数指定的文件描述符,关于基本输入输出那篇文章最后已经使用了dup2进行恢复,这里不做介绍。

#include <stdio.h>#include <unistd.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));int main(){    int fd = dup(fileno(stdout));    FILE *file;    PRINT_D(fd)        // use stdout to output    fprintf(stdout, "uses fd:%d output\n", fileno(stdout));        // get the FILE * by file descriptor    file = fdopen(fd, "w");    if(!file)    {        perror("fdopen error");        close(fd);        return -1;    }        // use new fd to output    fprintf(file, "uses fd:%d output\n", fd);        fclose(file);    return 0;}

可以看到,dup产生的新文件描述符值为3,也可以和stdout(文件描述符对应于1)一样进行标准输出。


Q: fflush和fsync到底有什么区别?

A: fflush只是将数据写到内核缓冲区,并不一定写到设备上;fsync会将内核缓冲区中需要被写到设备上的数据写到设备上;


Q: 有的时候需要生成一个临时文件,自己去创建可能重复,可能覆盖已有的文件,想要生成一个随机的文件,有相关函数么?

A: 是的。mkstemp函数可以完成此功能。

int mkstemp(char *template);

示例代码:

#include <stdio.h>#include <unistd.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str));#define ERR_PERROR_RETURN(str) \        if(ret < 0)     \        {       \            perror(#str" error");   \            return -1;              \        }   int main(){    // mkstemp's first argument should has postfix XXXXXX, or the result will be not what you need.    char buf[] = "hello-XXXXXX";    int ret = mkstemp(buf);    ERR_PERROR_RETURN(mkstemp)    PRINT_STR(buf)        return 0;}

运行结果:

用ls -l命令查看是否创建了上面的文件:

可以看到,确实创建了这样的文件。


Q: 文件操作函数clearerr函数该在何时使用?

A: 当操作文件后出现了错误但是又需要继续操作时可以用clearerr清除错误标志位,以避免后面的文件操作因为ferror的判断而导致错误。

#include <stdio.h>#include <unistd.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str));#define ERR_PERROR_RETURN(str) \        if(ret < 0)     \        {       \            perror(#str" error");   \            return -1;              \        }   int main(){    FILE *fp = fopen("test", "r");    if(fp)    {        int ch = 'a';        fputc(ch, fp);        if(ferror(fp))        {            printf("ferror...\n");        }        ch = fgetc(fp);        printf("%c\n", ch);        PRINT_D(ferror(fp))                fclose(fp);    }        return 0;}

运行:

可以看到出现ferror后,没有清除错误标志位,后面判断ferror依然为错误。所以,可以增加clearerr函数。

#include <stdio.h>#include <unistd.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str));#define ERR_PERROR_RETURN(str) \        if(ret < 0)     \        {       \            perror(#str" error");   \            return -1;              \        }   int main(){    FILE *fp = fopen("test", "r");    if(fp)    {        int ch = 'a';        fputc(ch, fp);        if(ferror(fp))        {            printf("ferror...\n");            clearerr(fp);        }        ch = fgetc(fp);        printf("%c\n", ch);        PRINT_D(ferror(fp))                fclose(fp);    }        return 0;}
运行:


发现最后ferror判断已经正常了。


Q: 关于文件缓冲区操作,有setbuf, setbuffer, setvbuf, setlinebuf, 它们究竟什么关系?

A: 现依依介绍。下面先举个关于setvbuf的例子:

int  setvbuf(FILE *restrict stream, char *restrict buf, int type, size_t size);
第一个参数为文件指针;第二个参数为buf地址;第三个参数为缓冲区类型,有无缓冲,行缓冲和全缓冲3中方式;第四个参数为缓冲区大小。

#include <stdio.h>#include <unistd.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str)); #define PAUSE       { getc(stdin);    rewind(stdin); }static  char    buf[BUFSIZ];int main(){    setvbuf(stdout, buf, _IOFBF, 6);    printf("hello");    PAUSE        return 0;}
如上,设置标准输出的缓冲区为buf, 且大小为6, 采用全缓冲;所以后面执行的时候,输出hello,它是5个字节,没有达到缓冲区大小6,所以不会立即输出;执行到PAUSE,任意输入一个字符,hello会被输出。

执行后,随意输入一个字符,比如上面的a,然后换行,hello才被显示。

如果将代码setvbuf最后的参数改为5,那么hello会在程序运行后便输出。

#include <stdio.h>#include <unistd.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str)); #define PAUSE       { getc(stdin);    rewind(stdin); }static  char    buf[BUFSIZ];int main(){    setvbuf(stdout, buf, _IOFBF, 5);    printf("hello");    PAUSE        return 0;}
运行:


接着介绍setbuf函数:

void  setbuf(FILE *restrict stream, char *restrict buf);
它实际上就等同于如下代码:

接下来是setlinebuf函数:

int  setlinebuf(FILE *stream);
它等同于:

不过它的缓冲区大小取决于调用者。

setbuffer函数:

void  setbuffer(FILE *stream, char *buf, int size);
类似的,下面为测试代码:

#include <stdio.h>#include <unistd.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str)); #define PAUSE       { getc(stdin);    rewind(stdin); }static  char    buf[BUFSIZ];int main(){    setbuffer(stdout, buf, 5);    printf("hello");        PAUSE        return 0;}
运行结果就不写了。


Q: 关于进程控制的函数exit和_exit到底有什么区别?

A: 区别在于_exit退出进程前不会刷新缓冲区、关闭流等操作,而exit函数会。如下代码(文件名为testForC.c):

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str)); int main(){    printf("hello");    exit(0);}

运行:

如果使用_exit函数:

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str)); int main(){    printf("hello");    _exit(0);}
运行:

可以发现运行它并没有输出缓冲区中的hello.


Q: 如何获取进程的优先级?

A: 使用getpriority即可。

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));#define PRINT_STR(str)      printf(#str" is %s\n", (str)); #define PAUSE       { getc(stdin);    rewind(stdin); }int main(){    pid_t   pid = getpid();    int     priority = getpriority(PRIO_PROCESS, pid);    PRINT_D(priority)    PAUSE        return 0;}

运行后:

使用如下命令得到它的优先级:ps -l

可以看到testForC进程的优先级列NI也为0.和上面得到的是一致的。

如果要修改优先级,可以使用setpriority函数或者nice函数。


xichen

2012-5-16 17:53:01