vlambda博客
学习文章列表

Linux c 基础汇总以及扩展

1、sleep()函数

 

在windows 上:

    头文件是#include <windows.h>

    Sleep(1000); //S大写,单位为毫秒,这里是1000毫秒。


在Linux上:

    头文件是#include <unistd.h>

    sleep(1);  //s小写,单位是秒,这里是1秒。



编译并执行如下,sleep(2)前后date命令打印的时间相差sleep的2秒。

Linux c 基础汇总以及扩展



2、结构体


(1) 内存大小以及对齐原则


1) 结构体中成员变量分配的空间是按照成员变量中占用空间最大的来作为分配单位,同样成员变量的存储空间也是不能跨分配单位的,如果当前的空间不足,则会存储到下一个分配单位中。

 

 

 

4)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在成员末尾加上填充字节。


Linux c 基础汇总以及扩展


运行结果如下,结构体s1最宽基本类型int i占4个字节,char类型c1和c2各占1个字节,因为总大小是最宽成员的整数倍,所以会填充2个字节,故结构体s1总大小为 4+4 = 8。s2 = 4+4+4 = 12;s3 = 4+4+4+4 = 16。

Linux c 基础汇总以及扩展


Linux c 基础汇总以及扩展


(2)定义结构


注:声明不占空间,定义结构体变量(实例化)占空间。


struct tag { 

    member-list

    member-list 

    member-list  

    ...

} variable-list ;


tag 是结构体标签。


member-list 是标准的变量定义,比如 int i;或者 float f,或者其他有效的变量定义。


variable-list 结构变量,定义在结构的末尾,最后一个分号之前,可以指定一个或多个结构变量。


在一般情况下,tag、member-list、variable-list 这 3 部分至少要出现 2 个。


例1:

Linux c 基础汇总以及扩展

 

此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c。


例2:

Linux c 基础汇总以及扩展


用SIMPLE标签的结构体,另外声明变量t1、t2、t3如下

        struct SIMPLE t1, t2[20], *t3;

 

例3:

也可以用typedef创建新类型

Linux c 基础汇总以及扩展


然后可以用Simple2作为类型声明新的结构体变量:

        Simple2 u1, u2[20], *u3;


(3)定义时初始化

直接最末尾的结构体变量赋值

struct s = {“mem a”,"mem b","mem c"};


(4)访问成员运算符(.) 

        a.b


(5)结构体(指针)作为函数参数

   void printBook(struct Books book){}

   

Linux c 基础汇总以及扩展


上图使用结构体指针Book *book作为函数printfbook的参数,运行结果如下:

Linux c 基础汇总以及扩展



(6)指向结构体的指针(详细使用见下一条)


可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:

    struct Books{

        char * title;

        int page;

    }

    struct Books Book1;       

    struct Books *struct_pointer;


    struct_pointer = &Book1;


为了使用指向该结构的指针访问结构的成员,必须使用 -> 运算符,如下所示:

      struct_pointer->title;

  

Linux c 基础汇总以及扩展


运行结果如下图:

Linux c 基础汇总以及扩展



(7)结构体数组

 

1)一个结构体变量中可以存放一组数据(如一个学生的学号,姓名,成绩等数据)。

如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组。

结构体数组与数据值型数组不同之处在于每个数组元素都一个结构体类型的数据,它们分别包括各个成员(分量)项。

  

  2)定义

    struct student

    {

      int num;

      ....


    }stu[3];


  或

    struct

    {

      int num;

       ...

    }stu[3];

  

  3)初始化

    struct student

    {

      int mum;

      char name[20];

      char sex;

      int age;

      float score;

      char addr[30];

    }stu[3] = {{10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},

            {10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"},

            {10101,"Li Lin", 'M', 18, 87.5, "103 Beijing Road"}};


①定义数组 stu 时,元素个数可以不指定,即写成以下形式:

                            

                  stu[] = {{...},{...},{...}};


编译时,系统会根据给出初值的结构体常量的个数来确定数组元素的个数。


②数组的初始化也可以用以下形式:


    struct student

    {

      int num;

      ...

    };

    struct student stu[] = {{...},{...},{...}};


即先声明结构体类型,然后定义数组为该结构体类型,在定义数组时初始化。

  

4)调用

    stu[i].name



3、结构体指针


(1)用指针引用结构体变量成员的方式:


1) (*指针变量名).成员名

2)指针变量名->成员名


1)和2)是等价的。“->”是“指向结构体成员运算符”,它的优先级同结构体成员运算符“.”一样高。

p->num的含义是指针变量p所指向的结构体变量中的num成员。p->num最终代表的就是num这个成员中的内容。


注意:只有“指针变量名”后面才能加“->”,千万不要在成员名后面加“->”(如下面用例),即如果成员也是个结构体,只能用“.”。


