黑马程序员--IOS基础第十二天(C语言)

本文深入讲解C语言中的高级主题,包括安全字符串操作、内存管理、动态内存分配、指针函数及函数指针、结构体定义与使用等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



1fgets()

    该函数一个文件操作相关的函数

    暂时使用这个函数可以从键盘商接收一个字符串,保存到数组中

  原来接收字符串保存到数组中的方法

     char str[50];

 

    1 scanf("%s",str);  //缺点:不能接收空格

 

    2 gets(str);       //优点:可以接收空格

                           //会有一个警告,不安全的

                           //不安全:比如数组长度是50,如果我们输入的

                           //的长度正好是50个,此时把50个字符全部存到

                           //数组中,存在问题 因为没有空间存放字符串结束符

 

   fgets()是一个安全的字符串接收的函数

 

     char ch[5];  //如果使用fgets此时数组中最多存放4个可见字符

                   //会自动的把数组的最后一个元素存放\0

   fgets()使用格式:

          fgets(数组名,数组长度,stdin);//std 标准 in 输入

   fputs();也是一个文件操作相关的函数

   格式:

      fputs(数组名,stdout);

 

 

#include <stdio.h>

#include <string.h>

 

int main(int argc, const char *argv[]) {

   

   char ch[5];

   //fgets从输入缓冲区读取一个字符串保存到字符数组中

   //当输入的字符串的长度大于数组的长度,此时fgets会自动的

      //把数组的最后一个元素变成\0

   //当输入的字符串的长度小于数组长度的时候,fgets还会接收回车

   fgets(ch, sizeof(ch), stdin);

   //去掉多余的\n

   if(ch[strlen(ch)-1]=='\n') ch[strlen(ch)-1]='\0';

 

   //\n  它的ascii码值10

   for (int i=0; i<5; i++) {

        printf("%d\t",ch[i]);

   }

   

   //fputs是不会自动换行

   //fputs也不能进行格式化的输出

   //puts 可以自动换行

   fputs(ch, stdout);

   //printf("--->%s\n",ch);

 

   

   

   return 0;

}

2.fgets(),fputs()优缺点:帮我们自动截取输入的字符串,使得我们队字符串的存取是安全的。

3.const

 1)什么是const:是一个类型修饰符,可以让变量的值不能改变。

 2)作用:(1)定义常量

          2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。

          3)可以避免意义模糊的数字出现,同样可以很方便的进行参数的调整和修改。同宏定义一样,可以做到不变则以,一变都变

          4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。

          5)可以节省空间

           6)提高效率

  3)如何使用const

    const使用的地方

 

 

    1)修饰变量

 

       int a = 10;

 

      printf("%d\n",a);  //10

 

       a = 200;

 

      printf("%d\n",a);  //200

 

    2)修饰指针变量

 

    3)修饰数组

 

#include <stdio.h>

/**

 * const修饰变量,使得变量变成常量

 */

void test1(){

 

    //使用const修饰变量a

    const int a = 10;

    printf("%d\n",a);  //10

   

    //a = 200;//read-only

    printf("%d\n",a);  //200

 

}

/**

 * 用指针强制修改const常量

 */

void test2(){

 

 

    //使用const修饰变量a

    const int a = 10;

    printf("%d\n",a);  //10

   

   

    int *p = &a;  //

    *p = 100;  //强制修饰常量

   

   printf("%d,%d\n",a,*p); //

 

}

 

