• C Language


    在linux(Centos)上面写C语言的代码,需要先安装gcc:yun  install gcc

    C语言的代码文件约定是以.c的后缀文件,C++的是.cpp

    C语言的注释使用:/**/和//,与C#类似

    用一个简单的C代码来说明C语言程序的基本结构:

    //这是一个测试程序       -----这是注释

    #include <stdio.h>        ------这是编译预处理指令,告诉C编译器在编译之前要包含stdio.h文件,这个文件包含下面我们使用的printf函数

    int main()                      ------主函数,程序的入口

    {

      printf("你好! ");

      return 0;

    }

    将上面的代码保存为一个first.c的文件,然后执行:gcc -o first first.c。这是gcc的编译指令,表示将first.c编译生成一个叫first的文件

    再执行:./first。表示执行first文件

    C语言的数据类型:char字符,int整数,double浮点数,字符串char[](C语言中没有string类型,字符串需要用字符数组来存储),构造类型(包括数组和结构体),指针类型,复数类型_Complex,虚数类型_Imaginary,布尔类型_bool,单精度浮点float

    变量的初始化:在C语言中定义了一个变量,机器会给我们这个变量分配一块内存,但并不会去将这块内存中的数据清空,这块内存中原本有什么数据那么这个变量就指向哪些数据。所以我们在定义了一个变量之后,还需要手动去清空这个变量指向的内存也叫变量的初始化,一般的类型我们直接给他赋值就可以了。如果是字符数组我们用下面的语句清空里面的脏数据:char name[21]; memset(name,0,sizeof(name));

    给字符串赋值:char name[21];strcpy(name,"西施");

    const定义常量:const in i = 90;这样定义的变量不能重新赋值

    C语言获取键盘输入函数:getchar()获取输入的单个字符,gets()获取输入的多个字符作为字符串处理,scanf()比较灵活的输入模式

    C语言输出函数:putchar()输出单个字符,puts()输出字符串,printf()比较灵活的输出模式

    printf()函数(需要包含stdio.h头文件):用于格式化输出。printf("文字结束后换行 "); 表示换行。printf("我的年龄是:%d",18);%d表示一个整数。printf("我的性别是:%c",x);%c表示一个字符。printf("我的体重是:%lf",65.5);%lf表示一个浮点数。printf("我的名字是:%s","dadada");%s表示一个字符串。printf("我的名字是:%s。我的年龄是:%d","dadada",18);可以在一个输出里面包含多种格式输出。上面的都可以使用相应类型的变量代替具体的值。

    注:printf函数不能单独的输出一个变量:如int i = 0;printf(i);。需要写成这样:int i = 0;printf(“%d”,i);

    scanf()函数:用于接收用户输出的数据,需要用户输入完成后回车。printf("请输入你的年龄:");int age = 0;scahnf("%d",&age);printf("你的年龄是:%d",age);。不同类型的输入格式和输出使用的符号一致。一次输出多个多个数据的时候用空格分隔,printf("请输入你的姓名和年龄(中间用空格分开):");int age = 0;char name[21];memset(name,0,sizeof(name));scanf("%s %d",&name,&age);printf("你的姓名是:%s,年龄是:%d",name,age);

    因为C的代码中我们需要用到很多C内置的函数如:printf(),memset()等,这些函数都需要在C文件开始时包含对用的头文件,不然会报错。当我们不知道某个函数需要包含哪个头文件的时候可以使用man 函数名的方式查看帮助,如:man 3 printf(数字3表示第三个printf函数,因为内置中可能有多个同名的函数) 

    /除法的使用:

    int a =18;int b = 5;int c;double d;

    c = a/b;//a的值时3,因为c时整数所以只保留了结果的整数部分

    d = a/b;//d的值时0.000,因为a,b时整数,所以除出来默认是一个整数,而使用了一个浮点数来接收,他接收不到,所以是0.0000

    d = (double)a/b;//d的值是3.6000,这里是将a/b的结果做了一个强制转换,出来的值就是比较正确的值

    sizeof(),用来计算一个变量或者常量在内存中所占用的字节数,它不是一个函数而是一个运算符。

    switch()

    {

      case **:****;

      case **:****;

      default:break:

    }//switch的用法同C#一致,但是在case判断的时候,只能结果是整数的表达式(3+4)或者是字符(‘a’,因为字符可以和整数相互转换)或者整数(10),不能是小数和字符串。

    在C中没有字符串的类型,用字符数组来表示字符串,而为了区别表示字符串的字符数组和其他数组的区别,在C中字符串数组需要以‘’结尾,所以我们在定义一个字符串数组的时候一般需要多定义一位来结尾存放‘’。如存放一个hello的数组,char he[6];char[0] = 'h';char[1] = 'e';char[2] = 'l';char[3] = 'l';char[4] = '0';char[5] = ''(也可以写成char[5] = 0);printf("%s",he);。而我们在使用strcpy(he,"hello");这个函数来赋值字符数组的时候可以不用再在后面添加‘’,因为这个函数已经默认帮我们做了这个工作了。如果一个字符数组长度很大,里面只存了一个简短的字符串,那么我们可以通过判断数组[i]的值是否等于0来判断字符串的结束。

    函数:C中函数的定义一般分为两部分:首先是函数的定义(类似C#中接口的定义),然后是函数的实体。定义一般放在main()函数上面,实体放在main函数下面。如:

    #include <stdio.h>

    //函数的注释

    int add(int a,int b);

    int main()

    {

      int i = 0;

      i = add(3,5);

      printf("%d",i);

    }

    int add(int a,inb)

    {

      return a+b;

    }

    这个函数的定义不写也可以执行,但按照约定最好还是不好省略。

    将函数定义在外部的头文件中:

    新建一个_public.h的文件:

    #include <stdio.h>

    double mul(double a,double b);

    新建一个_public.c文件:

    #include "_public.h"

    double mul(double a,double b)
    {
    return a*b;
    }

    新建一个test.c文件调用定义在外面的函数:

    #include <stdio.h>
    #include <string.h>
    #include "_public.h"

    int main()
    {
    double c = mul(3,5);
    printf("%lf",c);
    }

    然后gcc打包的时候需要将test.c和_public.c一起打包:gcc -o test test.c _public.c

    执行test文件:./test

    注:gcc给我们提供了一些库函数,是只要在程序中包含头文件就可以直接使用的,因为编译器默认在编译C代码的时候就会将那些库函数编译进去。这些库函数也是有头文件(.h文件)和代码文件(.c文件,但是它的代码文件是.a的一个文件,这个文件是将多个.c的文件打包一起再进行了一次编译)的,具体所在目录可以去网上搜索下。

    在头文件中定义的全局变量,在引用这个头文件的程序中可以使用,但是一般不会再头文件中定义全局变量。

    指针:在C中每定义一个变量就在内存中开辟一块内存给这个变量,而这个内存空间有一个地址可以通过&变量名获取(如:int i = 1;&i可以获取i这个变量所在的内存地址),获取的值是一个十六进制的数。

    这个十六进制的地址可以赋给指针类型的变量,指针变量的定义需要在变量名前面加上*号,如char ch = "a";char *a=&ch;定义一个字符类型的指针变量只能赋值字符类型变量的内存地址,int i = 2;int *b = &i;定义一个整数类型的指针变量只能赋值整数类型变量的内存地址。虽然变量的地址&变量名返回的是一个十六进制的数,但是如果直接将一个十六进制的数赋值给指针变量是不行的,如:int *c = FFF00000;

    在用printf输出指针变量的时候,使用%p来作为占位符,如:int i = 2;int *b = &i;printf("%p",b);

    我们定义指针变量的时候:int i = 2;int *b = &i;,如果单独获取b的值就是一个十六进制的数指向i变量的地址,如果获取*b的值就是i的值也就是指向这个指针地址中存放的数据的值,所以如果我们要改变i的值我们可以直接i=3或者*b=3。

    当我们在一个函数中需要多个返回值的时候,可以在外面定义一个变量,然后将这个变量的指针传入函数,在函数内部通过改变这个指针指向的内存的值就相当于改变了外面定义在外部的变量的值,这样可以起到类似返回值的效果,如:

    int i;

    void add(int a,int b,int *sum);

    int main()

    {

      int rel;

      add(5,6,&rel);

      printf("%d",rel);

    }

    void add(int a,int b,int *sum)

    {

      *sum = a+b;

    }//在函数的参数是普通变量的时候,在传入参数的时候相当于将我们外面的参数复印了一份传入函数内部,内部操作的是这个复印件不会对原件产生影响,如果传入的是变量的指针

    //相当于将原件在哪传入了进去,在函数内部就可以直接通过这个地址找到原件,直接修改原件的值。

    在使用scanf()函数时候我们就是传入的&变量名,就是这个原理,相当于将变量的地址传入,函数内部将输入的值通赋值给这个地址所指向的值,就相当于改变了外部定义的这个变量。

    空指针:当我们定义了一个指针而没有指向任何的地址的时候它就是一个空指针,空指针是不能直接赋值的,因为它还没有指向一个内存的地址,没有内存来存放赋给的值,所以会报错,如:int *p;*p = 20;报错

    数组变量的地址:数组变量的地址是它存放的第一个元素的地址,如int arr[10];那么&arr和&arr[0]和arr的值是一样的,arr实际就是一个地址。

    地址的加减运算:地址的+1/-1表示下一个存储单元/上一个存储单元,如:int i =2;&i+1表示i地址的下一个存储单元,它的值实际上就是&i这个十六进制数+4,因为是整数型的存储单元,而一个int型的数占4个字节,所以下一个整数的存储单元在地址上的反应就是+4。这个在数组中应用较多,因为数组变量的地址就是数组存储的第一个值的地址,我们就可以通过+1/-1来拿到数组中下一个或者上一个的值。

    指针占用的内存空间:当我们定义一个指针变量的时候,它的值实际是一个十六进制的数,它也是需要占用内存空间来存这个十六进制的数,不管什么类型的指针变量占用的内存空间都是8个字节。

    整数深入理解:整数除了用int,还可以用short(短整型)和long(长整型)来表示,这三种类型前面都可以在加上signed(有符号,有负数),unsigned(无符号,每负数)修饰符,一个字节有8位也就是最多可以存28-1这么多的数字也就是0-255,如果两个字节就是28*28-1 ...当然如果是有符号的需要减半,因为需要一半去存负数。

    整数的类型不同在用printf()输出时所使用的占位符也不同,并且八进制、十进制、十六进制都是不同的,这里只说十进制的,%hd,%d,%ld,%hu,%u,%lu分别表示有符号的短整型,有符号的整型,有符号的长整型,无符号的短整型,无符号的整型,无符号的长整型。

    有时候我们有种需求就是在整数前面补零。比如2019-07-02这种情况,这时候输出要写成printf("%d-%02d-%02d",year,month,day);这里%02d表述输入一个两位整数,如果不足在前面补零。

    整数相关的几个库函数:int atoi(const char *nptr)将字符串nptr转化为int整数,long atol(const char *nptr)将字符串nptr转化为long整数,int abs(const int j)求int整数的绝对值,long labs(const long j)求int整数的绝对值

    可以使用typedef给一个类型定义一个别名,这样就可以使用这个别名来定义变量如:typedef unsigned int haha;int main(){haha i = 10;}。

    获取一个随机整数:int i = rand();,需要包含头文件<stdlib.h>。直接用这个函数取随机数的时候同一程序多次执行可能获取到的随机数是一样了,为了避免这个问题需要在执行这个函数之前先给他一个种子srand(3);我们只要保证每次代码执行给的种子不一样,那么取的随机数就会不一样,所以一般将时间作为种子srand(time(0))。并且获取到的随机数是一个很大的数字有时候我们需要一个某个范围的随机数,一般采用的方式是将这个结果再用一个数去余数。

    字符类型和整数类型可以相互转换,可以给char类型变量赋值一个整数,同样也可以给int变量赋值一个字符。char类型的变量再内存中存的实际上就是一个整数是这个字符对应的ascII码。同样在用printf输出的时候,char和int都可以用%d,%c进行输出,只是一个是输出整型对应的字符,一个是输出整型而已。

    字符类型常用的库函数:int isalpha(int ch);若ch是字母则返回非0不是则返回0,int isalnum(int ch);若字符是字母或者数字则返回非0否则返回0,int isdigit(int ch);若字符是数字则返回非0否则返回0,int islower(int ch);若ch是小写字母则返回非0否则返回0,int issupper(int ch);若ch是大写字母则返回非0否则返回0,int toupper();若字母是小写则返回相应的大写字母,int tolower();若字母是大写则返回相应的小写字母

    浮点数:C中表示浮点数有float,double,long double这几个类型。

    float定义的浮点数,精度很差,比如:float a = 12.56;int rel = (a==12.56);它与12.56本身都不是相等的,可能相差0.0001,所以float的精度很差,一般很少使用。

    double定义的浮点数,精度就好得多,一般十六七位的数都能保持数值一样。

    输出的时候:float用%f,double用%lf,long double用%Lf。%lf默认是输出小数点后六位,如果要指定显示小数点后n为用%.nlf(四舍五入)。也可以指定整数部分的位数%m.nlf,如果整数部分小于m在在左边补空格大于则原样输出。

    double类型的一些库函数:double atof(const char *nptr);将字符串nptr转换为double,double fabs(double x);求x的绝对值,double pow(double x,double y);求x的y次方,double round(double x);四舍五入,double ceil(double x);向上取整,double floor(double x);向下取整,double fmod(double x,double y);x/y整除后的双精度余数,double modf(double val,double *ip);将val分解成整数部分和小数部分整数部分放在ip所指的变量中小数部分返回,还有一些求正弦余弦等三角函数对数指数等需要的时候再去网上搜索

    科学计数法的书写:12300000,数学中写成1.23*107,C中可以写成1.23e7或者1.23E7,将一个数字按照科学计数法的方式输出是用%e,如果要指定整数和小数的位数用m.n%e的模式。

    字符串:字符数组不等于字符串,只有以结尾的字符数组才叫字符串,所以在定义一个字符数组来存储字符串的时候,字符数组的长度要比存入的字符串的长度多一位用来最后存入这个。一个中文占两个字节。

    字符串的初始化:char str[21];memset(str,0,sizeof(str));意思是将字符数组中所有元素置为0,因为字符串必须是以结尾的字符数组。

    字符串的数组名是数组元素的首地址,所以在取数组的地址的时候不用再需要用&取地址符。所以这里有一个应用是用字符串的首地址来截取字符串:

    char str[21];

    memset(str,0,sizeof(str));

    strcpy(str,"abcdefghijk");

    printf("%s ",str);//abcdefghijk

    printf("%s ",str+1);//bcdefghijk

    printf("%s ",str+2);//cdefghijk

    一个字符数组是用来判断字符串的结尾的,如果我们强制将一个字符数组的中间插入一个0,那么这个字符数组再作为字符串输出的时候就只显示0前面的内容。

    字符串输出的时候用%s,也可以添加一个数组来指定输出的字符串的宽度%10s则输出十个字符宽度右对齐,%-10s则输出十个字符宽度左对齐,如果字符串长度大于设置的宽度则按实际长度输出。

    字符串常用的库函数:size_t strlen(const char* str);计算字符串的长度不包括,size_t是unsigned int类型的别名也就是说可以直接用int类型来接收,strlen计算的是实际的长度,知道遇到第一个为止,而用sizeof获取一个字符数组的大小是获取整个数组所占的内存大小也就是定义时数组指定的长度。char* strcpy(char* dest,const char* src);将str字符串拷贝到dest所指向的地址。char* strccpy(char* dest,const char* src,cosnt size_t n);和字符的复制函数类似只是只复制前n的字符。char* strcat(char* dest,const char* src);将src字符串拼接到dest字符串的后面,会先去掉dest尾部的拼接完成后再在尾部添加一个。int strcmp(const char *str1,const char *str2);比较两个字符串的大小,相等返回0,不相等返回非0。char *strchr(const char *s,const int c);返回字符串s中第一次出现字符c的位置没有找到则返回0,*strrchr(const char *s,const int c);返回字符串s中最后次出现字符c的位置没有找到则返回0。char *strstr(const char *str,cost char *substr);返回字符串str中第一次出现子字符串substr的地址,如果没有找到则返回0。

    结构体:

    struct st_student

    {

      char name[20];

           int age;

      double score;

    };//类似这样的我们称为结构体,结构体一般以st_开头,定义完结构体后可以将结构体作为一个模板来定义变量。

    用结构体定义变量:struct st_student st;

    结构体和数组类似,它的各个成员在内存中是连续的,但是用sizeof(st_student)来计算结构体所占的大小的时候,可能会比定义的变量加起来所占的内存要大,这是因为结构体中各个成员之间可能会存在空隙。

    结构体的变量名和数组的变量名不一样,它并不是直接指向了结构体的指针,而是一个普通的变量,需要用&取地址符来获取地址如:&st。

    结构体的初始化还是用memset(&st,0,sizeof(st));

    对结构体中各个成员的赋值:strcpy(st.name,"Tom");st.age = 28;st.score = 99;

    也可以用结构体定义数组:st_student stus[21];和其他类型的数组操作基本一样。

    结构体指针:同其他指针一样要定义能指向结构体的指针,需要使用对应的结构体来定义:

    struct st_student *stu;*stu = &st;(*stu).age = 18;或者stu->age = 18;两种都是通过结构体指针给结构体成员赋值的操作。

    在将结构体作为参数的函数当中,一般不会直接传入结构体,而是传入结构体的地址,如:

    void setValue(struct st_student *stu)

    {

      strcpy(stu->name,"Tom");

      stu->age=18;

    }

    在初始化数组和结构体的时候,除了使用memset()函数,还可以使用bzero()函数,void bzero(void *s,size_t n);s一般是数组名或者结构体地址,n为要清零的字节数一般用sizeof()

    printf()是将结果输出到屏幕当中,sprintf()是一个将结果输出到字符串当中的函数,我们一般用这个函数来将其他类型的数组转化为字符串类型。int sprintf(char *str,const char *format...);,它的用法和print()一样,只是在前面多了一个字符数组变量用来接收输出的字符串而已,如:char str[21];memset(str,0,sizeof(str));sprintf(str,"%d",12);

    C中字符串跨行的表示:在写代码的过程中有时候有一个很长的字符串超过了一行的范围,可以用下面这种方式书写:

    peintf("%s ","aaaaaaaaaa"

    "bbbbbbbb"

    "cccccccccc");//最后输出:aaaaaaaaaaabbbbbbbbbbbccccccccccc

    main函数的参数,main函数之前我们都没有给它写传入的参数,实际上main函数是可以有传入的参数的,这些参数在执行代码的时候传入,比如我们一些的常用的liunx命令:ls列出当前文件夹中的所有目录这个可以直接用,也可以带参数输出ls -l *.c列出当前文件夹中以.c结尾的所有文件。

    mian函数有三个参数:int argc用于存放命令行参数的个数,char *argv[]一个字符串数组它的每个元素都是一个字符指针指向一个字符串及命令行中的每一个参数,char *envp[]也是一个字符串数组每个元素指向一个环境变量的字符指针存放的是该系统所有的环境变量。环境变量可以直接用env命令查看

    如:

    #include <stdio.h>

    #include <string.h>

    int main(int argc,char *argv[],char *envp[])

    {

      int ii = 0;

      printf("参数的个数是:%d ",argc);

      for(ii=0;ii<argc;ii++)

      {

        printf("第%d个参数是:%s ",ii,argv[ii]);

      }

           printf("一下是环境变量: ");

      int ii1 = 0;

      while(envp[ii1]!=0)

      {

        printf("%s ",envp[ii1]);

        ii1++;

      }

    }

    用命令运行编译好的上面的c文件的时候,如果不带参数默认有一个参数,就是运行程序时输入的命令。

    用这种方式接收到的参数都是字符串类型的,不管你输入的是什么参数。

    动态内存管理:C中可以在程序中主动向计算机申请一块内存。void *malloc(unzigned int size);,在内存的动态存储区中分配一个长度为size的连续空间,返回值是分配区域的起始地址,返回的指针是void类型的指针,就是一个指针地址不知向任何的数据类型,你可以把这个返回的指针赋值给任意一个数据类型的指针,如果申请内存失败则返回空(NULL,即0);使用:int *pi=malloc(sizeof(int));申请4字节大写的空间给pi指针。

    void free(void *p);释放用动态分布的内存空间。如:free(pi);在free之后pi指针所指向的内存可以分配给其他使用,但是并没有清空里面的内存,也没有情况pi的指向,所以free之后一般还需要pi=0还清空指针。

    这两个函数需要包含<stdlib.h>头文件。

    C中文件的操作:C中文件分为文本文件和二进制文件。

    文本文件是把每个字符的ASCII码存入文件中,每个ASCII码占一个字节,每个字节表示一个字符,所以文本文件也称为ASCII文件。

    二进制文件是将数据对应的二进制数值存储在文件中,是字节序列文件。

    例如123,在文本文件中看成三个字符‘1’‘2’‘3’,然后依次存储每个字符的ASCII码:49  50  51以二进制:00110001 00110010 00110011存储。可以用vi和记事本打开,看见的是ASCII字符。

    123如果按照二进制文件存储,可以被看成字符,短整型,整型,长整型。按不同的数据类型存储的话,最终存储的二进制也不一样。所以在打开二进制文件之前必须先知道它是按哪种方式进行的存储才能准确识别。

    文件指针:C中打开文件的时候,会为打开的文件分配一个文件信息区。它是一个结构体struct _IO_FILE,这个结构体有一个别名FILE,这个结构体中存储了文件描述信息、文件缓存区的位置、文件读写到的位置等基本信息。FILE结构体和文件操作的库函数在<stdio.h>头文件中声明。

    当我们打开文件的时候用fopen(),会动态分配一个FILE结构体存储打开文件的信息,并将这个结构体的地址作为函数的返回值返回。在调用关闭文件的函数fclose()关闭文件之后,还会自动释放这个FILE文件指针所占用的内存空间。

    打开文件:FILE *fopen(const char *filename,const char *mode);filename是打开的文件的路径,mode是打开的方式(r只读/文件必须存在,w只写/如果文件存在则清除原文件内容不存则新建文件,a追加只写/如果文件存在则打开文件不存在则新建文件,r+读写,w+读写)。如果打开的二进制文件mode就需要多加一个b字母,如只读写为rb,其他类似。

    文本文件的读取:fprintf(FILE *fp,const char *format,...);将拼接的字符串输出到文件。

    文本文件的写入:char *fgets(char *buf,int size,FILE *fp);从文件中读取一行数据,size一般是sizeof(buf),如果size大于一行的数据则读取整行,如果小于一行的数据就只读size大小的字节。如果函数读取成功则返回buf,如果失败则返回空,即0,一般通过判断返回是否为0来判断文件是否读取结束。 

    二进制文件的读写:二进制文件没有行的概念,存放的数据也不是字符串。我们可以直接把内存中的数据写入二进制文件,读取的时候也是直接从文件中读取指定大小的一块数据。

    二进制文件的写入:size_f fwrite(const void *ptr,size_t size,size_t nmemb,FILE *stream);参数说明:ptr,内存区块的指针,存放要写入数据的地址,可以是数组、变量或者结构体等。size:先固定填写1。nmemb,打算写入数据的字节数。fp:文件指针。返回值是本次成功写入的字节数。

    二进制文件的读取:size_f fread(const void *ptr,size_t size,size_t nmemb,FILE *stream);参数和写入的参数差不多,就是第一个参数变为接收读取的内容地址。如果读取成功返回值是读取到的字节数,失败或者读取完毕则返回0;

    在定义的一个FILE文件结构中,有存储一个指向文件当前读取到的位置的值,一般这个值由程序自己维护,就是刚打开文件的时候如果是r或者w的模式的时候这个地址指向文件的第一个字节,如果打开模式是a追加模式则指向文件的内部。并且每读取一次,这个读取到的位置的值就移动一次。C语言也提供了几个函数来操作这个当前的存取位置。long ftell(FILE *fp);返回当前文件位置指针的值,这个值是相当于文件开始位置的字节数。void rewind(FILE *fp);将位置指针移到文件开头。int fseek(FILE *p,long offset,int origin);offset为偏移量及要移动的字节数正数表示往右一定负数往左移动,origin为移动的起始位置0表示文件开头位置1表示当前位置2文件末尾位置。

    当我们在写入一个文件的时候,如果我们是一行行的写入它并不是写入一行就将一行写到文件当中,而是先将其放到缓冲区中,等到缓冲区满或者写入满了才写入文件。C中提供了一个函数来将缓冲区中的数据马上写入文件中。int fflush(FILE *fp);。比如每写入一行就调用这个函数就可以实现立即写入文件的效果。

    目录操作(需要包含<stdlib>头文件):获取当前目录char *getcwd(char *buf,size_t size);buf是一个字符串指针用来存当前目录的字符串,size是长度,如果获取到的目录长度大于size则返回null,否则返回指针buf。它的功能相当于pwd命令。

    切换目录:类似liunx的cd命令,int chdir(const char *path);返回0表示切换成功,非0表示切换失败。这个切换目录不会改变我们终端当前所在的目录,而是改变程序运行时的目录,比如改变目录后我们在用上面的获取当前目录的函数获取到的就是改变后的目录。

    创建目录(需要包含头文件<sys/stat.h>,并且在windows环境下好像不能用):int mkdir(const char *path,mode_t mode);,path时创建的目录的路径,mode是用来设置权限之类的东西当前可以默认填00755。

    删除目录:int rmdir(cont char *path);

    获取目录中的文件列表(包含<dirent.h>头文件):DIR *opendir(const char *path);打开目录,struct dirent *readdir(DIR *dirp);读取目录,int closedir(DIR *dirp);关闭目录。DIR是一个文件指针,就像我们读取文件的时候的FILE文件指针一样。dirent结构体中的内容有:long d_ino;索引节点号,off_t d_off;在目录文件中的偏移,unsigned shor d_reclen;文件名长,unsigned char d_type;文件类型(这里有很多种文件类型,我们只关心8和4,8代表常规文件,4代表目录),char d_name [NAME_MAX+1];文件名。struct dirent *readdir(DIR *dirp);这个读取目录的函数也是调用一次读取一个目录并不是一次性全部读取出来。

    判断文件的权限(需要包含unistd.h):int access(const char *path,int mode);,path是路径,mode是需要判断的权限类型:R_OK 4是否有读权限、W_OK 2是否有写权限、X_OK

    1是否有执行权限、F_OK 0是否存在。我们一般用这个函数来判断文件或者目录是否存在:access("/root/temp",F_OK);,满足条件返回0不满足返回-1。

    获取文件的信息(需要包含sys/stat.h):int stat(const char *path,struct stat *buf);,获取到的文件的信息保存在stat结构体中,执行成功返回0失败返回-1。stat结构体中有定义了很多属性,我们只关心其中的几个{mode_t st_mode;文件的类型和存取权限,off_t st_size;文件大小以字节为单位,time_t st_time;文件最后一次被修改的时间}。示例代码:

    struct stat st;

    if(stat("/root/test/1.txt",&st)!=0)return -1;

    if(S_ISREG(st.st_mode))printf("是一个文件");

    if(S_ISDIR(st.st_mode))printf("是一个文件夹");

    printf("filename=%s,type=%d,mtime=%ld,size=%d ","/root/test/1.txt",st.st_mode,st_mtime,st.st_size);

    文件重命名操作:int rename(const char *oldpath,const char *newpath);,类似于操作系统的mv命令。返回0成功-1失败。

    文件的删除:int remove(cont char *path);,类似于系统的rm命令。0成功-1失败

    时间操作:C种用time_t来表示时间类型,它其实是一个long类型的别名,在time.h头文件中定义。

    time_t time(time_t *t);获取1970年1月1日到现在的秒数。time_t now = time(0);0代表传入一个空指针地址,我们直接以返回值的方式接收调用的结果。由于获取到的是秒数不适用于我们日常的使用,struct tm *localtime(cont time_t *);用于将获取到的秒数转化为一个tm类型的结构体。

    tm结构体为{int tm_sec;秒数,int tm-min;分钟数,int tm_hour;小时数0-23,int tm_mday;日期天1-31,int tm_mon;月份数0-11,int tm_year;年份等于实际年份减去1900,int tm_wday;星期0-6 星期一是1,int tm_yday;从今天1月1日到现在的天数0-365 0表示1月1日,int tm_isdst;夏令时标识符}。

    mktime与localtime函数的意义相反,用于将结构体表示的时间转化为秒数,time_t mktime(struct tm *tm);

    程序睡眠(需要包含unistd.h头文件):undigned int  sleep(unsigned int seconds);程序睡眠多少秒,int unsleep(useconds_t usec);程序睡眠多少微秒。sleep(1);unsleep(1000000);都表示睡眠一秒。

    gettimeofday()用于获取当前的秒和微秒时间,需要包含sys/time.h头文件,秒是1970年1月1日到现在的秒数,微秒是当前秒已经逝去的微秒数。int gettimeofday(struct timeval *tv,struct timezone *tz);其中timeval结构体包含{long tv_sec;1970年1月1日到现在的秒数,long tv_usec;当前秒已经逝去的微秒数},结构体timezone包含{int tz_minuteswest;和greewich时间差了多少分钟,int tz_dstime;type of DST correction}。在调用这个函数的时候我们一般不关系时区,所以时区参数一般串0即一个空地址。

    errno获取系统的错误信息:当我们调用系统的库函数的时候,我们常常是通过函数的返回值来判断执行的操作是否成功,如果是失败了我们也不知道失败的原因。在C中提供了一个全局变量errno来存方系统函数调用过程中产生的错误码。errno.h头文件中包含这个全局变量。这个errno存储的是最近一次错误的错误码。要通过错误码获取相应的错误信息还需要char *strerror(int errno);这个函数来通过错误码获取相应的错误信息,这个函数在头文件string.h中声明。

    并不是所有的系统函数的错误都会存在错误码中,一般是操作系统的函数才会保存在错误码中。比如strcpy()这种算法实现类的函数就不会保存错误码。如下如果删除文件出错获取错误信息的示例:

    if(remove("/root/test/1.txt")!=0)

    {

      printf("删除文件失败,错误码为%d,详细信息:%s ",errno,strerror(errno));

    }

    C的编译预处理:C源程序编译为可执行的文件一般有这几个步骤:C源程序=》编译预处理=》编译=》优化程序=》汇编程序=》链接程序=》可执行文件

    预处理的过程主要是读取C程序代码对其中的预处理指令(以#开头的指令)和特殊符号进行处理,删除程序中的注释和多余的空白符号,产生新的源代码提供给编译器。

    我们可以在用GCC编译程序的时候加上-E参数来手动预处理生成预处理后的文件:gcc -E -o test.E test.c//test.E是生成文件。

    以#开头的预处理指令一般有下面几种:

    (1)包含文件。#include。可以是包含头文件#include <stdio.h>。也可以包含.c的文件,比如要包含我们自己写的帮助库文件的时候我们之前是在用gcc编译的时候,将_public.c文件写在编译的命令行里面,这里也可以直接用#incluede "_public.c"包含在代码中,就不用再gcc编译时输出包含的文件了。

    (2)宏定义指令。#define PI 3.14。相当于再预编译的时候做一个全局的字符的替换将代码中出现的PI替换为3.14。也可以定义带参数的宏,如:

    #define MAX(x,y)  ((x)>(y)?(x);(y))

    使用:int a = MAX(5,6);

    (3)条件编译。

    1/#ifdef 标识符

    程序段1

    #else

    程序段2

    #endif

    表示如果标识符名称的宏已经被定义了则执行代码段1,否则执行代码段2。

    2/#ifndef 标识符

    代码段1

    #else

    代码段2

    #endif

    这个和上面的用法相反,如果标识符未被定义则执行代码段1否则执行代码段2。实际过程中一般用来防止头文件被重复包含。

    如:我们自己写的_public.h文件

    第一行#ifndef _PUBLIC_H

    #define _PUBLIC_H 1

    中间代码

    最后一行#endif

    3/#undef 用于取消已经定义的标识符

    GDB调试C代码:首先用gdb -v查看系统是否安装了gdb调试工具,如果显示没有这个命令则安装:yum -y install gdb

    当我们用gcc -o test test.c编译test.c的文件的时候,生成的test文件没有包含源码,在运行test的时候是不能进行调试的。所以在编译的时候要多加一个-g参数:gcc -g -o test test.c,然后就可以用gdb调试这样打包出来的test文件。首先gdb test启动调试,接着可以用下面的命令设置调试的一些参数:

    set args xx xx 设置执行程序的输入参数(如果代码需要输入参数的情况),多个参数传入用空格隔开

    break 23 在23行设置断点

    run 运行程序,如果有断点会在断点处停下

    print xx 查看变量xx的值

    next 往下执行一条语句,不会进入函数内部相当于C#的F10

    continue 程序继续运行,直到遇到下个断点

    l 列出当前停下的断点前后的代码

    set ii=20 改变代码中ii变量的值为20,后续的运行使用到ii的地方值就为20.注:这里如果是给字符串赋值还是用ii="test",不用strcpy()

    step 往下执行一条语句,如果是函数则进入函数体内部(注如果该函数是调用的第三方函数没有源码的话是进不去的),和C#的F11类似

    makefile:make是一个liunx中的一个命令,用于执行makefile文件。由于在真正开发过程中一个C的项目可能很大,分布在不同的目录下,并且相互之间有依赖关系,有一定的编译的前后顺序,如果每次运行的时候都要去记住这这编译顺来来一个个的编译就很麻烦。makefile就是为了解决这个问题,程序员可以安装它的语法规则将怎个项目的编译顺序或者文件位置都先写在这个文件中,然后用make命令执行这个文件就可以按照你书写的规则一次性进行所有编译打包工作了。

    比如我们有四个C的文件test1.c、test2.c、test3.c、test4.c我们想一次性就将四个文件编译出来test1、test2、test3、test4个文件

    这里有一种其他的解决方案是使用shell脚本文件,这个同windows的shell脚本文件类似,就是将一些操作系统能够直接识别的命令写在一个文件中依次执行。

    由于我们编译的命令:gcc -o test1 test1.c其实也就是执行了一条命令,所以可以将下面这些命令保存为一个test.sh文件:

    gcc -o test1 test1.c

    gcc -o test2 test2.c

    gcc -o test3 test3.c

    gcc -o test4 test4.c _public.c

    然后执行sh test.sh就可以一次性执行这四个命令实现全部编译。

    其实makefile的原理和这个shell有点类似,但是makefile更加高级,可以通过语法指定编译其中某一个文件,并且如果其中某个源代码文件改变时在编译的时候就只去再编译那个改变的文件,避免重复编译。下面是一个简单的makefile文件编写,实现上面类似的功能:

    all: test1 test2 test3 test4//标准开头,说明执行make的时候执行这四个过程

    test1:test1.c//声明test1过程,冒号后面是该过程依赖的文件

    (这里前面必须空一个tab键)gcc -o test1 test1.c//过程执行的命令,这里可以按格式换行写多条命令,这些命令只要系统可以识别都行如:cd cp等都可以

    test2:test2.c//声明test2过程

    (这里前面必须空一个tab键)gcc -o test2 test2.c

    test3:test3.c

    (这里前面必须空一个tab键)gcc -o test3 test3.c

    test4:test4.c

    (这里前面必须空一个tab键)gcc -o test4 test4.c _public.c

    clean://声明clean过程,但没有声明在all里面,可以使用make clean来指定执行

    (这里前面必须空一个tab键)rm -rf test1 test2 test3 test4

    保存文件为makefile,执行的时候就直接用make即可,也可以用make clean指定执行makefile中的clean过程。

    makefile中还可以使用定义变量:

    CC=gcc

    all: test1 test2

    test1:test1.c

    (这里前面必须空一个tab键)$(CC) -o test1 test1.c

    test2:test2.c

    (这里前面必须空一个tab键)$(CC) -o test2 test2.c

    这里定义的变量就是在执行时将变量用指定字符替换。

    makefile的注解用#和//都可以

  • 相关阅读:
    【JS】事件传播
    【JS】鼠标跟随
    【JS】使用JS实现回到顶部按钮功能
    【JS】DOM获取元素方法
    【JS】DOM操作元素
    【JS】实现全选功能
    【JS】定时器的开启与关闭
    【JS】动态渲染页面
    【JS】标签页切换
    Elasticsearch(ES)简介
  • 原文地址:https://www.cnblogs.com/maycpou/p/12377265.html
Copyright © 2020-2023  润新知