美文网首页
C语言学习笔记(二)

C语言学习笔记(二)

作者: 皮皮雅_ | 来源:发表于2019-07-20 14:31 被阅读0次

    1.指针是一种数据类型,装地址的数据类型。注意指针类型(对偏移,内存操作理解很重要),什么样的类型决定了操作空间的大小。地址加减,就是加减一个类型的大小。*代表间接访问运算符。

    #include<stdio.h>
    main()
    {
        //int a = 12;
        //int *p = &a;//理解:首先声明一个指针类型的变量p、再取a的地址赋值给p。这里的*,起标记作用,标记p是一个指针变量
        //p = &a;
        //printf("% p %p %p %d\n", p, &a,&p,*p);//&p取p的地址,与前两个不同。p装的是a的地址.这里*是间接访问运算符或内存操作运算符
        //a = 14;
        //printf("%d\n", *p);//14 改变指向变量的值,也改变了指针的值
        //*p = 145;
        //printf("%d", a);//145 改变指针的值,也改变了指向变量的值
        
        int a = 12;
        int *p = &a;//切记:p是一块空间,有他自己的地址
        int *p3 = p;
        printf("%d\n", *p3);//输出12 ,p代表a的空间地址,*p3获取的就是a的值
        //证明不同
        //int *p1 = &p;//   报错:“p1” : “int *”与“int **”的间接寻址级别不同
        
        int **p1 = &p;//二级指针。、,装一级指针地址
        *p1 == p;//此时两者的关系 true
        *p == a;
        
        getchar();
        return 0;
    }
    

    2.指针与数组二级指针与数组无关,涉及到数组与指针,利用好数组就是地址+[偏移量]的思维。指针数组(地址数组)(例:int p[5]):用数组装入地址的数组指针(例:int (p)[5]):数组类型的地址**

    #include<stdio.h>
    int main(void)
    {
        int a[5] = { 9,6,4,5,4 };
        int *p = &a[0];
        printf("%p %p %p %p %p\n", p,p+1,p+2,p+3,p+4);//运算完p没有改变,用多条语句++p,也可以实现改变地址,但是会引起p的改变
        printf(" %p %p\n", &*(p+2), &p[2]);
        int i ;
        for ( i = 0; i < 5; i++)
        {
            printf("%p\n", *(p + i));
            //printf("%p\n", *p++);//注释上一句结果同上9 6 4 5 4,此语句p++改变了p的指向
            printf("%d ,%d\n",a[i],p[i]);//指针的下标运算(指定首地址,且为连续的空间),两者结果相同 (地址+[偏移量])。
        }
    
        printf("%p %p %p %p %p %p\n", a, &a[0], &a[1], &a[2], &a[3], &a[4]);//a也是首地址,上下两者输出一样。
    
        //**指针数组
        int a1 = 1, b1 = 2, c1 = 3;
        int *arr[3] = { &a1,&b1,&c1 };
        printf("%d %p\n", *arr[1], arr[1]);//分别用来修改值,修改地址
        //指针数组的拉链结构
        int b11[3] = { 1,2,3 };
        int c11[2] = { 2,5 };
        int d11[4] = { 1,3,2,44 };
        int e11[5] = { 45,6,5,4,26 };
        int f11[2] = { 4,9 };
        int *a11[5] = { b11,c11,d11,e11,f11 };
        printf("a11:%d\n",a11[2][2]);//二维数组的方式访问,但是二维数组地址是连续的,一般拉链结构地址不连续,相互分开不同。大小不同但类型要同
        
        //**数组指针
        int a2[5] = { 3,4,2,6 ,8};
        int*p2 = &a2[4];
        //int *p22 = &a;//类型不兼容报错,根据报错找类型  warning C4047: “初始化”:“int *”与“int (*)[5]”的间接级别不同
        //下标不一样,类型是不同的。int [6] int [5]不同类型的,可用上句测试
        int(*p22)[5] = &a2;
        //数组指针使用
        p22[2];//为a2数组的地址,再偏移2(2个a2数组的大小,2*5*4)
        printf("第二个:%d\n", (*p22)[2]);//输出2,取值设置等操作
        printf("%p %p\n", &(*p22)[2],&a2[2]);
        getchar();
        return 0;
    }
    

    3.二维数组与指针。声明的指针指向哪个地址,操作的就是对应层次的地址。一个指针指向一个变量,p对应他本身(若p=&a[0][0]即*p==a[0][0])。指针的大小取决于编译环境。32位编译环境下的全为4字节,64位位8字节(vs编译器可以切换)。

    #include<stdio.h>
    int main(void)
    {
        //int a = 12;
        //int *p = a;//警告:“int *”与“int”的间接级别不同
        //printf("%p %d\n", p, *p);//输不出值
        int a[2][3] = { {2,5,3} ,{3,5,3} };
        int *p = &a[0][0];//==a[0]
        for (int i = 0; i <= 5; i++)
        {
            printf("%d\n",*(p+i));//可以全部输出,因为存的空间是连续的
        }
    
        int (*p1)[3] = &a[1];
        int(*p2)[2][3] = &a;//对比a[2][3]发现数组指针与数组一种形式上的关系。但是每一种声明的方式,指向层次都是相同的。可以和上一句对比
        int i, j;
        for ( i = 0; i < 2; i++)
        {
            for (j = 0; j < 3; j++)
            {
                printf("%d\n", (*p2)[i][j]);
    
            }
    
        }
        
        getchar();
        return 0;
    }
    

    4.堆区和栈区 内存分为:栈区(由系统申请),堆区(由自己申请释放),全局区,字符常量区,代码区。malloc申请一段连续的指定大小的空间,并返回首地址。

    #include<stdio.h>
    #include<stdlib.h>//两个都是malloc/calloc的库文件
    #include<malloc.h>
    #include<memory.h>
    int main(void)
    
    {
        //void * maloc(size_t size)
        int *p = (int*)malloc(4);//参数为字节数(除4得一,代表只有一个数),将空间标记为int*类型。4默认转为无符号的了,或者写成4u
        p = (int*)malloc(4);//再申请的话会造成空间丢失,内存泄漏。但还是被占用的
         //地址的类型决定了,地址每次访问的大小。size_t==unsigned int
        //申请不到失败,返回NULL
        *p = 2;//赋值
        //空间赋值,字节设置值
        memset(p, 0, 4);//memcpy才是数组赋值。参数1为赋值变量,参数二所赋的值,参数三字节数
    
    
        if (NULL==p)
        {
            printf("申请失败!!");
        }
        int a = sizeof(size_t);//32位编译器环境是4字节,64位是8字节
        printf("%p\n",p);
    
        free(p);//释放空间,把空间还给操作系统
        printf("%p\n", p);//释放后p就是野指针了(访问受限)。还有就是定义指针为初始化也是野指针
    
        //malloc与数组
        int *p1=(int*)malloc(sizeof(int) * 5);
        int a1[5];
        //int *p2 = &a1[0];
        //p1 = p2;
        /*for (int i = 0; i <= 5; i++)
        {
            printf("%d\n",*(p+i));
    
        }*/
       //free(p2);
    
        //数组类型
        int (*p2)[5]=(int(*)[5])malloc(sizeof(int) * 5);
        (*p2)[5] = &a1;
        printf("比较:%d\n",*p2==a1);//0
        printf("比较1:%d\n", *p2 == a1);//1
        for (int i = 0; i < 5; i++)
        {
            printf("%d\n", *( p2)[i]);
    
        }
        free(p2);
        int(*p3)[2][5] = (int(*)[2][5])malloc(sizeof(int) * 5);
    
        //calloc
        int*p4=(int*)calloc(5, 4);//5个元素,每个4字节;申请数组比较方便
        for (int i = 0; i < 5; i++)
        {
            printf("%d\n",p4[i]);//区别之一,全部初始化为0
    
        }
        free(p4);
        //realloc重新修改字节
        printf("%d\n", _msize(p));//_msize()查看malloc出的空间字节大小
        int *p5 = (int*)realloc(p, 20);//重新分配20字节
        getchar();
        return 0;
    }
    

    5.函数基本使用

    #include<stdio.h>
    #include<malloc.h>
    void fun1();//函数声明:先声明,函数体可以放最后
    int fun2();
    int fun4(int a, float b);
    int fun5(int *p);
    void fun6(int **p);
    void fun(void)//无返回值,无参数。 整体放在最后面调用报错。使用函数声明来解决
    {
        printf("这是一个函数!");
        return;//终止函数
    }
    //返回多个值
    int * fun3(void)
    {
        int *p = (int*)malloc(8);
        *p = 4;
        p[1] = 5;
        return p;//返回指针
    
        //int p1[2] = { 4,5 };//虽然表面输出没问题,这是c自身的bug。会有警告
        //return p1;//返回栈区,这里p1为栈区局部变量,运行完函数后,空间就被释放了,使用的是非法空间
    }
    
    int main(void)
    {
    //一般函数的执行略慢一丢丢于未封装的代码。因为涉及到跳转
        fun();//调用:函数地址+参数列表
        printf("%p %d %d\n", fun,&fun,fun==&fun);
        (&fun)();//结果同上,不要忘记前面的小括号。
        void(*p)(void) = fun;//函数指针
        fun1();
        printf("返回值:%d\n",fun2());
    
        //返回多个值函数测试
        int*a = fun3();
        printf("%d %d\n",a[0],a[1]);
        free(a);//记得释放堆空间
    
        //通过函数修改外部值,思路修改地址,把地址传进函数
        int b = 2;
        int *p1 = &b;//p1代表(装载的)b的地址,p1自己还有一个自己空间的地址
        fun5(p1);
    
        //通过函数修改指针变量
        fun6(&p1);
    
        getchar();
        return 0;   
    }
    
    void fun1(void)//函数定义 
    {
        printf("这是一个fun1!");
    }
    int fun2(void) 
    {
        printf("无参有返回值函数!");
        return 4;
    }
    int fun4(int a,float b)
    {
        printf("两参数 %d %d\n",a,b);
        return 4;
    }
    int fun5(int *p) {
        *p = 24;
        printf("xiugao的%d\n",*p);
    }
    void fun6(int **p) {
        *p = NULL;
    }
    

    5.较为特殊的函数和用法(数组参数,递归函数),递归注意递归控制变量

    #include<stdio.h>
    #include<stdarg.h>
    //一维数组
    void fun(int *p,int length)//int *p改为 int p[]([]可以写任何整数字),数组做形参会被解析成指针
    {
        for (int i = 0; i < length; i++)
        {
            printf("%d\n",p[i]);
        }
    }
    //二维数组
    void fun1(int (*p)[3],int hang,int lie)//同一维的改写,参数int p[任意][3]
    {
        for (int i = 0; i < hang; i++)
        {
            for (int j = 0; i < lie; i++)
            {
                printf("%d\n",p[i][j]);
            }
        }
    }
    //函数类型
    int fun2() {}
    int fun3(int a) {}
    void fun4(int a) {};
    
    //递归函数
    int fun5(int n)
    {
        if (n==1 || n==2 || n==0)
        {
            return 1;
    
        }
        else {
            return fun5(n - 1) + fun5(n - 2);
        }
    
    
    }
    //指定未知参数个数a
    void fun5(int a,...)
    {
        va_list ap;//定义参数数组
        va_start(ap,a);//将参数装进数组
        printf("%d\n", va_arg(ap, int));//将数取出来,就没了。再取就是 顺序往下取第二个
        printf("%lf\n", va_arg(ap, double));
        printf("%d\n", va_arg(ap, int));
    }
    int main()
    {
        int a[5] = { 1,2,3,4,5 };
        fun(a, 5);
    
        int a1[2][3] = { {1,2,3},{4,5,6} };
        int(*p)[3] = a1;
        fun(a1,2,3);
    
        //函数地址 地址类型:有返回值类型,参数类型个数决定
        //int ();对应的类型去掉名字,保留参数
        //int (int a);
        int(*p1)(int a) = fun3;//int a对应fun3的参数列表
        //int (*p1)(int a) = fun4;//也是可以的
    
        //递归斐波拉契数列,通项公式型
        printf("递归:%d\n", fun5(6));
        //多参数
    
        fun5(3, 12, 34.34 , 78);//3是指参数个数
    
        getchar();
        return 0;
    }
    

    6.字符

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h> 
    int main(void)
    {
        printf("%c\n",'A');//要用'',不然编译器会吧字母当做变量
        printf("%c\n", 64);//输出字符@
        printf("%d\n", 'A');//65
        putchar('a');//输出
        putchar('\n');
        char a = 66;//1字节
        printf("%c\n", a);//B
        //scanf我们在控制台的输入,系统会开辟一块空间(输入缓冲区)存储我们的所有输入,按下回车结束
        int d, f;
        scanf("%d", &d);//从缓冲区拿数据,输入时2 3连续输,中间带空格可正常输出。回车后,这时scanf就去缓冲区取相应类型的数据
        scanf("%d", &f);//去缓冲区读取数据
        printf("%d %d\n", d, f);
        //清空缓冲区 setbuf(stdin,NULL)(c标准)和fflush(stdin)(不标准,有些编译器不支持)还有while((c=getchar())!='\n'&&c!=EOF);
        setbuf(stdin,NULL);
        char e, g;
        scanf("%c", &e);//字符类型的输入,回车和空格对scanf的读取有影响,输入一个字母回车。此时相当于存储了字母1和\n(回车)
        scanf("%c", &g);//一般直接输入两个相连例如ab
        printf("%c %c\n", e, g);//d存储为输入的字母,f存储的是\n
        
        //scanf_s读取格式
        scanf_s("%c \n", &e, 1);//1表示字节数,多个输入加多个指定的字节数
        //char h =_getch();//随输入随读取,不需要回车确认。头文件conio.h
        
        char c1 = 0;//转换大小写
        while (1)
        {
            scanf("%c", &c1);
            if(c1>=65&&c1<=90)
            {
                printf("%c",c1+32);
            }
            else if (c1 >= 97 && c1 <= 122)
            {
                printf("%c", c1 - 32);
            }
            else if (c1 == '\n')
                break;
        }
        //字符数组
        char arr[5] = { 'r','g','g','r','r' };
        
        getchar();
        return 0;
    }
    

    7.字符串(以\0 (0)结尾的字符数组)

    #include<stdio.h>
    #include<string.h>
    int main(void)
    {
        char str[3] = { 'a','b','e' };//这是纯字符数组
        char str1[3] = { 'a','b','\0' };//这就是字符串了
        printf("%s\n", str);//输出乱码,因为结束不了
        printf("%s\n", str1);//输出ab(遇到\0(0)结束,\0转义为0),不输出\0。用%c可以输出\0
        
        printf("%s\n", "dfaf 522!!");//常量字符串,自带\0(dfaf 522!!\0)
        char *p = "aaa555 55";//双引号返回字符串的首地址
        char *pp = 'a';//
        printf("指针输出字符%c\n", *p);
        printf("指针输出字符地址%p \n", p);
        //*p = 'w';//崩了,内存地址指向非法
        //p [0]= 'w';//报错:写入位置 0x00966B44 时发生访问冲突。p指向的是一个常量区字符串地址,无法再进行修改。
        //p = "wfwfgwg";//ok
        //printf("指针输出字符串%s\n", *p);//直接炸掉
        printf("指针输出字符串%s\n", p);
        printf("查看*p %c\n", *p);//输出w
        printf("我也可以输出!!");//定义中第一个参数为char* 而""刚好返回地址
    
        char str11[15] = "1546 eege";//常量字符串不能修改。字符串在栈区数组和常量区都有
        str11[1] = 'w';//改变的是栈区的
        char str2[] = "vg1546 eege";//不用考虑越界
        //str1[10]="wdqwfq";错误的不能一次转入。通过循环可以
        char *p1 = "de";
        int i = 0;
        for (; *p!='\0'; p++)
        {
            str2[i] = *p;
            i++;
        }
        str2[i] = '\0';//给结尾加个\0,不然就是替换前两个而已。
        strcpy(str2, str11);//将str11赋值给str2(str2空间要足够大)
        printf("%s\n", strcpy(str2, str11));
        //strcpy()//类比于scanf_s(str,10,str1)多了个指定(参数1的)字节数
        
        strncpy(str2, "fwfwgwg", 3);//3表示自从字符串中取三字节到str2中
        strncpy_s(str2,10, "fwfwgwg", 3);//多了个指定参数1的字节数
    
        char str22[20];
        scanf("%s", str22);//输入字符串后面默认加了\0,输入的空格相当于一个分割符,不读空格。
        //scanf("%s", str22,19);//保留一个字节给系统输入\0
        //gets(str22);//读入全部,包括空格
        //gets_s(str22,19);//总结_s系列的函数,主要优化了越界处理和报错,方便查找。在本句就报错,不再程序执行完在报错
        printf("%s",str22);
        
        char str3[3] = "谭";//字符串装入汉字两个字节,另外后面有个\0;或者百度汉字国标码
        //求字符串长度
        size_t a = strlen("156466");//只读取字符数,不读取\0. size_t无符号整形重命名
        //字符串比较,切记与字符串长短无关,按顺序比较asc2码大小
        int result = strcmp("abcdff", "abef");//前面的字符串大于后面的返回>0(两字符的差值),小于的返回小于0((两字符的差值),带符号),相等返回0;
        //字符串拼接
        strcat(str3, "f");
        strcat(str3, "ferbe",1);//选一字节拼到str3
        //将数字字符串转为整数
        int aa = atoi("255");//头文件stdlib.h
        int aa1 = atoi("gwg255");//返回0
        int aa2 = atoi("255gree");//返回255
        //整数转字符串
        char stra[20] = { 0 };
        itoa(235, stra, 2);//将235以二进制存于stra
        //sprintf将后面的值全转为字符串存到stra里
        sprintf(stra, "%d,%c,%f", 12, 'v', 12.3f);//不会输出到控制台,要用printf
        //测试一些转义字符的字节数
        printf("%u\n", strlen("\n"));
        //  \123 \0123 数字都为八进制的、\xf45为16进制的
    
        //字符串数组
        char *sta1[3] = {"fwef","regfwef","tjyy"};
    
        getchar();
        return;
    }
    

    8.结构体及数据存储。32位和4位cpu一次处理的数据分别为4字节,8字节。数据存储规则:字节对齐/内存对齐。

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    //手动设置字节对齐,这里表示每次存四字节
    #pragma pack(4)
    
    //在全局区定义则全部区域可用
    struct Stu
    {
        void(*p)(void);//结构体不能定义函数,若要传入函数,就用指针
        char name[10];
        int age;
    
    }stu1,stu2;//声明变量方式1
    
    struct 
    {
        struct Stu stuu;//结构体嵌套
        char name[10];
        int age;
    
    }stu3={ "蔡徐坤",21 };//没名字的要在下面声明办理,不然其他地方难声明。这里初始化是全局的
    
    void fun(void){}
    
    struct Stu1
    {
        char name[10];
        int age;
    };
    
    int main(void)
    {
        struct Stu student = {"蔡徐坤",21};//声明变量,初始化(按顺序)。外面的变量也可这样初始化,初始化就是全局的,这里的是局部的
        //初始化指定元素struct Stu student = {.age=23 };
        struct Stu *p = &student;
        p->age;//结构体指针调用方式
        (&student)->name;//用点会报错
        strcpy((*p).name,"ysl");//设定定义字符串的值,不能直接用p->name="ysl"
        strcpy(p->name, "ysl2");
        student = (struct Stu) { "蔡徐坤2", 21 };//复合文字结构
    
        struct Stu student1 = {fun};//传入函数的地址
        //结构体数组
        struct Stu student3[3] = { { "蔡徐坤3", 21 },{ "蔡徐坤4", 21 },{ "蔡徐5", 21 } };
        //结构体大小也用sizeof(),计算规则依据于内存对齐
        struct Stu1 student11 = { "蔡徐坤啊",21 };//
        printf("%u %u\n",sizeof(struct Stu1), sizeof(student11));
        //(顺序存储,读取顺序)计算规则:1.以最大类型为字节对齐宽度2.一次填补各个成员字节3.结尾补齐。另外数组的话,就看初始定义的大小char a[10],再根据基本类型中最大的(不是a[10])读取字节数取算
    
        system("pause");
        return 0;
    }
    

    9.联合体和枚举

    #include<stdio.h>
    #include<stdlib.h>
    
    //联合类型,所有成员共用一块内存。修改一个变量其他的也会被改变
    union Un
    {
        char c;
        short s;
        int i;
    
    }un1;
    
    //枚举 一组有名字的 int常数
    enum Color {red ,black,white,blue=20};
    
    
    int main(void)
    {
        printf("%p %p %p",&un1.c, &un1.s, &un1.i);//都一样
        union Un un2 = {.i=34};//只那能初始化一个
    
        enum Color co = 202;//大小就是4字节
        printf("%d %d %d %d %d",red,black,white,blue,co);// 0 1 2
        system("pause");
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:C语言学习笔记(二)

          本文链接:https://www.haomeiwen.com/subject/pyjdkctx.html