int main(int argc, const char * argv[]) {

   

    int a = 10;

    int b = 20;

   

    //定义个指针变量,p指向a

    //此处的const的作用是指针变量p的指向可以改变,但是指向的变量的值

    //不能改变的

   

    //1const修饰的指针变量指向可变,指向的变量的值不可变

    const int *p = &a; //p初始化

               p = &b;  //重新修改了p的指向

    //*p = 1000;

   

    int const *p1 = &a;

               p1 = p;  //p1的指向也是可以改变

//    *p1 = 100;  p1指向的变量的值是不能改变的

   

    //2const修饰的指针变量,指针变量指向的变量值可以变,指向不能变

   

    int * const p2 = &a;

    *p2 = 2000; //值可以改变

    //p2  = &b; //p2的指向不能变

   

    //3const修饰的指针变量的指向和值,都不能改变

    const int * const p3 = &a;

//    p3  = &b;  //指向不能变

//    *p3 = 100;  //指向的变量的值也不能变

   

   

    /*

    

     记忆技巧:

    

        const * 的位置

    

        如果 const * 的左侧 表示指针变量指向的变量的值不能变,但是指向可以改变

    

        如果 const * 的右侧 表示指针变量指向的变量的值可以改变,但是指向不可以改变

    

        如果 const 出现在 *的两侧,表示指针变量的指向和值都不能改变

     */

    printf("a = %d\n",a);

    return 0;

}

4.内存分配方式:有三种

1)从静态存储区域分配:在定义的时候已经分配好

2)在栈上创建:在执行函数时,函数内局部变量的存储单元都可以在栈上创建

3)从堆上分配,亦称动态内存分配:程序在运行的时候分配。Mallocnew

5.内存的分区:

 

BOSS段:通常是指用来存放程序中未初始化的全局变量和静态变量

数据段:用来存放程序中已初始化的全局变量和静态变量;字符串常量等

代码段:存放程序执行代码的一块内存区域

堆:存放被动态分配的内存段

栈:存放用户临时创建的局部变量

6.动态内存分配:一般按照可以容纳可能出现的最多元素来做

 优点:简单

缺点:声明中引用了限制,如果程序需要使用的元素超过声明的长度:

  1. 解决的方法是把数组声明的更大一些,但是同时新的缺点又出现了,如果程序实际需要的元素数量比较少时,这样绝大部分的内存空间都被浪费了

  2. 如果超过数组容纳的范围时,程序必须有合理的响应,不应该由于一个异常而失败,但也不应该printf看上去正确实际错误的结果

7.常用动态分配内存分配函数:

   C语言中提供了3个动态内存分配函数:

 

    1malloc 函数

 

      格式: void * malloc(unsigned size);

 

      从内存的堆区分配大小为size个字节的连续的内存空间

      如果内存分配成功 返回内存的首地址

 

                失败  NULL

 

 

 

#include <stdio.h>

#include <stdlib.h>

/**

 * malloc函数的使用

 */

void test1(){

 

    //从内存中申请一块内存空间,可以存储4个整数

    // = 赋值,要求等号的左右两侧的类型要一致

    //p中存放的事新申请的内存空间的首地址

    //注意:malloc 申请的内存空间,如果我们不赋值?

    //     是垃圾数

    int *p = (int*)malloc(4*sizeof(int));   //16个字节

   

    //使用一个函数给malloc申请的空间进行初始化

    memset(p,'a',16);

   

    if (p!=NULL) {

       

        //申请成功做的事情

        //        *p = 10;

        //        *(p+1) = 100;

        //        *(p+2) = 1000;

        //        *(p+3) = 10000;   //存放4个整数

       

    }else{

        //内存申请失败

        printf("内存申请失败!\n");

        

    }

   

    for(int i=0;i<4 ;i++){

       

       printf("%c\t",*(p+i));

       

    }

 

}

 

void test2(){

 

    //calloc 分配指定块数和长度的内存空间

    //格式:calloc(块数,长度)

    //分配了4块,每一块内存长度为4的内存空间

    //他们的地址也是连续的

   

    //注意事项:

    //calloc 它使可以帮我们自动的初始化为0

    int *p = (int *)calloc(4,sizeof(int));   //16个字节

   

    if (p!=NULL) {

       

        //申请成功做的事情

        *p = 10;

        *(p+1) = 100;

        *(p+2) = 1000;

        *(p+3) = 10000;   //存放4个整数

       

    }else{

        //内存申请失败

        printf("内存申请失败!\n");

       

    }

   

    for(int i=0;i<4 ;i++){

       

       printf("%d\t",*(p+i));

       

    }

 

}

