数组相关
1.数组概念
数组:相同类型变量的有序集合
从内存角度,是一片连续的内存空间
数组初始化:
- 在编译时明确指定全部元素为0
int a[10] = {0};
- 在程序运行时,重置内存块为0
memset(a,0,sizeof(a));
- 数组元素个数在初始化的时候可以明确指出也可以根据初始化列表元素个数确定
2.数组类型
数据类型:固定大小内存块的别名
指针类型:依赖于指针所指向的内存空间的大小
- C语言中的数组有自己特定的类型:由元素类型和数组长度决定
例:int array[5]的类型为int [5]
- 可以重命名数组类型,并用新的数组类型名命名数组变量,使用
typedef
关键字进行重命名
typedef int (MYINT5)[5];//MYINT5表示一个含有5个元素的int类型的数组
MYINT5 array;//相当于int array[5];
数组名的技术盲点:
- 数组首元素的地址和数组地址是两个不同的概念
- 数组名代表数组首元素的地址,它是个常量
变量本质是内存空间的别名,一定义数组,就分配内存,内存就固定了。所以数组名起名以后就不能被修改了。
- 数组首元素的地址和数组的地址值相等,但只是值相等而已
C语言规定:
int a[10]; printf("得到整个数组的地址a: %d \n", &a); printf("数组的首元素的地址a: %d \n", a);
3.数组指针类型
- 根据数组类型定义数组指针变量
用类型定义变量是天经地义的事情
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ARRAY_SIZE 5
int main(void)
{
int i = 0;
typedef int (myArray)[ARRAY_SIZE];//重命名一个数组类型myArray,表示int [5]
myArray array;//相当于int array[5];
myArray * p_array = NULL;//使用数组类型定义一个(myArray数组类型的)指针,用于指向一个myArray的变量
p_array = &array;//将指针变量指向数组类型的变量array,此时指针的步长是整个数组长度
for(i= 0;i<ARRAY_SIZE;i++){
(*p_array)[i] = i+1;//通过指针操作数组内存空间
}
for(i= 0;i<ARRAY_SIZE;i++){
printf("%d\n",(*p_array)[i]);
}
printf("Hello World!\n");
return 0;
}
- 根据数组指针类型定义数组指针变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ARRAY_SIZE 5
int main(void)
{
int i = 0;
typedef int (*myArray)[ARRAY_SIZE];//重命名一个数组指针类型myArray,表示int (*)[5]
myArray p_array=NULL;//使用数组指针类型定义一个指针,相当于int (*array)[5];
//告诉编译器 给我分配一个指针变量
int array[ARRAY_SIZE]={0};
p_array = &array;//将指针变量指向数组类型的变量array,此时指针的步长是整个数组长度
for(i= 0;i<ARRAY_SIZE;i++){
(*p_array)[i] = 2*i+1;//通过指针操作数组内存空间
}
for(i= 0;i<ARRAY_SIZE;i++){
printf("%d\n",(*p_array)[i]);
}
printf("Hello World!\n");
return 0;
}
- 直接定义数组指针变量
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ARRAY_SIZE 5
int main(void)
{
int i = 0;
int (*p_array)[ARRAY_SIZE] = NULL;//告诉编译器 给我分配一个指针变量
//直接定义一个数组指针,相当于int (*p_myArray)[5];
int array[ARRAY_SIZE]={0};
p_array = &array;//将指针变量指向数组类型的变量array,此时指针的步长是整个数组长度
for(i= 0;i<ARRAY_SIZE;i++){
(*p_array)[i] = 2*i+2;//通过指针操作数组内存空间
}
for(i= 0;i<ARRAY_SIZE;i++){
printf("%d\n",(*p_array)[i]);
}
printf("Hello World!\n");
return 0;
}
4.多维数组
4.1本质推演
多维数组名就是一个数组指针变量,指向除了最高维以外的剩余维数的数组
多维数组名是一个多级指针,取
*
以后减少一级指针。同样的对于二级指针,取*
以后变成一级指针,二级指针指向一块内存,常是一个数组,一级指针指向一个数组里面的元素。
n级指针的值和n-1级指针的值(就是地址相等)重合,拿二维数组来说,就是某一行整个行的地址(一个数组地址)和该行首元素的地址在相等,这是必然的,他们都是该片内存的起始位置,必然相等。但是他们的数据类型不一样。n级指针指向的内存块更多,n-1级指针指向的只是n级指针指向的内存块的一部分,常是其中的一个元素。
多维数组char a[i][j] ==> ((a+i)+j)转换技巧分析
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main()
{
int a[3][5];
int c[5]; //&c + 1;
int b[10]; //b代表数组首元素的地址 &b代表这个数组的地址 &b+1相当于 指针后移4*10个单位
//a代表什么什么那?a是一个数组指针 指向低维数组的指针
//a +1;
printf("a:%d, a+1:%d \n", a, a + 1); //4*5
{
int i = 0, j = 0, tmp = 0;
for (i = 0; i<3; i++)
{
for (j = 0; j<5; j++)
{
a[i][j] = ++tmp;
}
}
printf("\n");
for (i = 0; i<3; i++)
{
for (j = 0; j<5; j++)
{
printf("%d \n", a[i][j]);
}
}
}
//a的本质是一个数组指针,每次往后跳一维的维数
{
int i = 0, j = 0;
//定义了一个数组指针 变量
int(*myArrayPoint)[5]; //告诉编译给我开辟四个字节内存
myArrayPoint = a;
printf("\n");
for (i = 0; i<3; i++)
{
for (j = 0; j<5; j++)
{
//myArrayPoint[i][j] = ++tmp;
printf("%d \n", myArrayPoint[i][j]);
}
}
}
printf("a:%d,*a:%d\n",(int)a,(int)(*a));//再次证明二级指针和一级指针的值一样,因为是同一块内存起始地址,
//但是代表的数据类型不一样,二级指针是一大块内存,一级指针只是前者其中的一个元素。
/*
char cbuf[30]; // cbuf(1级指针) 代表数组首元素的地址。。。&cbuf(二级指针) 代表整个数组的地址
char array[10][30]; //array是二级指针
(array+i) //相当于 整个第i行的数组地址 //二级指针 &cbuf
(*(array+i))//一维数组的首地址 cbuf
(*(array+i))+j //相当于第i行第j列的地址了把。。。。&array[i][j]
*((*(array+i))+j) //相当于第i行第j列的值。。。。<====>array[i][j]
*/
system("pause");
}
a[i][j] === a[0+i][j] ==> *(a+i)[j] ===> *(a+i)[0 + j] ==> *( *(a+i) + j)
结论:a是一个指向int [5]的数组指针 a+1 向后跳5*4,跳一行。
4.2参数退化
是一个存在的事实
void printArray01(int a[3][5]) //最不好的代码
{
int i, j, tmp = 0;
for (i=0; i<3; i++)
{
for (j=0; j<5; j++)
{
printf("%d ", a[i][j]);
}
}
}
void printArray02(int a[][5]) //二维数组是一个指向一维数组的指针。所以函数参数不用关心有多少行,只需要给出每一行有多少列确定指针步长即可
{
int i, j, tmp = 0;
for (i=0; i<3; i++)
{
for (j=0; j<5; j++)
{
printf("%d ", a[i][j]);
}
}
}
void printArray03( int (*b)[5]) //直接定义一个指向int [5]的数组指针
{
int i, j, tmp = 0;
for (i=0; i<3; i++)
{
for (j=0; j<5; j++)
{
printf("%d ", b[i][j]);
}
}
}
4.3作函数参数
- 1.C语言中只会以机械式的值拷贝的方式传递参数(实参把值传给形参)
int fun(char a[20], size_t b)
{
printf("%d\t%d",b,sizeof(a));
}
原因1:高效
原因2:
C语言处理形参a[n]的时候,它没有办法知道n是几,它只知道&a[0]是多少,它的值作为参数传递进去了
虽然c语言可以做到直接int fun(char a[20]),然后函数能得到20个数字,但是,C没有这么做,以简化编译器的实现。
2.二维数组参数同样存在退化的问题
- 二维数组可以看做是一维数组
- 二维数组中的每个元素是一维数组
- 二维数组参数中第一维的参数可以省略
void f(int a[5]) ====》void f(int a[]); ===》 void f(int* a);
void g(int a[3][3])====》 void g(int a[][3]); ====》 void g(int (*a)[3]);
- 3.等价关系
数组参数 | 等效的指针参数 |
---|---|
一维数组 char a[30] | 指针 char* |
指针数组 char *a[30] | 指针的指针 char **a |
二维数组 char a[10][30] | 数组的指针 char(*a)[30] |
4.4证明多维数组内存中是连续存储的
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
void printfArray01(int *array, int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
printf("%d ", array[i]);
}
}
void main()
{
int a[3][5];
int i, j, tmp = 1;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
a[i][j] = tmp++;
}
}
//把二维数组 当成 1维数组 来打印 证明线性存储
printfArray01(*a, 15);
printf("hello...\n");
system("pause");
return;
}
5.指针数组
5.1菜单
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int searchKeyword(char * table[],int count,char *keyword,int *pos)
{
int ret = 0;
int i = 0;
if (table == NULL || keyword == NULL || pos == NULL){
ret = -1;
return ret;
}
for (i = 0; i < count; i++){
if (strcmp(table[i], keyword) == 0){
*pos = i;
return ret;
}
}
if (i == count)
{
*pos = -1;
}
return ret;
}
#define DIM(a) (sizeof(a)/sizeof(*a))
void main()
{
char * src[] = {
"while",
"case",
"static",
"break"
};
int pos;
searchKeyword(src, DIM(src), "case", &pos);
printf("case is :%d\n",pos);
searchKeyword(src, DIM(src), "break", &pos);
printf("break is :%d\n", pos);
system("pause");
return;
}
5.2命令行参数
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//main函数是操作系统调用的函数
//在程序执行的时候可以向main函数传递参数
/*
argc 命令行参数个数
argv 命令行参数数组
env 环境变量数组
int main();
int main(int argc);
int main(int argc, char *argv[])
*/
int main(int argc, char* argv[], char**env)
{
int i = 0;
printf("******************* Begin argv *******************\n");
for (i = 0; i < argc; i++)
{
printf("%s\n", argv[i]);
}
// for(i=0; argv[i]!=NULL; i++)
// {
// printf("%s\n", argv[i]);
// }
printf("******************* End argv *******************\n");
printf("\n");
printf("\n");
printf("\n");
printf("******************* Begin env *******************\n");
for (i = 0; env[i] != NULL; i++) //
{
printf("%s\n", env[i]);
}
printf("******************* End env*******************\n");
getchar();
}
操作系统可调用的main函数格式有如下几种:
int main();
int main(int argc)
int main(int argc, char *argv[])
int main(int argc, char* argv[], char**env)
5.3指针数组的自我结束功能
指针数组最后一个元素用NULL或者’\0’或者0结尾可以表示指针数组结束,实现自我结束功能,而且三者实质一样
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
void main()
{
int inum = 0;
int pos = 0;
int a[10];
int i = 0;
//指针数组 自我结束能力
char* c_keyword[] = {
"while",
"case",
"static",
"do",
'\0'
};
char* c_keyword2[] = {
"while",
"case",
"static",
"do",
0
};
char* c_keyword3[] = {
"while",
"case",
"static",
"do",
NULL
};
for (i = 0; c_keyword[i] != NULL; i++)
{
printf("%s\n", c_keyword[i]);
}
printf("\n....\n");
for (i = 0; c_keyword2[i] != NULL; i++)
{
printf("%s\n", c_keyword2[i]);
}
printf("\n....\n");
for (i = 0; c_keyword3[i] != NULL; i++)
{
printf("%s\n", c_keyword3[i]);
}
system("pause");
}