C语言中的数组和指针相关知识

  • 定义数组
    • <类型>变量名称[元素数量]

int grades[100];

double weight[20];

  • 元素数量必须要是整数
  • 数组内部的元素需要初始化,默认情况下是随机值
  • 数组的集成初始化(以下三个指令均可以在vscode中可以实现,本机是C11

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

int b[10]={67};  

  • 这是集成初始化的另一种常见形式:表示b[0]=67,其余的所有元素都是0

int a[10]={[0]=2,[2]=3,6};//集成初始化时的定位(C99中使用)

  • 用[n]在初始化数据中给出定位
  • 没有定位的数据接在前面的位置后面
  • 其他位置的值补零
  • 也可以不给出数组大小,让编译器计算
  • 比较适合于初始数据稀疏的数组
  • 假如想要设置一个数组的所有元素都是a,一般可以采用for循环遍历的方式
  • 直接用大括号给出数组的所有元素的初始值
  • 不用给出数组的大小,编译器会替我们数数
  • 什么是数组?
    • 数组是一种数据容器(特点如下)
      • 其中的所有元素具有相同的数据类型
      • 一旦创建,不能改变大小
      • 数组中的元素在内存中是连续依次排列的
  • 数组的单元
    • 数组的每个单元就是数组类型的一个变量
    • 使用数组是放在[ ]中的数字叫做下标或索引,下标从0开始计数
  • 有效的下标范围
    • 编译器和运行环境不会去检查下标是否越界,无论是对数组单元读还是写
    • 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃
    • 长度为0的数组是可以存在的,但是没有用
  • 数组的大小
    • sizeof给出整个数组所占据的内容的大小,单位是字节
    • 求出数组的大小

sizeof(a)/sizeof(a[0])

数组大小=用数组a在内存中占据的字节数÷数组a中某一个元素所占据的字节数

  • 数组的赋值
    • int a []={2,3,4,6,8,5,8,9,4,2};
    • int b[]=a;
    • 数组本身不可以被赋值
    • 要把一个数组的所有元素交给另一个数组,必须采用遍历的方式
    • 遍历一般使用for循环
  • 数组作为函数参数
    • 不能在[ ]中给出数组的大小
    • 不能再去利用sizeof去计算数组的大小
    • 因此在数组作为函数参数时,通常必须再使用另外一个参数来传入数组的大小
  • 利用数组求解100以内的素数
  • #include <stdio.h>
    
    int main()
    
    {
    
        const int maxNumber = 100;
    
        int isPrime[maxNumber];
    
        int x;
    
        /*先将数组进行初始化,每个元素都设置为1*/
        for (int i = 0; i < maxNumber; i++)
    
        {
    
            isPrime[i] = 1;
    
        }
    
        /*从x=2开始标记,将不是素数的数所在数组中的位置标记为0
        例如6不是素数,那么数组下标为6的变量标记为0*/
        for (x = 2; x < maxNumber; x++)
    
        {
    
            if (isPrime[x])
    
            {
    
                for (int i = 2; i * x < maxNumber; i++)
    
                {
    
                    isPrime[i * x] = 0;
    
                }
    
            }
    
        }
    
        /*标记完之后,对根据数组的标记将下标打印出来,这个下标就是我们想要的素数*/
        int cnt = 0;
    
        for (int i = 2; i < maxNumber; i++)
    
        {
    
            if (isPrime[i])
    
            {
    
                cnt++;
    
                printf("%d\t", i);
    
                             /*负责整理输出格式*/
                if (cnt % 6 == 0)
    
                {
    
                    printf("\n");
    
                }
    
            }
    
        }
    
        printf("\n");
    
     
    
        return 0;
    
    }

     

  • 二维数组
    • int a[3][5];
    • 表示a是一个3行5列的矩阵
  • 二维数组的遍历
  • for(i=0;i<3;i++)
    
    {
    
    for(j=0;j<5;j++)
    
        {
    
    a[i][j]=i*j;
    
        }
    
    }

     

  • a[i][j]是一个int变量
  • 表示第i行第j列上的单元
  • a[i,j]=a[j]
  • 二维数组的初始化

int a[ ][ 5 ]={

{0,1,2,3,4,5},

{2,3,4,5,6,7}

};

  • 列数必须给出,行数可以由编译器来数
  • 每行元素用一个{ },逗号进行分隔
  • 如果省略,表示补零
  • 也可以用a[ i ] [ j ]=n,的方式进行定位
  • &运算符---查找地址
    • %p对应的是&变量,用于输出变量的地址
    • &只能对变量取地址,如果不是变量(a+b、a++)将无法取地址
    • &加变量名时,给出的是变量所在的地址
    • 注意:C语言中,主函数和子函数各自的局部变量名称相同时,每个变量依旧是相互独立的变量,拥有独立的地址和各自的值。
    • 地址的占据的内存大小与编译器是64位、32位有关系
  • 什么是指针?
    • pointer是一个值为内存地址的变量
    • 指针是一个可修改的变量,它的值就是这个指针所指向的地址
      • char类型---字符
      • int类型---整数
      • pointer类型---地址(指针就是地址
    • 间接运算符(解引用运算符):*指针或地址
      • 和指针一起工作,用于找出指针指向的地址单元(变量)的内容
      • 例如:ptr=&bah  //表示指针ptr指向变量bah所在的地址

          val=*ptr  //表示取出指针ptr所指的地址单元的内容

综上:bah=val

  • (*)+(指针或地址)给出的是指针指向的地址存储的单元内容
  • 声明指针变量
    • 指针一旦定义就初始化为0----int *p=0(指针定义的好习惯)
    • 声明时必须要说清楚指针指向的变量是什么类型(int、float、char)
    • 例如:int *pi---pi是一个指针,且*piint类型pi可以说是指向int类型的指针
    • 一般情况下声明指针时:*和指针名之间有空格;解引用变量时无空格
  • 注意:指针是一个新类型,不是整数类型
  • 什么时候形式参数是指针?什么时候形参是普通类型?
    • 使用普通变量
      • 如果调用函数时,传递的是数值,那么形参应该是普通类型
    • 使用指针的情况
      • 如果要在被调函数中改变主调函数的变量,那么调用函数时传递的参数就是地址,那么形参应该是指针
      • 函数用于返回运算的状态,结果通过指针返回
        • 让函数返回特殊的不属于有效范围内的值来表示出错
      • 函数返回多个值,某些值就只能通过指针返回
        • 传入的参数实际上是需要保存带回结果的变量(可以理解为传入的参数是变量而不仅仅是值)
    • 使用指针的常见错误
      • 定义了指针变量,还没有指向任何变量,就开始使用指针

int *p;

*p=123;

  • 变量
    • 编写程序时:两个属性:名称和值
    • 编译之后:两个属性:地址和值(在计算机内部,地址就取代了变量的名称属性)
    • 普通变量把值作为基本量,把地址作为通过&运算符获得的派生量
    • 指针变量把地址作为基本量,把值作为通过*运算符获得的派生量
  • 指针与数组
    • 指针表示法数组表示法是两种等效的方法---编译器编译后生成的机器代码是一样的
      • 对于表达式ar[i]和*(ar+i),无论ar是指针变量还是数组名上述的表达式都是正确的
      • 但是只有当ar是指针变量时,才能有ar++
    • 函数原型中有数组---那么通过这个数组参数传进函数的是什么?
      • 函数参数表中的数组实际上是指针,指针指向的是数组的地址
        • sizeof(a)==sizeof(int*);//在函数内部的情况
        • 但是可以用数组的运算符[ ]进行运算
        • 函数原型中以下情况是等价的(表达的意思一样)
          • int sum(int *ar,int n);<--等价-->int sum (int ar[ ],int n)
      • 所以在函数内部进行操作的数组,就是主调函数的那个数组
    • 数组变量是特殊的指针(本身就是一种const指针(常量指针),指向的是数组的一个单元的地址,这个常量指针不可以再被赋值)
      • 可以认为ar[n]等价于*(ar + n)的意思是“到内存的ar位置, 然后移动n个单

元, 检索储存在那里的值”

  • 数组名是该数组首元素的地址(下同)
  • 数组变量本身表达的就是地址
    • int a[10];其中数组变量a就表达了地址的含义也即int *p=a;(无需&取地址)
    • 但是数组的单元表达的是单个变量,需要用&取地址---> a==&a[0]
    • [ ]运算符可以对数组做,也可以对指针做
      • 对数组做[ ]--->a[0]
      • 对指针做[ ]--->p[0]
        • int min=2;//min 是一个普通变量
        • int *p=min;//p是一个指针,指向min
        • p[0]=2;  //对指针用数组运算符,表达的意思是将p所指的地址代表的变量看做只有一个元素的数组,所以p[0]就是2
    • *运算符可以对指针做,也可以对数组做

int a[] = {1, 2, 3, 4, 5, 66, 78, 44, 3, 22, 45, 65, 33, 21, 456};

int b = *a; //此时b等于1,表示的是数组a的地址处的变量的值,也就是数组的第一个元素

  • 关于左值
    • 左值之所以叫左值是因为出现在赋值号左边的是值,而不是变量,他是表达式计算的结果
      • a[0]=2;    //a[0]表示的是一个值,而不是变量
      • int i =10;// i 表示的是一个变量
  •  指针与const
    • 如果const在*的前面(指向const的指针)
      • const int *p=&i  //这表示指针p所指的变量i是const。表示i不可以通过指针进行修改
        • *p=26(错误)
      • 但i是可以单独进行改变的,指针p也可以改变再指向其他的变量
        • i=26;    p=&j;
    • 如果const在*的后面(只能指向一个变量的指针)
      • int * const p=&i  //表示指针p是一个常量,也就是说p只能指向i不能再指向其他的变量
        • *p=26;(正确)
        • p++;(错误)
    • 综合以上两种指针---可以存在一种由两个const定义的指针
      • const int * const ptr=a;// 表示指针ptr只可以指向变量a,并且不能够通过指针ptr改变a的值
    • 关于指针赋值和const需要注意的两大规则
      • 指向const的指针既可以指向const数据的地址,又可以指向非const数据的地址
        • 此指针不会改变数据
      • 普通指针只能指向非const数据的地址
        • 普通指针可以改变数据
    • 数组变量本身是const的指针(所以数组不可以被另一个数组赋值)
      • const int a[ ]={1,2,3,4,5};
      • 在数组之前再加const表示数组内的每一个单元都是const
      • 所以必须对其进行初始化,之后便不可修改
      • 这种每个单元都不可改变的数组的作用是什么呢?
        • 由于把数组传入函数时,传进去的其实是地址,所以函数内部有可能会修改数组的值
        • 为了保护数组的值不被破坏,可以将参数设置为const,这样的话,即使数组本身不是const类型的,只要传进函数内部后,还是会被当做const,被保护起来。
          • int  sum(const int a[ ],int length);
  • 指针运算(对指针的运算其实是加的一个相应的类型的距离
    • char a1='A';char *p=a1;(假设p=0x3454)
      • p+1则表示指针指向的那个类型变量a1后的一个地址(p+1=0x3455)
    • int a=4;int *p=a;(假设p-=0x3466)
      • p+1则表示指针指向的类型变量a之后的那个地址(p+1=0x346A)
    • 若*p=a[0],则*(p+1)=a[1],所以一般指针的这种运算是指向一片连续分配的空间,例如数组这种结构,其中的变量一个接一个,否则是没有意义的。
    • 同样的对指针的运算还有
      • 相减(p1-p2):结果不是地址之差,而是中间差了多少个元素。{地址之差/sizeof(类型)}
      • 指针加、减一个整数a:结果是指针指向下a个类型的元素,或者之前a个类型的元素
      • 递增、递减:结果是指针指向的下一个或者上一个类型的元素,而不是单纯的地址加一或减一
    • 获取指针的地址(&指针)
      • int b=5;
      • int *p=b;
      • printf("指针的地址是:%p",&p);
    • *p++
      • 取出p指针所指的那个数据后,顺便将p指向下一个位置去
      • *的优先级没有++高
      • 常用于数组类的连续空间操作
      • 在某些CPU上,可以直接翻译为一条汇编指令
    • 千万不可以解引用未初始化的指针
      • int *p;//计算机分配了一个存储指针变量的地址
      • *p=5;(错误)//因为如果没有对指针进行初始化,那么计算机就没有给指针分配一个明确的地址去存储数据,此时指针所指的地址是一个随机值,如果硬是往里面写数据,有可能会造成程序崩溃。
    • 指针比较
      • <,<=,==,>,>=,!=都可以对指针做运算
      • 比较的是他们在内存中的地址
      • 数组中的单元的地址是线性递增的
    • 0地址
      • 0地址是一个特殊的地址,一般是不能随便碰的
      • 一般可以用NULL来表示0地址
  • 指针的类型
    • 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
    • 但是指向不同类型的指针是不可以相互赋值的,为了避免指针的错用
  • 指针的类型转换
    • void* 表示不知道指向什么类型数据的指针
      • 如果计算则与char* 的计算规则相同
    • 转换指针的类型
      • 将int*---->void*
        • int *p=&i;

           void*q=(void*)p

        • 这种运算并没有改变p所指的变量的类型,而是让后人用不同的眼光去看待p所指向的变量
        • 也就是我不把它看做int,而是看做void。
  • 指针可以用来做什么?
    • 需要向函数内部传入很大的数据(例如数组)时,可以用指针作为参数
    • 传入数组后对数组进行操作
    • 函数需要返回不只一个结果(例如swap函数
    • 动态申请内存
  • 动态内存分配
    • 通俗一点就是向计算机要内存空间,是在程序运行时,再向计算机要内存
    • 一般是用malloc函数
      • #include<stdlib.h>
      • void* malloc(size_t  size);
      • 利用malloc申请到的内存空间是以字节为单位的,返回的结果是void *,一般需要类型转换为自己需要的类型号

int *a;

a=(int*)malloc(n*sizeof(int)); 

//本语句的意思就是通过malloc函数向计算机申请一块内存空间,这个空间的大小是n*sizeof(int),

返回的是一个void*类型的指针,也就是一个指向未知类型数据的地址,之后再把这个指针转化为我们

需要的类型---int *。

  • 与申请内存空间相对应的就是释放内存空间free()
    • free(a);
    • 只能还申请来的空间的首地址
  • 如果申请失败了就会选择返回0或者是NULL
  • 静态内存分配指的是在编译时确定数组等数据类型的大小,然后由计算机分配好
  • 转换
    • 总是可以把一个非const的值转化为const的

void f(const int * x); //形参是一个指针,并且不能通过这个指针改变所指变量的值

int a =12;

f(&a);

  • 当要传递的参数的类型比地址还要大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外边的变量修改
  • %*s、%2d、%d
#include<stdio.h>

int main()

{

    int a,b;

    scanf("%2d%*2s%d",&a,&b); //%*2s表示忽略两个字符;%2d表示两位10进制数

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

    return 0;

}
  • 指向多维数组的指针
    • int(*pz)[2];//该语句定义了一个指针pz,表示指向一个内含两个int类型值的数组
    • {{1,2},{3,4},{5,6},{7,8}}
    • 因为[ ]的优先级高于*,所以下面的语句含义不同
      • int * par[2];//该语句表示定义了一个数组par,数组含有两个指向int型的指针元素
  • 指针的兼容性
    • 两个指针指向的类型不一样则不可以相互赋值
    • 两个指针指向的维度不一样则不可以相互赋值
      • 主要指一维数组名和二维数组名
  • 函数与多维数组
    • 有函数原型
      • void somefunction(int(*pt)[4]);//表明pt是一个指针,指向内含4个int元素的数组的指针
      • 等价于 void somefunction(int pt[ ][4]);
    • int sum2(int ar[ ][ ],int rows);(错误的声明格式)
      • 因为这样声明后,编译器将不知道,指针ar指向的数组元素的大小是什么,因为不知道数组元素内有几个元素
      • int sum2(int ar[ ][4],int rows);正确的声明格式
    • 其中黄色部分是可省略的
    • 同样的,对于一个指向N维数组的指针,只有最左边的方括号的内容是可以省略的
      • 因为最左边的方括号只用于表明这是一个指针,其余的方括号用于描述指针指向的数据类型
      • int sum4d(int ar[][10][20][30],int rows);
        • 表示ar指向一个10x20x30的int数组
  • 变长数组(VLA)
    • 通常声明一个数组:
      • type name [size];
      • 传统的C数组,要求size是整型常量表达式
    • 在C11/C99标准中,出现了一种新标准---就是变长数组
      • size是整型非常量表达式

int rows=3;

int cols=4;

double  arry[rows][cols];//定义了一个大小可变的二维数组

  • 在函数原型中,VLA的存在形式如下
    • int sum2d(int rows,int cols,double arry[rows][cols]);
    • 上述定义了一个可变二维数组---注意形参的声明顺序不可改变
  • 另外:变长数组在定义之后,就不可变了;这里的可变主要是说在创建数组时,可以使用变量指定数组的维度
  • 复合字面量
    • 字面量是除符号常量以外的常量
      • 例如:5、2.1、'Y'
    • 通常是使用复合字面量来表示数组类型的字面量
      • 例如:int arry[2]={0,1}<====>(int [2]){0,1}
      • int arry2[2][3]={ {1,2,3},{2,3,4} }<====>(int [2][3]){{1,2,3},{2,3,4}}
    • 复合字面量是匿名的,所以创建时必须直接使用
      • int *pt1;
      • pt1 = (int [2]) {10, 20};
    • 复合字面量的类型名也代表了首元素的地址,所以可以把它指针
    • 复合字面量可以作为实际参数,传入相应的函数,因此就不需要再额外创建数组
      • 例如:total3 = sum((int []){4,4,4,5,5,5}, 6);
    • 复合字面量提供的是临时需要的值
      • 作用于是所在的代码块

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖啡与乌龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值