int main(int argc, const char * argv[]) {

   

    int *p = (int*)malloc(4*sizeof(int));   //16个字节

    printf("old %p\n",p);

    //realloc 函数可以给已经存在的空间扩充大小

    p = realloc(p, 40*sizeof(int));

    printf("new %p\n",p);

    //40个内存空间

    if (p!=NULL) {

       

        //申请成功做的事情

        *p = 10;

        *(p+1) = 100;

        *(p+2) = 1000;

        *(p+3) = 10000;   //存放4个整数

        

        *(p+39)= 1;

       printf("%d\n",*(p+39));

       

    }else{

        //内存申请失败

        printf("内存申请失败!\n");

   

    }

   

    for(int i=0;i<4 ;i++){

   

       printf("%d\t",*(p+i));

   

    }

   

    return 0;

}

8.内存泄露:

解决办法:在p被释放之前,先要释放堆区中的内存空间

Free函数使用:

定义函数:void freevoid *ptr);

假若参数ptr所指的内存空间已被收回或是未知的内存地址,则调用free()可能会有无法预期的情况发生。若参数ptrNULL,则free()不会有任何影响

9.指针函数:

 

   返回值是指针的函数

   指针函数的定义

      指针类型 * 函数名(){

          return 地址;

      }

 

#include <stdio.h>

 

//返回的事两个数中大数的地址

//返回的是形参x y的中大数的地址

int* max(int x,int y){

 

    printf("max x =%p\n",&x);

    printf("max y =%p\n",&y);

    return x>y?&x:&y;

 

}

 

int *max2(int *x,int *y){

    printf("max x =%p\n",x);

    printf("max y =%p\n",y);

    return *x>*y ?x :y;

 

}

 

int main(int argc, const char * argv[]) {

   

    //返回a b两个数中大数的地址

    int a = 3,b = 4;

    printf("a =%p\n",&a);

    printf("b =%p\n",&b);

    int *p =max2(&a,&b);

    printf("p = %p\n",p);

   

   

return 0;}

证明了:调用函数的时候形参变量没有新分配空间。实参和形参只是地址传递

10.

函数指针变量

 

        存放函数的首地址的指针变量就是函数指针变量

 

    函数指针变量的定义

 

      返回值类型 (*变量名)(函数的参数);

 

 

      函数的声明:

 

      int sum(int a,int b);----> 函数指针 int(*p1)(int a,int b);

  

      //定义了一个函数指针 p1

      //p1可以存放 返回值是int类型,并且有两个形参,形参的类型还都是int类型的函数的地址

 

      //函数指针的初始化

 

      p1 = sum;  //sum存的就是函数在内存中的首地址

 */

 

#include <stdio.h>

 

float f1(int x,int y){

 

    return x+y;

 

}

 

int sum(int a,int b){

 

    return  a+b;

 

}

int main(int argc, const char * argv[]) {

   

   

    int x= 3,y = 5;

    int s = sum(x, y);

    //定义函数指针

    int (*p)(int a,int b);

    //初始化函数指针

    p = sum;  //sum 函数的首地址

   

//    p = f1; 不可以的

  

    //注意:定义函数指针的时候,可以不用写形参名

    int (*p1)(int ,int );

    p1 = sum;

    return 0;

}

11.函数指针的使用

#include <stdio.h>

 

int sum(int x,int y){

   

    return x+y;

   

}

 

int jian(int x,int y){

   

    return x-y;

   

}

 

int cheng(int x,int y){

   

    return x*y;

   

}

 

int chu(int x,int y){

 

    return x/y;

 

}

 

