UC编程基础知识整理

Published: 2014-02-19

Tags: C/C++

本文总阅读量
  1. 字符读取与行读取

  2. 环境变量的查询与修改

  3. 输入输出重定向(标准IO)

  4. 输入输出重定向(系统调用)

  5. 通过fwrite()和write()比较标准库函数和系统调用的速度


1. 字符读取与行读取

下面的演示程序实现从/etc/passwd文件中提取用户名,打印到屏幕上并保存在copyname.txt文件中 使用的函数是getc()、putc()、putchar()

#include <stdio.h>

int main()
{
    FILE *fpr, *fpw;
    int c = 0, f = 0;
    /* 以下打开源文件 */
    if((fpr = fopen("/etc/passwd", "r")) == NULL)
    {
        printf("open file /etc/passwd failes.\n");
        return;
    }
    /* 以下打开目标文件*/
    if((fpw = fopen("./copyname.txt", "w")) == NULL)
    {
        printf("open file ./copyname.txt failed.\n");
        fclose(fpr);
        return;
    }
    while((c = getc(fpr)) != EOF)
    {
        /* 字符已经读取到了c */
        if(f == 0)
        {
            if(c != ':')
                putchar(putc(c, fpw));
            else
                f = 1;
        }
        else if(c == '\n')
        {
            f = 0;
            putchar(putc(c, fpw));
        }
    }
    fclose(fpr);
    fclose(fpw);

    return 0;
}

下面这个演示程序是按行来获取数据的

#include <stdio.h>

int main()
{
    FILE *fpr, *fpw;
    char buf[1024], *p1, *p2;
    /* 打开源文件 */
    if((fpr = fopen("/etc/passwd", "r")) == NULL)
    {
        printf("open /etc/passwd file failed.\n");
        return;
    }
    /* 打开目标文件 */
    if((fpw = fopen("./copynameid.txt", "w")) == NULL)
    {
        printf("open ./copynameid.txt failed.\n");
        fclose(fpr);
        return;
    }

    memset(buf, 0, sizeof(buf));

    while(fgets(buf, sizeof(buf), fpr) != NULL)
    {
        /* p1指向第一个":",p2指向第二个":" */
        if((p1 = strstr(buf, ":")) == NULL) break;
        if((p2 = strstr(p1+1, ":")) == NULL) break;
        p1++; p2++;
        /* p1指向第二个域密码字段,p2指向第三个域用户ID字段 */
        /* 以下代码移动字符串内容,将ID字段的内容移动到用户名字段后 */
        while(*p2 != ':')
        {
            *p1 = *p2;
            p1++; p2++;
        }
        *p1 = 0;
        /* 屏幕输出 */
        puts(buf);
        /* 文件输出 */
        fputs(buf, fpw);
        fputs("\n", fpw);
        /* 清楚内存 */
        memset(buf, 0, sizeof(buf));
    }
    fclose(fpr);
    fclose(fpw);

    return 0;
}

输出省略。这个程序很有意思的地方是在buf中利用指针直接判断并修改数据

2. 环境变量的查询与修改

每个程序中都维护一个指向环境变量的指针char **environ; 子进程会从父进程继承环境变量。子进程环境变量的修改不一定会影响父进程 无关的多个进程之间修改环境变量不会互相影响

打印环境变量

#include <stdio.h>

extern char **environ;
int main()
{
    while(*environ)
    {
        printf("%s\n",*environ++);
    }
    return 0;
}

查询环境变量

多数时候,只是查看一个环境变量的值。可以使用 char * getenv(const char *name);函数

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char *path=getenv("PATH");
    printf("path=%s\n",path);
    return 0;
}

这个和shell中用echo $PATH打印出来的效果是一样的

设置环境变量

putenv 定义函数 int putenv(const char * string); 表头文件 #include 函数说明 putenv()用来改变或增加环境变量的内容。 参数 string的格式为name=value,如果该环境变量原先存在,则变量内容会依参数string改变,否则此参数内容会成为新的环境变量。

#include <stdio.h>
#include <stdlib.h>

int main()
{
        char *HELLO;
        putenv("HELLO=hello");
        HELLO=getenv("HELLO");
        printf("HELLO=%s\n",HELLO);
        return 0;
}

修改环境变量

