- 定义数组
- <类型>变量名称[元素数量]
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是一个指针,且*pi是int类型,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 *p=&i //这表示指针p所指的变量i是const。表示i不可以通过指针进行修改
- 如果const在*的后面(只能指向一个变量的指针)
- int * const p=&i //表示指针p是一个常量,也就是说p只能指向i不能再指向其他的变量
- *p=26;(正确)
- p++;(错误)
- int * const p=&i //表示指针p是一个常量,也就是说p只能指向i不能再指向其他的变量
- 综合以上两种指针---可以存在一种由两个const定义的指针
- const int * const ptr=a;// 表示指针ptr只可以指向变量a,并且不能够通过指针ptr改变a的值
- 关于指针赋值和const需要注意的两大规则
- 1·指向const的指针既可以指向const数据的地址,又可以指向非const数据的地址
- 此指针不会改变数据
- 2·普通指针只能指向非const数据的地址
- 普通指针可以改变数据
- 1·指向const的指针既可以指向const数据的地址,又可以指向非const数据的地址
- 数组变量本身是const的指针(所以数组不可以被另一个数组赋值)
- const int a[ ]={1,2,3,4,5};
- 在数组之前再加const表示数组内的每一个单元都是const
- 所以必须对其进行初始化,之后便不可修改
- 这种每个单元都不可改变的数组的作用是什么呢?
- 由于把数组传入函数时,传进去的其实是地址,所以函数内部有可能会修改数组的值
- 为了保护数组的值不被破坏,可以将参数设置为const,这样的话,即使数组本身不是const类型的,只要传进函数内部后,还是会被当做const,被保护起来。
- int sum(const int a[ ],int length);
- 如果const在*的前面(指向const的指针)
- 指针运算(对指针的运算其实是加的一个相应的类型的距离)
- 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地址
- char a1='A';char *p=a1;(假设p=0x3454)
- 指针的类型
- 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
- 但是指向不同类型的指针是不可以相互赋值的,为了避免指针的错用
- 指针的类型转换
- void* 表示不知道指向什么类型数据的指针
- 如果计算则与char* 的计算规则相同
- 转换指针的类型
- 将int*---->void*
-
int *p=&i;
void*q=(void*)p
- 这种运算并没有改变p所指的变量的类型,而是让后人用不同的眼光去看待p所指向的变量
- 也就是我不把它看做int,而是看做void。
-
- 将int*---->void*
- 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);
- 复合字面量提供的是临时需要的值
- 作用于是所在的代码块
- 字面量是除符号常量以外的常量