int main(int argc, const char * argv[]) {

   

    //函数指针的使用

    //1、定义函数指针

    int (*p)(int ,int );

    //2、给函数指针初始化

    p = sum;  //p也指向了sum函数

   

    switch (3) {

        case 1:

            p = sum;

            break;

        case 2:

            p = jian;

            break;

        case 3:

            p = cheng;

            break;

        case 4:

            p = chu;

            break;

           

        default:

            break;

    }

   

    //3、用函数指针间接的调用函数

    //     (*p)(23,45);

    int s = p(23,45);

 

    printf("%d\n",s);

   

    return 0;

}

注意:(1)函数指针变量不能进行算数运算,这是与数组指针变量不同的。数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。
      
2)函数调用中(*指针变量名)的两边的括号不可少,其中的*不应该理解为求值运算,在此处它只是一种表示符号.

12.构造类型:

构造类型:

 

       由一个或者多个已定义类型的元素用构造的方法,构造新的类型

 

    构造类型:

 

       数组   结构体

 

    结构体:

 

       由相同类型或者不同类型的数据用构造方法,构造的类型

 

    结构体的定义

    

       struct 结构体名{

 

 

           成员列表;

 

       }; //注意此处的分号不能省

 

   比如要定义一个汽车的结构体

 

 

      struct Car{

 

          char *color;

          int lunzi;

          int speed;

 

      };

 

 

   定义一个iPhone的结构体

 

      struct iPhone{

 

          char *color;

          float  screenSize;

          int  sn;

          int  imei;

 

      };

 

   定义一个学生的结构体

 

     struct Student{

 

         char name[20];

         char sex;

         int age;

         float score;

         int sno;

 

     };

 

#include <stdio.h>

 

int main(int argc, const char * argv[]) {

   

    //定义一个车的结构体

    struct Car{

       

        char *color;

        int lunzi;

        int speed;

       

    };

   

   

    //定义一个iPhone的结构体

   

    struct iPhone{

       

        char *color;

        float  screenSize;

        int  sn;

        int  imei;

       

    };

   

    //定义一个学生的结构体

   

    struct Student{

       

        char name[20];

        char sex;

        int age;

        float score;

        int sno;

       

    };

   

    return 0;

}

13. 结构体变量有定义有三种方法

 

    1)先定义结构体,然后在定义结构体变量

 

      struct Student{

       

         //学生学号

         int sno;

         //学生姓名

         char name[21];

         //学生年龄

         int age;

         //学生成绩

         float score;

 

      };

 

      //注意:

      1)结构体定义完成以后,计算机并不会给结构体分配内存空间

      2)会在定义结构体变量后,分配存储空间

 

      //结构体变量定义格式:

      struct 结构体名 结构体变量名;

 

      struct Student stu1;  //这句话表示定义一个Student结构体类型的变量,变量的名称是stu1;

      //stu1因为是Student类型,stu1可以存放学生的学号、姓名、年龄、成绩

 

 

      struct Studentstu4,stu2,stu3;  //定义多个结构体变量

 

 

    2)定义结构体的同时,定义结构体变量

 

       格式:

 

       struct 结构体名{

 

 

       }结构体变量1,结构体变量2....;

 

         struct Student{

        

             //学生学号

             int sno;

             //学生姓名

             char name[21];

             //学生年龄

             int age;

             //学生成绩

             float score;

        

         }stu5,stu6,stu7; //也是用Student结构体定义了三个结构体变量

         //名称分别为stu5,stu6,stu7

        

 

 

    3)使用匿名结构体定义结构体变量

 

        struct {

 

 

 

        }结构体变量1,结构体变量2....;

 

        struct {

 

           char *color;

           int lunzi;

           int speed;

 

        }car1,car2,car3;

 

#include <stdio.h>

struct Student{

   

    //学生学号

    int sno;

    //学生姓名

    char name[21];

    //学生年龄

    int age;

    //学生成绩

    float score;

   

};

 