修改PATH环境变量加上HOME目录,把修改后的环境变量打印出来。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char *path=getenv("PATH");
    char *home=getenv("HOME");
    int n=strlen(path)+strlen(home);
    char *str=malloc(n+2);

    sprintf(str,"%s:%s",home,path);
    printf("str=%s\n",str);

    setenv("PATH",str,1);
    path=getenv("PATH");
    printf("new path=%s\n",path);
    free(str);
    return 0;

}

setenv与putenv区别

函数定义:int putenv(const char * string); putenv()用来改变或增加环境变量的内容。 参数string的格式为name=value,如果该环境变量原先存在,则变量内容会依参数string改变,否则此参数内容会成为新的环境变量。 返回值:执行成功则返回0,有错误发生则返回-1。

函数定义:int setenv(const char *name,const char * value,int overwrite); setenv()用来改变或增加环境变量的内容。参数name为环境变量名称字符串。 参数value则为变量内容,参数overwrite用来决定是否要改变已存在的环境变量。如果overwrite不为0,而该环境变量原已有内容,则原内容会被改为参数value所指的变量内容。如果overwrite为0,且该环境变量已有内容,则参数value会被忽略。 返回值:执行成功则返回0,有错误发生时返回-1。

3. 输入输出重定向(标准IO)

#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;
    char szBuf[100];

    /* 将屏幕标准输出的内容重定向到文件 */
    if((fp = freopen("./1", "w", stderr)) == NULL)
    {
    perror("freopen");
    return EXIT_FAILURE;
    }

    /* stderr已经输出重定向,所有的错误输出都将写入文件./1中 */
    fputs("I like linux.", stderr);

    /* 关闭文件 */
    fclose(fp);

    /* 将标准输入由键盘输入更改为从文件./1中读入*/
    if((fp = freopen("./1" ,"r", stdin)) == NULL)
    {
    perror("freopen");
    return EXIT_FAILURE;
    }
    memset(szBuf, 0, sizeof(szBuf));
    /* stdin已经输入重定向, 所有内容都将写入文件./1中 */
    fgets(szBuf, sizeof(szBuf), stdin);
    printf("szBuf = [%s]\n", szBuf);
    /* 关闭文件 */
    fclose(fp);

    return 0;
}

上面的程序摘自《精通Unix下C语言编程与项目实践》 运行结果:

[sincerefly@localhost UC]$ ./a.out
szBuf = [I like linux.]
[sincerefly@localhost UC]$

也就是说,通过使用标准库中的freopen函数实现了输入输出的重定向 再来多看看两个例子,来自freopen函数的百度百科 举例1:

#include <stdio.h>
int main()
{
    /* redirect standard output to a file */
    if (freopen("D:\\OUTPUT.txt", "w", stdout)==NULL)
    fprintf(stderr, "error redirecting stdout\n");
    /* this output will go to a file */
    printf("This will go into a file.");
    /* close the standard output stream */
    fclose(stdout);
    return 0;
}

举例2:

#include <stdio.h>
int main()
{
    int i;
    if (freopen("./OUTPUT.txt", "w", stdout)==NULL)
    fprintf(stderr, "error redirecting\stdout\n");
    for(i=0;i<10;i++)
    printf("%3d",i);
    printf("\n");
    fclose(stdout);
    return 0;
}

从文件in.txt中读入数据,计算加和输出到out.txt中

举例3:

#include <stdio.h>
int main()
{
    freopen("in.txt","r",stdin); /*如果in.txt不在连接后的exe的目录,需要指定路径如./in.txt*/
    freopen("out.txt","w",stdout);/*同上*/
    int a,b;
    while(scanf("%d%d",&a,&b)!=EOF)
    printf("%d\n",a+b);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

若要返回到显示默认 stdout) 的 stdout,使用下面的调用: freopen( “CON”, “w”, stdout ); //输出到控制台”CON” 检查 freopen() 以确保重定向实际发生的返回值。 下面是短程序演示了 stdout 时重定向

举例4:

/*Compile options needed: none*/
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
    FILE *stream ; //将内容写到file.txt, "W"是写 ("r"是读)
    if((stream = freopen("file.txt", "w", stdout)) == NULL)
    exit(-1);
    printf("this is stdout output\n");
    stream = freopen("CON", "w", stdout);/*stdout 是向程序的末尾的控制台重定向*/
    printf("And now back to the console once again\n");
}

