• C语言知识总结(3)


    数组

    数组的特点:

    • 只能存放一种类型的数据,比如int类型的数组、float类型的数组

    • 里面存放的数据称为“元素”

    初始化方式

    int a[3] = {10, 9, 6};
    
    int a[3] = {10,9};
    
    int a[] = {11, 7, 6};
    
    int a[4] = {[1]=11,[0] = 7};

    常见错误

    int a[];
    
    int[4] a;
    
    int a[b];
    
    a = {10, 11};
    
    a[4] = {10,9,8,5}; 

    内存分析

    • 数组存储空间的大小

    • 存储空间的划分(内存的分配是从高地址到低地址进行的,但一个数组内部元素又是从低到高进行的

    • 数组名的作用,查看元素地址
    • 数组越界的注意

    二维数组

    二维数组是一个特殊的一维数组:它的元素是一维数组。例如int a[2][3]可以看作由一维数组a[0]和一维数组a[1]组成,这两个一维数组都包含了3个int类型的元素

    初始化

        int a[3][4] = {1,2,3,4,5,6};

        int a[3][4] = {{},{},{}};

        int a[][5] = {3,21,31,2,32,1};

    注意错误:

        int a[3][4];

        a[3] = {};

    字符串

    很多个字符组合在一起就是字符串了

    初始化

    • char a[] = “123”;  和 char a [] = {‘1’,’2’,’3’};的区别,可以比较大小

    • “123”其实是由’1’、’2’、’3’、’’组成

    • “123”的存储分布

    • 字符串的输出”%s”,’’是不会输出的

    的作用

    ''是一般字符串语句中的结束符号,可以判断字符串是否结束

    字符串数组

    初始化

    char names[2][10] = { {'J','a','y',''}, {'J','i','m',''} };  
    char names2[2][10] = { {"Jay"}, {"Jim"} };  
    char names3[2][10] = { "Jay", "Jim" };

    指针

    定义格式    类名标识符 *指针变量名;    如  int *p;

    先定义后赋值

    // 简单取值
    int a = 10;
    int *p;
    p = &a;
    printf(“%d”, *p);
    // 简单改值
    *p = 9;

    定义的同时赋值

    int a = 10;
    int *p = &a;

    清空指针

    p = 0;
    p = NULL;

    指针与数组

    1、指向数组元素的指针与普通指针变量一样,例如

    int a[5];
    int *p=&a[0];
    p=a; //数组名代表数组的首地址
    p=&a[1];//指针指向第二个元素 

    在数组里面,通过*(p+1)、*(p-1)就可以直接获取上下元素。
    2、指针下标:在C语言中,中括号[]是下标运算符,一般我们习惯用数组名[下标]对数组元素进行访问,实际上,下标运算符可用于指针运算,例如:

    int a[4];
    int *p=&a[1];  //指针p指向数组的第二个元素

    p[1] --> *(p+1) 下一个元素,即a[2]
    p[-1] --> *(p-1) 上一个元素,即a[0]

    下标运算符:D[N]没有规定D和N的顺序,N[D]也是可以的,例如:
    1[p] --> *(1+p)
    (p+1)[1] --> *(p+1+1) //用的人少
    3、访问数组元素的4种方式:  a[i]、 *(a+i)、p[i]、*(p+i) 

    #include <stdio.h>
    int main(){
      int x[] = {1,2,3};
      int s=1,i,*p=x+2;  //*p = &x[2]
      for(i=2;i>=0;--i)
        s -= (-i)[p]; // (-i)[p] = *(-i+p) 1-1=0 0-2=-2 -2-3=-5
      printf("%d
    ",s); // -5
      return 0;  //1[p] --> *(1+p)
    }

     指针与常量

    1、指向常量的指针:指针变量指向一个常量,此时指针所指向的值不能修改,但指针本身(存储的地址)可以指向另外的对象,例如

    int a = 3, b = 10;
    const int *p = &a;
    *p = 5;  //报错,不能修改
    p = &b; //可以修改 

    2、指针常量:声明指针常量,则指针本身不能改变。例如

    int a=3, b=10;
    int *const p = &a;
    p = &b;  //报错,不能修改
    *p = 5;  //可以修改

    理解:看const后面是什么,如果是*p则表示指针的值不能修改,如果是p则表示指针不能更换指向。
    3、类型转换:C语言中,如果将常量的地址赋值给非常量指针(类型转换),就可以修改地址里的值,从而影响代码中用到常量的表达式。例如

    const int a=5;  //常量
    int *p = (int*)&a;  //类型换换,去掉const
    *p = 100;   //修改地址指向的值
    int b = a+1;   //101 

    指针与字符串 

    1、字符串除了用字符数组处理外,还可以用字符指针来处理,字符指针指向字符串常量(连续存储区)的首地址。例如 

    char str[10] = "Hello";
    const char *pstr = "Hello";

    注意:"Hello"字符串常量为 const char* ,所以字符指针定义也要用const char*。(C语言中也可以只用 char*)
    2、赋值:可以将字符串整体赋值给字符指针,而字符数组除了初始化的时候是不能整体赋值的,如

    char str[10];
    const char *pstr;
    str = "Hello";  //错误
    pstr = "Hello"; //正确

    理解:字符数组名是一个常量,不能被赋值,而字符指针是一个变量,存储字符串常量的地址,变量的值(存储的地址)是可以改变的。 
    3、访问字符元素:访问字符串的字符元素,通过下标或指针移位的方式即可,和普通数组一样。如

    const char *p = "Hello";
    p[1];  //'e'
    p[1] = 'A';  //报错,不能修改字符串常量的元素
    char a[]="Hello", *pa=a;
    pa[1] = 'A';  //可以修改 

    注:要理解字符指针指向的是字符数组还是字符串常量。

    数组指针

    1、数组指针:指向一个数组的指针变量(注意不是指向数组的首元素)称为数组指针(也称行指针)。例如 

    int a[4];
    int *p1=a;  //指向数组首元素的指针,指向的类型是int
    int (*p2)[4]=&a; //数组指针,指针指向的类型是4个int组成的数组
    p1+1  --> a[1]
    p2+1  --> a[5]   //注意这里+1是4个int的大小

    注意:赋值要使用p2 = &a,如果用p2=a会报错,因为a的类型是 int *,  而&a的类型是int (*)[4],这是两种指针类型(虽然地址一样)。
    2、访问数组元素:由于数组指针指向一个数组,那么*p就是数组本身,(*p)[i]表示数组的第i个元素,例如

    int a[4]={10,20,30,40};
    int (*p)[4]=&a;
    (*p)[1] --> 20

    数组指针与二维数组  

    1、数组指针用于二维数组,指针指向以二维数组每一行数组,例如
    int a[3][4];
    //可看成3个int [4]的数组:a[0]、a[1]、a[2]
    int (*p)[4]=a; //定义指向int [4]数组的指针变量
    p --> a[0]
    p+1 --> a[1]
    注意:二维数组名a为int (*)[4]类型。
    2、访问数组元素,如
    p[0][1] --> a[0][1]
    *(*(p+1)+2) --> a[1][2]
    演示-用数组指针遍历输出二维数组{{1,2,3,4},{5,6,7},{8,9}}。
    注意:p[i][j]  -- > *(*(p+i) + j)

    #include <stdio.h>
    int main(){
      int a[3][4]={{1,2,3,4},{5,6,7},{8,9}};
      int i,j,(*p)[4]=a;
      for(i=0;i<3;++i){
        for(j=0;j<4;++j)
         //printf("%d ",p[i][j]);
           printf("%d ",*(*(p+i)+j));
         printf("
    ");
      }
      return 0;
    }

    指针数组与二维数组

    1、指针数组:指定义一个由指针组成的数组,即数组中的每个元素都是指针变量,例如

    const char *p[4];  
    //由p[0]..p[3]组成的数组,每个都是char*指针

    2、指针数组用于二维数组,下标表示二维数组的行数,例如: 

    int a[3][4];
    //可看成3个int [4]的数组:a[0]、a[1]、a[2]
    int *p[3];  //定义3个数组
    p[0] = a[0];  //指针赋值
    p[1] = a[1];

    3、数组元素:由于p[i]的类型是int*数组,其元素可以用 +j ,或下标[j]来访问,如
    p[0][1]  --> a[0][1]
    *(p[0]+1) --> a[0][1]
    演示-用指针数组遍历输出二维数组{{1,2,3,4},{5,6,7},{8,9}}。
    注:p[i][j]  -- > *(p[i] + j)

    #include <stdio.h>
    int main(){
      int a[3][4]={{1,2,3,4},{5,6,7},{8,9}};
      int i, j, *p[3];
      p[0] = a[0];
      p[1] = a[1];
      p[2] = a[2];
      for(i=0;i<3;++i){
        for(j=0;j<4;++j)
          //printf("%d ",p[i][j]);
          printf("%d ",*(p[i]+j));
        printf("
    ");
      }
      return 0;
    }

    指针数组与字符串

    指针数组适用于指向若干字符串(每个字符串不定长,用二维字符数组很浪费空间),会使字符串处理更加灵活,效率更高。例如
    const char *p[3]={"Hello","new world","how are you"};
    p[0][1] --> 'e'
    *(p[0]+1) --> 'e'   
    注:每一个字符串指针都是以''作为结尾。

    指向指针的指针

    1、指针变量*p存储的是一个地址,它自己也有地址,可以再用一个指针变量*prt指向p的地址,prt称之为指向指针的指针变量,简称二级指针,例如  int i, *p=&i, **prt=&p; 注意此时指针类型为“指向整型数据的指针变量”,不是整型数据。 prt = &i; //错误,指针类型不符
    2、对指针数组*p[n]来说,数组名代表首地址,每一个元素都是指针型数据,因此它就是一个二级指针,也可以用一个二级指针进行赋值及运算,例如
    int a[4]={1,3,5,7};
    int *pa[4]={&a[0],&a[1],&a[2],&a[3]};
    int **p=pa;
    **p --> 1
    **(p+1) --> 3

    指针与结构体

    1、结构体类型的指针变量,定义方式为
    struct 结构体类型 *变量名;
    struct student stu, *p;
    p = &stu;
    2、成员的引用:访问成员有以下三种方式:
    stu.name;
    (*p).name;
    p->name; //指针专用,指向运算符,优先级比单目运算符高
    p->age++; //先获得age成员,再++
    练习-根据姓名查询学分
    #include <stdio.h>
    #include <string.h>
    struct students{
        int no;
        char *name;
        int score;
    };
    struct students *find(struct students *s, int len, const char *name)
    {
        while(strcmp(s->name , name) != 0) 
          s+=1;
        return s;
    }
    int main()
    {
        struct students stu[] = {{14001, "张三", 60},
            {14002, "李四", 45},
            {14003, "张思", 81},
            {14004, "李武", 97},
            {14005, "张武", 75}};
        struct students *a = find(&stu[0], 5, "李武");
        printf("输出信息为,姓名:%s,学号:%d,成绩:%d", (*a).name, (*a).no, (*a).score);
        return 0;
    }

    指针作为函数参数

    函数参数是指针变量时,传递的是指针存储的地址,而变量做参数时传递的是具体的值(会产生形参的拷贝)。指针做参数时,地址(指针自己的值)不能被修改,但地址指向的值可以被修改。例如

    void change(int *p){
       *p = 20;
    }
    int main(){
       int a=5;
       change(&a);  //传入指针,在函数里a的值被修改
    }

    数组作为函数参数 

    数组名可作为函数的实参和形参,传递的是数组的首地址,在函数被调用时,对形参指向的值的修改会使得实参数组发生变化。由于传递的是首地址,那么也可以用指针来表示形参或实参,效果相同。
    1、形参实参都用数组名:

    void f(int arr[],int len){...}
    int main(){
      int a[10];
      f(a, 10);
    }

    2、实参用数组名,形参用指针变量

    void f(int *x,int len){...}
    int main(){
      int a[10];
      f(a, 10);
    }

    3、实参用指针变量,形参用数组名:

    void f(int arr[],int len){...}
    int main(){
      int a[10],*p=a;
      f(p, 10);
    }

    4、实参形参都用指针变量:

    void f(int *x,int len){...}
    int main(){
      int a[10],*p=a;
      f(p, 10);
    }

    演示-设计函数cat,将第二个参数的字符串被连接到第一个字符串后面。

    #include <stdio.h>
    void cat(char *p1, const char *p2){
      while(*p1) *p1++; //到p1的''位置,
      while(*p2)
        *p1++ = *p2++;  //将p2复制给p1的''后面位置
      *p1 = '';   //最后加个''
    }
    int main(){
      char s1[80]="Hello";  //长度要够
      const char *s2 = " world!";
      cat(s1,s2);
      printf("%s
    ",s1);
      return 0;
    }

    指针型函数

    返回值类型为指针的函数称之为指针型函数,如
    int *num(int x, int y);
    类型 *函数名([形参列表]);
    注意:返回指针类型意味着不需要生成拷贝,效率较高。
    理解:*的优先级低于()的优先级,因此函数名先和后面的()结合,以上格式意味着首先是一个函数,然后返回值是一个指针。如果是以下格式:int (*num)(int x, int y)
    则先将*和num结合,表示num是一个指针变量,指向一个函数(称为函数指针)。

    函数指针

    1、指向函数的指针变量称之为函数指针。例如
    int (*func)(int x, int y);
    类型 (*函数名)([形参列表]);
    函数指针变量指向一个函数的地址,可以将同样原型的函数赋值给变量,例如

    int max(int x,int y);
    int (*p)(int x,int y);
    p = max;  //将函数赋值给指针变量

    定义了函数指针变量以后,就可以用此变量来调用函数,也可以将之作为函数的参数(回调函数)。
    2、调用函数:用函数指针变量调用max和min函数。

    int max(int x, int y);  //求最大值
    int min(int x, int y);  //求最小值
    int (*p)(int x, int y);
    p=max;
    printf("%d
    ",p(2,3));  //3
    p=min;
    printf("%d
    ",p(2,3));  //2

    3、回调函数:又称callback,是一种事件驱动的编程模式,将函数指针变量作为参数传递给另外一个函数,函数里面当某些事件满足时会调用此函数指针变量指向的函数,例如。

    void print(int n){  //回调函数,打印信息
      printf("回调函数调用:%d
    ",n);
    }
    void loop(int max, int (*p)(int n)){ 
    //遍历1..n,如果7的倍数就打印信息
      int i;
      for(i=0;i<max;++i){
         if(i%7==0) p(i);
      }
    }

    理解:诸葛亮交给赵云三个锦囊,如果发生**事情,就打开第几个锦囊,这里的锦囊就是传递进去的函数指针变量。
    4、自定义函数指针类型:可以使用typedef来自定义一种函数指针类型,例如

    typedef (*MYFUNC)(int n); //自定义类型PRINT_FUNC
    MYFUNC p = print;  //将print函数赋值给变量p
    p(3);  //调用函数指针变量

    动态分配内存

    1、用malloc函数可以在程序中动态分配内存空间,返回void*指针,这时需要用一个指针变量将首地址保存起来,以后可以使用,如果没有保存,则这块空间就丢失了(称之为内存泄漏),例如

    int *p = (int*)malloc(sizeof(int)); 
    *p = 5;  
    p = (int*)malloc(sizeof(int));
    //p指向新地址,原内存丢失(泄漏)

    注意:需要#include <malloc.h>。
    2、动态数组:可以用malloc函数来定义动态数组。例如

    int *p = (int*)malloc(sizeof(int)*10); 
    //分配10个元素的数组
    p[0] = 1; 

    3、释放内存:free函数用来将动态分配的内存空间还给系统,之后系统可以继续使用回收的内存。例如

    int *p = (int*)malloc(sizeof(int));
    free(p);  
    p = NULL;
    int *p2 = (int*)malloc(sizeof(int)*5);
    free(p2); 
    p2 = NULL;

    注意:指针变量被释放以后,不能再赋值,因为它没有内存空间,申请内存后才能赋值或运算。为了防止继续赋值导致崩溃,建议将指针设置为NULL。
    4、栈和堆:malloc分配的内存空间从“堆”上申请内存,从低到高,而自动分配的如int a,是在“栈”上申请的内存空间,从高到低。

  • 相关阅读:
    Layui 数据表格显示图片,鼠标滑过图片放大
    ModuleNotFoundError: No module named redis
    Layui 数据表格特定数据行变色
    Qt下QMainWindow内部QTableView不能自适应大小
    android-启动另外一个Activity
    sqlite获取表字段
    Apache下更改.htaccess文件名称
    QTableView的indexAt使用方法
    Qt分割线
    QTableView排序
  • 原文地址:https://www.cnblogs.com/melodyzhy/p/4623281.html
Copyright © 2020-2023  润新知