int main(int argc, const char * argv[]) {

   

    //第一种方法

    struct Student stu1;  //这句话表示定义一个Student结构体类型的变量,变量的名称是stu1;

   

    //第二种,定义结构体的同时,定义结构体变量

    struct Student{

       

        //学生学号

        int sno;

        //学生姓名

        char name[21];

        //学生年龄

        int age;

        //学生成绩

        float score;

       

    }stu5,stu6,stu7; //也是用Student结构体定义了三个结构体变量

    //名称分别为 stu5,stu6,stu7

 

    struct {

       

        char *color;

        int lunzi;

        int speed;

       

    }car1,car2,car3;

   

    return 0;

}

14.结构体变量的方位方法:

 结构变量名.成员名

例如:boy1.num

15.结构体变量的初始化

#include <stdio.h>

#include <string.h>

 

int main(int argc, const char * argv[]) {

   

    struct Student{

       

        //学生学号

        int sno;

        //学生姓名

        char name[21];

        //学生年龄

        int age;

        //学生成绩

        float score;

       

    };

   

    //先定义结构体变量,然后再初始化

    struct Student stu1;  //占用字节数?

   

    //给结构体变变量初始化

    stu1.sno = 38;

    stu1.age = 18;

    stu1.score = 59.99f;

    char name1[21]="张三丰";

    //name1 = "张无忌";

    strcpy(stu1.name, "张三丰a");

   

    printf("学号:%d\n年龄:%d\n分数:%.2f\n姓名:%s\n",stu1.sno,stu1.age,stu1.score,stu1.name);

    //1stu1.name 占用多个字节   21

    printf("stu1.name%ld\n",sizeof(stu1.name));

    //2stu1.name 字符串长度是多少

    //汉字的编码问题

    // UTF-8  xcode中默认的中文的编码是utf-8格式

    //        国际通用编码 一个汉字占用 3个字节

    // GB2312 GBK  国标 一个汉字占用2个字节

    printf("stu1.name 长度:%ld\n",strlen(stu1.name));

   

    //stu1.name ="zbz";  //

   

   

    //2、定义结构体变量的同时,进行初始化

    //初始化值得顺序要和定义结构体的顺序一致

    struct Student stu2={8,"凤姐",18,49.99f};

    printf("\n\n学号:%d\n年龄:%d\n分数:%.2f\n姓名:%s\n",stu2.sno,stu2.age,stu2.score,stu2.name);

   

    //3、定义结构体的同时,还可以指定元素的初始化

    struct Student stu3={.name ="lady Gaga"};

    return 0;

}

16.结构体占用的内存空间:是每个成员占用的字节数之后(考虑对齐问题)

17.结构体对齐的含义:

 1)结构体总长度

 2)结构体内各数据成员的内存对齐,即该数据成员相对结构体的起始位置

18.结构体变量占用存储空间大小

#include <stdio.h>

 

int main(int argc, const char * argv[]) {

   

    //计算结构体变量在内存中占用的字节数的方法

    //1)先找对齐模数

    //  对齐模数 是结构体中的基本数据类型中占用字节数最大的那个

    //          4

    //2)在计算结构体变量的中各个成员占用的字节和

   

    struct A{

   

        char ch[13];

        char ch1[11];

        int a;

  

    };

    //定义结构体变量

    struct A a1;

    printf("%ld\n",sizeof(a1));

   

    return 0;

}

19.结构体的作用域:

 分为:全局结构体、局部结构体

#include <stdio.h>

 

//定义一个全局的结构体

struct Person{

 

    int age;

    char *name;

 

};

 

int main(int argc, const char * argv[]) {

   

    //定义结构体变量

    struct Person p1={18,"胡一刀"};

    //此处是可以的

    char *str ="aa";

    str="bbb";

    p1.name = "苗人凤";

   

    printf("age = %d\nname= %s\n",p1.age,p1.name);

   

 

    {

        //定义一个结构体

        //定义局部的结构体,这个结构体和全局的结构体同名

        struct Person{

       

            int age;

       

        };

   

       

    }

    //此处定义的p2 是局部结构体Person的类型

    struct Person p2;

    p2.name = "户外i";

   

   

    return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值