4. 输入输出重定向(系统调用)

在Unix下,系统重定向是使用dup和dup2函数完成的

在学习使用这两个函数之前,必须要搞懂一个概念就是文件描述符

摘自:《文件描述符和文件指针的区别》

文件描述符就是open文件时产生的一个整数,直到一个索引作用,它用于UNIX系统中,用于标识文件。

文件指针是指向一个FILE的结构体,这个结构体里有一个元素就是文件描述符。它用于ANSI C标准的IO库调用中,用于标识文件。

既然FILE中包含文件描述符元素,可以用fopen()直接获取指针fp,然后使用fp获得fp中所包含文件描述符fd的信息。

文件描述符应该是唯一的,但文件指针(值)却不是唯一的,但指向的对象却应该是唯一的。

FILE 中除了包含了fd信息,还包含了IO缓冲,所以可以理解为FILE是对fd的墙头,是C标准形式,所以FILE 比fd更适合跨平台,应该多用fopen在,少用open。

C语言文件指针与文件描述符之间可以相互转换:

int fileno(FILE *stream);

FILE fdopen(int fd, const char mode);

来看一下下图的实现过程

简单明了,不解释过多。再来看一下程序: 结合上图理解一下过程。

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    int fd, save_fd;
    char msg[] = "This is a test\n";

    fd = open("somefile", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
    if(fd<0) {
        perror("open");
        exit(1);
    }
    save_fd = dup(STDOUT_FILENO);
    dup2(fd, STDOUT_FILENO);
    close(fd);
    write(STDOUT_FILENO, msg, strlen(msg));
    dup2(save_fd, STDOUT_FILENO);
    write(STDOUT_FILENO, msg, strlen(msg));
    close(save_fd);

    return 0;
}

上面的程序按照流程图就很容易理解了,再多说两句就是STDOUT_FILENO也就是“1”的文件描述符 再有就是当改变输出方向后,不仅仅write函数,使用printf函数结果也都是一样的……haha

5. 通过fwrite()和write()比较标准库函数和系统调用的速度

fwrte是C标准库中提供的函数,是对write函数的扩展与封装,write则是Unix系统提供的函数。按照常理来讲,系统调用肯定比使用库快的多,但是事实正好相反 Why?原因就在于缓冲的问题,fwite会在内存中开辟缓冲区,来避免频繁的I/O,所以速度比系统调用要快(更多比较“open/read/write和fopen/fread/fwrite的区别”)

为了直观的比较一下fwrite和write的速度。我们来做一个简单的测试:

fwrite.c

#include <stdio.h>

int main(void)
{
    FILE *stream;
    int i;
    char str[] = "qwertyuiop\n";
    if ((stream = fopen("fwrite.txt", "w")) == NULL)
    {
        fprintf(stderr, "Cannot open output file.\n");
        return 1;
    }
    for(i=0; i<100000; i++)
    {
        fwrite(str, sizeof(str), 1, stream);
    }
    fclose(stream); /*关闭文件*/
    return 0;
}

运行时间:

real    0m0.009s
user    0m0.003s
sys     0m0.005s

write.c

#include <stdio.h>
#include <fcntl.h>

int main(void)
{
    int fd;
    int i;
    char str[] = "qwertyuiop\n";
    if ((fd = open("write.txt", O_RDWR|O_CREAT|O_TRUNC, 00664)) < 0)
    {
        fprintf(stderr, "Cannot open output file.\n");
        return 1;
    }
    for(i=0; i<100000; i++)
    {
        write(fd, str, sizeof(str));
    }
    close(fd); /*关闭文件*/
    return 0;
}

运行时间:

real    0m0.189s
user    0m0.008s
sys     0m0.181s

果然,从程序运行时间上看,使用标准库函数速度比直接系统调用快了很多 从程序的可移植性角度来讲,使用标准库函数也是一个好的习惯 而系统调用则是用在和系统关联度很高的地方

2016-03-27 补充

iSpeller 2014年2月26日13:27 对通过fwrite()和write()比较标准库函数和系统调用的速度的评论:

这样应该不算比较公平的比较吧,系统调用需要切换到内核态自然比较慢,其实除了切换速度的比较,还应该比较下执行的效率,例如一次性(而不是少量多次)读入20MiB 的数据,看看谁更快些。