综上所述,以下 3 种形式是等价的:

1) 结构体变量.成员名。

2) (*指针变量).成员名。

3) 指针变量->成员名。


用法例:

Linux c 基础汇总以及扩展


调试结果如下,上面圈的是未初始化的值,下面圈的是赋值后的值:

Linux c 基础汇总以及扩展



4、数组指针与指针数组


(1)数组指针(也称行指针)

    定义:int (*p)[n];


注:()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。


 例:     int (*p)[4]; 

该语句是定义一个数组指针,指向含4个元素的一维数组。


(2)指针数组

            定义:int *p[n];



int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]p[1]p[2],所以要分别赋值。


(3)数组与指针的扩展

对于数组 int a[3] = {4,6,7};

③  *(a+1):代表的是数组第一个元素的值a[1]!


调试如下:

Linux c 基础汇总以及扩展


以下示例将指针数组和数组指针放一个程序演示:

Linux c 基础汇总以及扩展


结果如下,数组指针p1与指针数组p2都是从数组s获取初始值,before打印的是从s获取的初始值。后面for循环将s重新赋值,p1、p2的值也会随之改变,改变的值打印如after。

Linux c 基础汇总以及扩展



5、多线程的简单使用


(1)pthread线程创建出现undefined reference to `pthread_create' collect2: error: ld returned 1 exit status


只需在用gcc编译时加上-pthread选项即可。(原因是没有链接上pthread库)


(2)在Linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化。


对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 或者调用pthread_mutex_init。


对于动态分配的互斥量, 在申请内存(malloc)之后, 通过pthread_mutex_init进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy。


(3)mutex互斥信号量锁住的不是一个变量,而是阻塞住一段程序。如果对一个mutex变量testlock,执行了第一次pthread_mutex_lock(testlock)之后,在unlock(testlock)之前的这段时间内,如果有其他线程也执行到了pthread_mutex_lock(testlock),这个线程就会阻塞住,直到之前的线程unlock之后才能执行,由此实现同步,也就达到保护临界区资源的目的。


注:同步和异步的区别:

  1)同步就是说多个任务之间是有先后关系的,一个任务需要等待另一个任务执行完毕才能继续执行(需要考虑资源冲突,需要加锁处理)

   2)异步就是说多个任务之间没有先后关系,不需要相互等待各做各的事(异步无需考虑资源冲突,不需特别处理。)


扩展:进程与线程


以下是简单使用示例:

Linux c 基础汇总以及扩展

Linux c 基础汇总以及扩展


可以看到以下运行结果是先sleep 4s后才释放了lock,这个时候才开始执行occpuy这个线程。

Linux c 基础汇总以及扩展



6、popen()


(1)linux中的popen()函数可以在程序中执行一个shell命令,并返回命令执行的结果。


有两种操作模式,分别为读和写。在读模式中,程序中可以读取到命令的输出,其中有一个应用就是获取网络接口的参数。在写模式中,最常用的是创建一个新的文件或开启其他服务等。


(2)popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。这个管道必须由pclose()函数关闭。


获取网络接口参数示例:

Linux c 基础汇总以及扩展


执行结果如下:

Linux c 基础汇总以及扩展


对比ifconfig直接查看的网口信息:

Linux c 基础汇总以及扩展



7、常用输入输出


(1)   getchar() and putchar()

随n的大小输出,输入多少输出多少,不会局限于下面示例数组s[n]的范围。


(2)   gets() and puts()

gets()仅可靠度要求低的情况下使用,puts输出所有gets到数组s的字符串。



(3)   fgets() and fputs()

fputs()只输出s[10]范围内的前s[1~9],s[10]被默认为结束字符串。


(4)  scanf("%[^\n]", a)

该输入可以包括空格在内的字符串。[^\n]表示遇到换行符停止。






以上三种常用输入输出一起总结在以下程序:

Linux c 基础汇总以及扩展


执行结果如下,三种方式都是输入输出同一字符串:

Linux c 基础汇总以及扩展



8、命令行参数


(1)执行程序时,当您想从外部控制程序,而不是在代码内对这些值进行硬编码时,可以从命令行传值给 C 程序。这些值被称为命令行参数,


(2)命令行参数是使用 main() 函数参数来处理的;其中,argc 是指传入参数的个数,argv[] 是一个指针数组,指向传递给程序的每个参数。


使用示例:

Linux c 基础汇总以及扩展


结果如下图,可执行程序a.out后面接参数 luhongwei,运行结果正常打印出来该参数;当把参数 lu 和 hongwei 分开是就会变成两个参数,识别结果是参数过多。

Linux c 基础汇总以及扩展



9、结构体指针数组与 linux 文件操作


(1) 结构体指针数组 s_dev-t *devs[],结构体指针 s_dev_t *dev


将结构体创建为新类型:

typedef struct {

    ...

}s_dev_t;


示例1:

Linux c 基础汇总以及扩展


打印结果如下:

Linux c 基础汇总以及扩展


我自己创建的/root/lhw/test文件内容如下:

Linux c 基础汇总以及扩展


示例2:

添加语句devs[2] = dev,即devs[2]指向dev。

Linux c 基础汇总以及扩展


dev和devs[2]的buf打印结果如下:

Linux c 基础汇总以及扩展


调试如下,其实devs[2]存放的是dev的指针,而dev这个指针又指向dev1,最终结果就是devs[2]->dev->dev1 。

Linux c 基础汇总以及扩展


注:

②  p *devs =》数组首元素(也是指针)的值,打印该元素(指针)指向地       址寄存器的值;本质是打印对应元素指针指向的值。

③  p *devs[n] =》n代表取第n-1个元素(指针)指向的值。



(2)linux 文件操作


注意:以下两套系统函数是配套使用的,不能两种混合使用。


1) UNIX系统调用函数open() fcntl() lseek()  read()  write()  close()


① open()

函数原型:

int open(const char *pathname, int flags);

int open(const char *pathname, int flags, mode_t mode);


返回值:成功则返回文件描述符,否则返回 -1。(open 返回的文件描述符一定是最小的未被使用的描述符)


参数介绍:

①pathname:是要打开/创建的文件路径名,可以是绝对路径也可以是相对路径。

②oflag:打开文件时,可以传入多个参数选项,用下面的一个或多个进行“或”运算,构成flags。这个参数可由以下常量(定义于 fcntl.h)通过逻辑或构成。

③mode:oflag带O_CREAT选项时可以用来创建文件,这时必须带该参数用来指定创建文件的权限模式,如0666。否则不需要。


oflag选用:


打开/创建文件时以下三个常量至少使用一个:

注意:以下三个常量必须按照要打开的文件的权限来选用,因为权限不对会出错,比如只有读(写)权限的文件使用读写方式打开也会出错。

O_RDONLY:只读方式打开

O_WRONLY:只写方式打开

O_RDWR:读写方式打开


以下常量可选用,非必须:

O_APPEND 每次写操作都写入文件的末尾;

O_CREAT 如果指定文件不存在,则创建这个文件;

O_EXCL  如果要创建的文件已存在,则返回 -1,并且修改 errno 的值;

O_TRUNC  如果文件存在并且以只写/读写方式打开,则清空文件全部内容;

O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端;

O_NONBLOCK 如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)。


以下三个常量同样是选用的,用于同步输入输出

O_DSYNC 等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新;

O_RSYNC  read 等待所有写入同一区域的写操作完成后再进行;

O_SYNC  等待物理 I/O 结束后再 write,包括更新文件属性的 I/O。


实例:open("/root/lhw/creat", O_RDWR | O_CREAT,0655);


② fcntl()

函数原型:

        int fcntl(int fd, int cmd);  

        int fcntl(int fd, int cmd, long arg);  

        int fcntl(int fd, int cmd , struct flock* lock);  


函数说明:

        fcntl系统调用可以用来对已打开的文件描述符进行各种控制操作以改变已打开文件的各种属性。


参数解析:

① fd:文件描述符;

② cmd:操作指令;有以下选择:

1)F_DUPFD:与dup函数功能一样,复制由fd指向的文件描述符,调用成功后返回新的文件描述符,与旧的文件描述符共同指向同一个文件。


2)F_GETFD:读取文件描述符close-on-exec标志。


3)F_SETFD:将文件描述符close-on-exec标志设置为第三个参数arg的最后一位。


4)F_GETFL:获取文件打开方式的标志,标志值含义与open调用一致。


5)F_SETF:设置文件打开方式为arg指定方式


6)  F_SETLK:此时fcntl函数用来设置或释放锁。当short_l_type为F_RDLCK为读锁,F_WRLCK为写锁,F_UNLCK为解锁。如果锁被其他进程占用,则返回-1;这种情况设的锁遇到锁被其他进程占用时,会立刻停止进程。


7)  F_SETLKW:此时也是给文件上锁,不同于F_SETLK的是,该上锁是阻塞方式。当希望设置的锁因为其他锁而被阻止设置时,该命令会等待相冲突的锁被释放。


8)  F_GETLK:第3个参数lock指向一个希望设置的锁的属性结构,如果锁能被设置,该命令并不真的设置锁,而是只修改lock的l_type为F_UNLCK,然后返回该结构体。如果存在一个或多个锁与希望设置的锁相互冲突,则fcntl返回其中的一个锁的flock结构。


文件记录锁是fcntl函数的主要功能。

记录锁:实现只锁文件的某个部分,并且可以灵活的选择是阻塞方式还是立刻返回方式;当fcntl用于管理文件记录锁的操作时,第三个参数指向一个struct flock *lock的结构体:

        struct flock

        {

            short l_type;    /*锁的类型*/

            short l_whence;  /*偏移位置:SEEK_SET,SEEK_CUR,SEEK_END*/

            off_t l_start;     /*加锁的起始偏移*/

            off_t l_len;    /*上锁字节*/

            pid_t l_pid;   /*锁的属主进程ID */

        }; 


short l_type用来指定设置共享锁(F_RDLCK,读锁)还是互斥锁(F_WRLCK,写锁)。当short l_type的值为F_UNLCK时,传入函数后将解锁。

每个进程可以在该字节区域上设置不同的读锁,但给定的字节上只能设置一把写锁,并且写锁存在就不能再设其他任何锁,且该写锁只能被一个进程单独使用。这是多个进程的情况。


单个进程时,文件的一个区域上只能有一把锁,若该区域已经存在一个锁,再在该区域设置锁时,新锁会覆盖掉旧的锁,无论是写锁还是读锁。


l_whence,l_start,l_len三个变量来确定给文件上锁的区域。

l_whence确定文件内部的位置指针从哪开始,l_start确定从l_whence开始的位置的偏移量,两个变量一起确定了文件内的位置指针先所指的位置,即开始上锁的位置,然后l_len的字节数就确定了上锁的区域。


当l_len的值为0时,则表示锁的区域从起点开始直至最大的可能位置,就是从l_whence和l_start两个变量确定的开始位置开始上锁,将开始以后的所有区域都上锁。如果要锁整个文件,直接把l_whence,l_start,l_len都设为0。


③arg:供命令使用的参数。 

④lock:供命令使用的参数。 


返回值:


③ lseek()

函数原型:

off_t lseek (int fd, off_t offset, int whence);




参数介绍:①fd:文件描述符

                 ②offset:使相对于whence的偏移量

                 ③whence:有三个特定的选项,其值如下:

                           SEEK_SET表示在文件头;

                           SEEK_CUR 表示在文件当前位置,允许offset有负值出现;

                           SEEK_END 表示在文件尾,允许offset有负值出现。


实例:

lseek(fd, 0, SEEK_SET); 将文件偏移量设置到了文件开始的第一个字节上;

lseek(fd, 0, SEEK_END);将文件偏移量设置到文件最后一个字节上;

lseek(fd, -1, SEEK_END);将文件偏移量设置到文件最后的倒数第一个字节上;


④ read()

函数原型:

ssize_t read(int fd, void *buf, size_t nbytes);


功能描述:读取文件中的内容。当文件是文本模式(非二进制模式),则函数会逐个字符进行读取;反之,如果文件以二进制模式打开,则会逐个字节进行读取。


参数解析:①fd:指文件描述符;

                 ②buf:是读出数据的一段内存空间;

              ③nbytes:每次读取的字节数,不得超过nbytes字节,这里的nbytes一般是buf剩余的空间大小。


返回值:成功则返回读到的字节数(>0),失败则返回-1,并且设置相应的errno,若读到文件尾,则返回0。


ssize_t类型是表示有符号的size_t,这样既可以返回正的字节数、0和负的数值。


注意:read()读取数据的时候,将数据保存在buf缓冲区中,同时文件的当前读写位置向后移动。此外,read()在读取数据时会将最后的回车(\n)同时读入到buf中,但是不会在后面加上字符串结束符(\0)。


实例:

 while((num = read(dev->fd, dev->buf, sizeof(dev->buf))) > 0) {}


⑤ write()

函数原型:

ssize_t write (int fd, const void * buf, size_t count);


功能描述:write()会把参数buf所指的内存写入count个字节到参数fd所指文件内。


返回值:如果顺利write()会返回实际写入的字节数。当有错误发生时则返回-1,错误代码存入errno中:

EINTR    此调用被信号所中断。

EAGAIN   当使用不可阻断I/O 时(O_NONBLOCK),若无数据可读取则返回此值。

EBADF    参数fd非有效的文件描述词或该文件已关闭。


⑥ close()

定义:

int close(int fd);


功能描述:关闭打开的文件,释放该文件所占用的资源。若不关闭打开的文件,会导致内存泄漏(程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果)


参数介绍:

       fd:文件描述符


返回值:成功则返回0,失败则返回-1


实例:close(dev->fd );


⑦综合应用实例

UNIX系统调用函数的综合应用:

Linux c 基础汇总以及扩展

Linux c 基础汇总以及扩展


原始 creat.c 文件内容:

Linux c 基础汇总以及扩展


执行程序结果如下,已经写入并完整打印该文件:

Linux c 基础汇总以及扩展


再看creat.c文件,已经成功写入:

Linux c 基础汇总以及扩展



2) C语言库函数fopen() fseek() fread()  fwrite()  fclose()


① fopen()

函数原型:

    FILE *fopen(char *filename, char *mode);


参数解析:

        filename:文件路径;

        mode:打开方式。


返回值:


FILE 是 <stdio.h> 头文件中的一个结构体,专门用来保存文件信息。定义一个FILE类型指针接收返回值:

            FILE *fp = fopen("filepath", "r");


打开文件出错时,fopen() 将返回一个空指针,也就是 NULL:


            if ((fp = fopen("path" ,"rb")) == NULL){

                printf();

                exit(0);    //结束程序

}


打开方式 mode:

选择控制读写权限参数(必须)

①"r":以“只读”方式打开文件。只允许读取,不允许写入。文件必须存在,否则打开失败。


②"w":以“写入”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。


③"a"以“追加”方式打开文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。


④"r+"以“读写”方式打开文件。既可以读取也可以写入,也就是随意更新文件。文件必须存在,否则打开失败。


⑤"w+":以“写入/更新”方式打开文件,相当于w和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么清空文件内容(相当于删除原文件,再创建一个新文件)。


⑥"a+":以“追加/更新”方式打开文件,相当于a和r+叠加的效果。既可以读取也可以写入,也就是随意更新文件。如果文件不存在,那么创建一个新文件;如果文件存在,那么将写入的数据追加到文件的末尾(文件原有的内容保留)。


选择控制读写方式参数(非必须)

①"t"文本文件方式。如果不写,默认为"t"。

②"b"二进制文件方式。


整体来说,文件打开方式由 r、w、a、t、b、+ 六个字符拼成,各字符的含义是:

  • r(read):读

  • w(write):写

  • a(append):追加

  • t(text):文本文件

  • b(binary):二进制文件

  • +:读和写


读写权限和读写方式可以组合使用,但是必须将读写方式放在读写权限的中间或者尾部(即不能将读写方式放在读写权限的开头)。


例:

将读写方式放在读写权限的末尾:"rb"、"wt"、"ab"、"r+b"、"w+t"、"a+t"

将读写方式放在读写权限的中间:"rb+"、"wt+"、"ab+"


② fseek()

函数原型:

        int fseek(FILE *stream, long int offset, int whence)


函数说明:

类型lseek功能,fseek为流文件设置偏移offset,然后从给定的 whence 位置查找的字节数。


参数解析:

① stream -- 指向 FILE 对象的指针,该 FILE 对象标识了流。

② offset -- 相对 whence 的偏移量,以字节为单位(负号前移,正号后移)。

③ whence -- 开始添加偏移 offset 的位置;一般指定为下列常量之一:   

        SEEK_SET    文件的开头

        SEEK_CUR    文件指针的当前位置

        SEEK_END    文件的末尾


返回值:

        如果成功,则该函数返回零,否则返回非零值。


③ fread()

函数原型:

        size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)


函数说明:

        从给定流 stream 读取数据到 ptr 所指向的数组中。


参数说明:

① ptr -- 指向带有最小尺寸 size*nmemb 字节的内存块的指针。

② size -- 要读取的每个元素的大小,以字节为单位。

③ nmemb -- 元素的个数,每个元素的大小为 size 字节。

④ stream -- 指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。


返回值:

        成功读取的元素总数会以 size_t 对象返回,size_t 对象是一个整型数据类型。如果总数与 nmemb 参数不同,则可能发生了一个错误或者到达了文件末尾。


实例:fread(buffer, strlen(long), 1, fp);


④ fwrite()

函数原型:

    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)


函数说明:

        把 ptr 所指向的数组中的数据写入到给定流 stream 中。


参数说明:

① ptr -- 指向要被写入的元素数组的指针。

② size -- 要被写入的每个元素的大小,以字节为单位。

③ nmemb -- 元素的个数,每个元素的大小为 size 字节。

④ stream -- 指向 FILE 对象的指针,该 FILE 对象指定了一个输出流。


返回值:

        如果成功,该函数返回一个 size_t 对象,表示元素的总数,该对象是一个整型数据类型。如果该数字与 nmemb 参数不同,则会显示一个错误。



⑤ fclose()

函数原型:

        int fclose(FILE *fp);


参数解析:

        fp为文件指针。


返回值:

文件正常关闭fclose() 的返回值为0,如果返回非零值则表示有错误发生。


⑥ c语言库函数文件操作综合应用实例


Linux c 基础汇总以及扩展


这个例子我直接搬的网上的,上图读写加1应该是考虑字符串结束符"\0";运行结果如下:

Linux c 基础汇总以及扩展



10、以结构体为元素的多维数组简单使用


创建结构体类型 xxx_reg:


typedef struct{

    unsigned int offset;

    unsigned char val;

}xxx_reg;


以结构体为元素的数组:

xxx_reg product_register_cfg[] = 

{

    /*元素以结构体xxx_reg

{0ffset,val}的形式赋值*/


{0x0B24, 0XC0},

{0X0B25, 0X00},

{0X0540, 0X01}

......

}


Linux c 基础汇总以及扩展


执行结果如下:

Linux c 基础汇总以及扩展



11、赋值字符变量,若超过变量类型大小,则默认只取最后一个字符


Linux c 基础汇总以及扩展


查找对照表可知,char buf 为 0x67 即 ‘g’,short buf1为 0x6667即 'f' 和‘g',int buf2 为 0x64656667 即 'd' 、'e' 、'f'、 'g'。所有类型都是按大小从末尾取。

Linux c 基础汇总以及扩展


ASCII对照表

Linux c 基础汇总以及扩展



12、区分 if(a<x<b) 与 if(a<x && x<b)


if(a<x<b)是错误的,这种格式会按从左到右先判断a<x,正确返回1,错误返回0;返回的值(0或1)再与b比较,这时候已经不是初衷了。所以需要判断一个范围时用if(a<x && x<b),这才是符合判断的初衷。


Linux c 基础汇总以及扩展


运行结果只有 a<x && x<b条件成立,method a<x<b条件不成立,没有打印结果。

Linux c 基础汇总以及扩展



13、a++ 与 ++a的使用区别


后缀自增法(a++): 先进行表达式运算,再进行自增运算。

前缀自增法(++a): 先进行自增运算,再进行表达式运算。


Linux c 基础汇总以及扩展


运行结果如下,a++先计算sa=sa+a=0+0=0 再a=a++=1;++b则先b=++b=1 再 sb=sb+b=0+1=1。故会出现以下sa在下一轮loop才加1,而sb直接在当前loop加1。

Linux c 基础汇总以及扩展



14、指针数据存储


若有:

unsigned int loop, *p, *q;

p = malloc(sizeof(unsigned long) *loop);

q=p;


则指针 q 与 p的关系如下:


malloc 分配指针p的空间,再赋值q = p,依次赋值*q等于2,6,8。

假设p指向0xc00000a0,则有以下结果:


*p = 2; *(p+1) = 6; *(p+2) = 8; 

注:p:0xc00000a0; (p+1):0xc00000a8; (p+2):0xc00000b0


*(q-3)=2; *(q-2)=6; *(q-1)=8;

注:指针q会随着被赋值而指向后8个字节;最开始q指向0xc00000a0,a0被赋值2后,q指向后8个字节0xc00000a8,a8=6后,q再指向后一个字节0xc00000b0。

b0=8后,q继续往后偏移8个字节指向0xc00000b8。


注意:具体偏移多少个字节与定义的指针类型有关,long是偏移8个字节,int是偏移4个字节,short是偏移2个字节;另外也可能与操作系统的位数有关。



示例:如果 arr[i] 的值不为0,则指针 q 记录该次错误

Linux c 基础汇总以及扩展


Linux c 基础汇总以及扩展


Linux c 基础汇总以及扩展


Linux c 基础汇总以及扩展


Linux c 基础汇总以及扩展



15、cpu大小端模式


结论:


假设原始数据为:0x15e4fb99(接近0x的15为高位字节,字节位级往右依次降低,99为最低位字节)


(1)  小端存储模式:(ARM一般默认小端模式)


(2)  大端存储模式:



16、函数作为调用接口的参数


如下图,接口populate_array()第三个参数直接调用产生随机数的函数getNextRandomValue()。

Linux c 基础汇总以及扩展


产生的随机数如下:

Linux c 基础汇总以及扩展



17、sizeof 与 strlen

(1)sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。

(2)sizeof是运算符,strlen是函数。
(3)sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。sizeof还可以用函数做参数,比如:
                

Linux c 基础汇总以及扩展


Linux c 基础汇总以及扩展


(4)数组作sizeof的参数不退化,传递给strlen就退化为指针了。
(5)大部分编译程序在编译的阶段就把sizeof的值确定了,可以是类型或是变量的长度。

Linux c 基础汇总以及扩展


Linux c 基础汇总以及扩展


(10)使用实例

在用到 sizeof 和 strlen 的时候,通常是计算字符串数组的长度,以下是一些使用实例:

                char str[20]="0123456789";


int a=strlen(str); //a=10,strlen 计算字符串的长度,以结束符‘\0’为字符串结束。

int b=sizeof(str); //而b=20,sizeof 计算的则是分配的数组 str[20] 所占的内存空间的大小,不受里面存储的内容改变。

Linux c 基础汇总以及扩展


Linux c 基础汇总以及扩展


上面是对静态数组处理的结果,如果用于指针结果并不一样:
                char* ss = "luhongwei";
①  sizeof(ss) 结果是4(32位系统)或8(64位系统),ss是指向字符串常量的字符指针,sizeof 获得的是一个指针所占的空间,所以是4或8。

②  sizeof(*ss) 结果1,*ss是第一个字符,其实就是获得了字符串的第一位'0' 所占的内存空间,是char类型的,占了 1 位。

③  sizeof("luhongwei")的结果是10,对于“”中的字符串,默认后面还有一个\0,所以实际的长度要+1。

④  strlen(ss)=9,如果要获取这个字符串的长度,则要使用 strlen。


Linux c 基础汇总以及扩展


Linux c 基础汇总以及扩展



18、ssize_t与size_t


(1)  ssize_t是有符号整型,在32位机器上等同int,在64位机器上等同long 。

(2)  size_t 就是无符号型的ssize_t,也就是unsigned long/ unsigned int (32位),不同的编译器或系统可能会有区别,主要是因为在32位机器上int和long是一样的。size_t在64位下是64位,那么size_t的正确定义应该是typedef unsigned long size_t。


19、去掉读取文件内容的换行符


Linux c 基础汇总以及扩展


主要是for循环里面轮询temp[i],若轮询到temp[i]为换行符'\n',则重新赋值'\0'。


20、堆栈分区


一个由C/C++编译的程序占用的内存分为以下几个部分:


(1) 栈区(stack) -- 由编译器自动分配释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。

(2) 堆区(heap) -- 一般由开发者分配释放,若开发者不是放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式类似链表。

(3) 全局区(静态区static) -- 全局变量和静态变量存储在一块,初始化的全局变量和静态变量 在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。

(4) 文字常量区 -- 常量字符串存放处;程序结束后由系统释放。

(5) 程序代码区 -- 存放函数体的二进制代码。



21、extern与include之间是否需要二选一


两种使用方法:

(1)直接extern int b;

(2)在b.h里面先申明extern int b,再到a.c里面include “b.h”


如下图:



附:C语言错误汇总


1: Ambiguous operators need parentheses — 不明确的运算需要用括号括起

2: Ambiguous symbol xxx — 不明确的符号

3: Argument list syntax error — 参数表语法错误

4: Array bounds missing — 丢失数组界限符

5: Array size toolarge — 数组尺寸太大

6: Bad character in paramenters — 参数中有不适当的字符

7: Bad file name format in include directive — 包含命令中文件名格式不正确

8: Bad ifdef directive synatax — 编译预处理ifdef有语法错

9: Bad undef directive syntax — 编译预处理undef有语法错

10: Bit field too large — 位字段太长

11: Call of non-function — 调用未定义的函数

12: Call to function with no prototype — 调用函数时没有函数的说明

13: Cannot modify a const object — 不允许修改常量对象

14: Case outside of switch — 漏掉了case 语句

15: Case syntax error — Case 语法错误

16: Code has no effect — 代码不可能执行到

17: Compound statement missing{ — 分程序漏掉"{"

18: Conflicting type modifiers — 不明确的类型说明符

19: Constant expression required — 要求常量表达式

20: Constant out of range in comparison — 在比较中常量超出范围

21: Conversion may lose significant digits — 转换时会丢失意义的数字

22: Conversion of near pointer not allowed — 不允许转换近指针

23: Could not find file xxx — 找不到XXX文件

24: Declaration missing ; — 说明缺少";"

25: Declaration syntax error — 说明中出现语法错误

26: Default outside of switch — Default 出现在switch语句之外

27: Define directive needs an identifier — 定义编译预处理需要标识符

28: Division by zero — 用零作除数

29: Do statement must have while — Do-while语句中缺少while部分

30: Enum syntax error — 枚举类型语法错误

31: Enumeration constant syntax error — 枚举常数语法错误

32: Error directive :xxx — 错误的编译预处理命令

33: Error writing output file — 写输出文件错误

34: Expression syntax error — 表达式语法错误

35: Extra parameter in call — 调用时出现多余错误

36: File name too long — 文件名太长

37: Function call missing ) — 函数调用缺少右括号

38: Fuction definition out of place — 函数定义位置错误

39: Fuction should return a value — 函数必需返回一个值

40: Goto statement missing label — Goto语句没有标号

41: Hexadecimal or octal constant too large — 16进制或8进制常数太大

42: Illegal character x — 非法字符x

43: Illegal initialization — 非法的初始化

44: Illegal octal digit — 非法的8进制数字 A

45: Illegal pointer subtraction — 非法的指针相减

46: Illegal structure operation — 非法的结构体操作

47: Illegal use of floating point — 非法的浮点运算

48: Illegal use of pointer — 指针使用非法

49: Improper use of a typedefsymbol — 类型定义符号使用不恰当

50: In-line assembly not allowed — 不允许使用行间汇编

51: Incompatible storage class — 存储类别不相容

52: Incompatible type conversion — 不相容的类型转换

53: Incorrect number format — 错误的数据格式

54: Incorrect use of default — Default使用不当

55: Invalid indirection — 无效的间接运算

56: Invalid pointer addition — 指针相加无效

57: Irreducible expression tree — 无法执行的表达式运算

58: Lvalue required — 需要逻辑值0或非0值

59: Macro argument syntax error — 宏参数语法错误

60: Macro expansion too long — 宏的扩展以后太长

61: Mismatched number of parameters in definition — 定义中参数个数不匹配

62: Misplaced break — 此处不应出现break语句

63: Misplaced continue — 此处不应出现continue语句

64: Misplaced decimal point — 此处不应出现小数点

65: Misplaced elif directive — 不应编译预处理elif

66: Misplaced else — 此处不应出现else

67: Misplaced else directive — 此处不应出现编译预处理else

68: Misplaced endif directive — 此处不应出现编译预处理endif

69: Must be addressable — 必须是可以编址的

71: No declaration for function xxx — 没有函数xxx的说明

72: No stack — 缺少堆栈

73: No type information — 没有类型信息

77: Not a valid expression format type — 不合法的表达式格式

78: Not an allowed type — 不允许使用的类型

79: Numeric constant too large — 数值常太大

80: Out of memory — 内存不够用

81: Parameter xxx is never used — 能数xxx没有用到

82: Pointer required on left side of -> — 符号->的左边必须是指针

83: Possible use of xxx before definition — 在定义之前就使用了xxx(警告)

84: Possibly incorrect assignment — 赋值可能不正确

85: Redeclaration of xxx — 重复定义了xxx

86: Redefinition of xxx is not identical — xxx的两次定义不一致

87: Register allocation failure — 寄存器定址失败

88: Repeat count needs an lvalue — 重复计数需要逻辑值

89: Size of structure or array not known — 结构体或数给大小不确定

90: Statement missing ; — 语句后缺少";"

91: Structure or union syntax error — 结构体或联合体语法错误

92: Structure size too large — 结构体尺寸太大

93: Sub scripting missing ] — 下标缺少右方括号

94: Superfluous & with function or array — 函数或数组中有多余的"&"

95: Suspicious pointer conversion — 可疑的指针转换

96: Symbol limit exceeded — 符号超限

97: Too few parameters in call — 函数调用时的实参少于函数的参数不

98: Too many default cases — Default太多(switch语句中一个)

99: Too many error or warning messages — 错误或警告信息太多

100: Too many type in declaration — 说明中类型太多

101: Too much auto memory in function — 函数用到的局部存储太多

102: Too much global data defined in file — 文件中全局数据太多

103: Two consecutive dots — 两个连续的句点

104: Type mismatch in parameter xxx — 参数xxx类型不匹配

105: Type mismatch in redeclaration of xxx — xxx重定义的类型不匹配

106: Unable to create output file xxx — 无法建立输出文件xxx

107: Unable to open include file xxx — 无法打开被包含的文件xxx

108: Unable to open input file xxx — 无法打开输入文件xxx

109: Undefined label xxx — 没有定义的标号xxx

110: Undefined structure xxx — 没有定义的结构xxx

111: Undefined symbol xxx — 没有定义的符号xxx

112: Unexpected end of file in comment started on line xxx — 从xxx行开始的注解尚未结束文件不能结束

113: Unexpected end of file in conditional started on line xxx — 从xxx 开始的条件语句尚未结束文件不能结束

114: Unknown assemble instruction — 未知的汇编结构

115: Unknown option — 未知的操作

116: Unknown preprocessor directive: xxx — 不认识的预处理命令xxx

117: Unreachable code — 无路可达的代码

118: Unterminated string or character constant — 字符串缺少引号

119: User break — 用户强行中断了程序

120: Void functions may not return a value — Void类型的函数不应有返回值

121: Wrong number of arguments — 调用函数的参数数目错

122: xxx not an argument — xxx不是参数

123: xxx not part of structure — xxx不是结构体的一部分

124: xxx statement missing ( — xxx语句缺少左括号

125: xxx statement missing ) — xxx语句缺少右括号

126: xxx statement missing ; — xxx缺少分号

127: xxx declared but never used — 说明了xxx但没有使用

128: xxx is assigned a value which is never used — 给xxx赋了值但未用过


有很多涉及计算机原理,本人机械出身缺乏很多相关专业知识,有些地方可能存在认知错误或者考虑不严谨。

由于本人水平有限,如有错误望多包涵!如果有朋友有兴趣一起学习讨论相关问题,欢迎指教!