前言
在C语言中,数组和指针是比较难的一部分,需要我们反复的学习,思考。只有掌握了数组和指针,我们的C语言水平就可以更进一步。
一、数组
1.数组的定义
数组是一组相同类型元素的集合。主要包括数组名称,元素类型以及元素个数。
语法如下:
类型 数组名称[元素个数];//要用常量或常量表达式来指定元素个数
int main()
{
int arr1[10];//表示这个数组中有10个int的数据
int arr2[4 + 6];//表示这个数组中有10个int的数据
return 0;
}
动态的数组定义:
int main()
{
int n = 0;
scanf("%d", &n);
int arr1[n];//表示这个数组中有n个int的数据
return 0;
}
动态的数组只能在支持C99标准的编译器上编译,且动态数组不可以进行初始化。
数组也有局部数组和全局数组,静态和非静态数组:
int arr[10];//全局数组
static char charr[10];//全局静态数组
int main()
{
double doarr[10];//局部数组
static short sharr[10];//局部静态数组
}
2.一维数组
数组的简单应用:
int main()
{
int arr[10];//定义了一个名为arr的数组,里面包含10个int类型的数据
for (int i = 0; i < 10; i++)
{
arr[i] = i;//为数组中的元素赋值
}
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);//打印数组中的元素
}
}
上述代码中用arr[i]时的[ ]是下标引用操作。数组的下标都是从0开始的。
定义了一个数组,我们可以用sizeof来求取数组所占的空间大小:
int main()
{
int arr[10];//定义了一个名为arr的数组,里面包含10个int类型的数据
char charr[10];//定义了一个名为arr的数组,里面包含10个char类型的数据
printf("arr=%d\ncharr=%d", sizeof(arr), sizeof(charr));
return 0;
}
由值我们可以看出szieof求数组的空间为 元素个数 * sizeof(数组类型)。
求数组中元素个数的方法如下:
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);//用数组总大小除数组一个元素的大小
printf("%d", sz);
return 0;
}
2.1一维数组存储
int main()
{
int arr[10] = { 0 };//把数组中所有元素初始化为0
char charr[10] = { 0 };//把数组中所有元素初始化为0
printf("arr中每个元素的地址如下\n");
for (int i = 0; i < 10; i++)
{
printf("%p\n", &arr[i]);//打印数组中每个元素的地址
}
printf("\n");
printf("charr中每个元素的地址如下\n");
for (int i = 0; i < 10; i++)
{
printf("%p\n", &charr[i]);//打印数组中每个元素的地址
}
return 0;
}
从结果可以看出在int型的数组中,每个元素的位置相差一个int的位置,在char型的数组中,每个元素的位置相差一个char的位置,我们由此可以看出数组的地址是连续存储的,且每个元素的地址相差这个数组类型的距离。
2.2一维数组初始化
void print(int arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[5] = { 0 };
int arr1[5] = { 1 };
int arr2[5] = { 1,2,3 };
int arr3[] = { 1,2,3 };//元素数量可以省略,但要进行初始化,系统会自动推断元素个数。
print(arr, sizeof(arr) / sizeof(arr[0]));
print(arr1, sizeof(arr1) / sizeof(arr1[0]));
print(arr2, sizeof(arr2) / sizeof(arr2[0]));
print(arr3, sizeof(arr3) / sizeof(arr3[0]));
return 0;
}
字符数组如下:
void print(char arr[], int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%c ", arr[i]);
}
printf("\n");
}
int main()
{
char charr1[5] = { 'a','b','c' };
char charr2[5] = "abc";
char charr3[] = { 'a','b','c' };
char charr4[] = "abc";
print(charr1, sizeof(charr1) / sizeof(charr1[0]));
print(charr2, sizeof(charr2) / sizeof(charr2[0]));
print(charr3, sizeof(charr3) / sizeof(charr3[0]));
print(charr4, sizeof(charr4) / sizeof(charr4[0]));
return 0;
}
在字符串数组中,可以用花括号来初始化,也可以用双引号来初始化。对比charr3和charr4的存放结果,我们可以看出用双引号会被自动的放入一个’\0’。
int main()
{
char charr1[5] = { 'a','b','c' };
char charr2[5] = "abc";
char charr3[] = { 'a','b','c' };
char charr4[] = "abc";
printf("charr1 = %s\n", charr1);
printf("charr2 = %s\n", charr2);
printf("charr3 = %s\n", charr3);
printf("charr4 = %s\n", charr4);
return 0;
}
我们可以看到charr打印出了未知的结果,这是因为发生了数组越界,打印数组时会打印到/0的位置,而上面我们有打印限制,所以未出现未知的结果。
3.二维数组
在C语言中多维数组就是元素为数组的数组,二维数组的每个元素是一维数组。多维数组声明时,每个维度用一个方括号表示。
int arr[10][10];//二维数组
char charr[10][10][10]//三维数组
在二维数组中,第一个表示行,第二个表示列。在上面的二维数组中,arr数组有10,每行有10个元素,因此该数组中有10*10共100个元素。在多维数组的声明中,可以省略第一维度的长度,但其他维度都必须指定长度。
3.1二维数组初始化
void print(int arr[][3])//打印数组中每个元素
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr1[3][3] = { {1,2,3},{4,5,6},{7,8,9} };//按照行进行存储
int arr2[3][3] = { 1,2,3,4,5,6,7,8,9 };//arr1的省略形式
int arr3[3][3] = { {1,0,0},{2,0,0},{3,0,0} };//没有初始化的元素,就默认初始化为0。
int arr4[3][3] = { {1},{2},{3} };//arr3的省略形式
printf("arr1打印如下:\n");
print(arr1);
printf("arr2打印如下:\n");
print(arr2);
printf("arr3打印如下:\n");
print(arr3);
printf("arr4打印如下:\n");
print(arr4);
}
3.2二维数组存储
int main()
{
int arr1[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
printf("数组中每个元素的地址\n");
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%p\n", &arr1[i][j]);
}
}
printf("数组中每行元素的地址\n");
for (int i = 0; i < 3; i++)
{
printf("%p\n", &arr1[i][0]);
}
return 0;
}
由此可以看出二维数组的储存方式,二维数组的存储时连续的,在地址可以看成一个一维数组。这也是为什么可以省略第一维度的长度,但其他维度都必须指定长度。其他维度决定间隔的地址。
3.数组名
int main()
{
int arr[5] = { 1,2,3,4,5 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%d\n", *arr);
return 0;
}
可以看出数组名是数组首元素的地址。
注意:sizeof(数组名),这里表示整个数组,是计算整个数组的大小,单位是字节。&数组名则表示整个数组,取出整个数组的地址。
int main()
{
int arr[5] = { 1,2,3,4,5 };
printf("%d\n", sizeof(arr));
printf("%p\n", &arr);
printf("%p\n", &arr[0]);
return 0;
}
整个数组的地址也是数组首元素地址。
二、初识指针
1.指针与地址
C语言和其他编程语言相比,C语言可以直接操作硬件,而操作硬件就要依靠C语言的指针。
指针的含义:1.指向的地址。2.指针有类型,类型表示从首地址开始取多少个字节。
//类型 * 指针变量
int main()
{
int* p;//指向整形变量的指针变量p
char* ch;//指向字符变量的指针变量ch
return 0;
}
在C语言中提供了两个特别的运算符:
&:取地址运算符。
*:取值运算符(解引用运算符)。
1.1指针的赋值
int main()
{
int* p;//未初始化
int* p1 = NULL;//把p1初始化为空指针
int a = 1;
int* p2 = &a;//取a的地址,使p2指向a
printf("%p\n", &a);// 打印a的地址
printf("%p\n", &p2);//打印p2的地址
printf("%p\n", p2);//打印p2存放的地址
printf("%d\n", *p2);//打印p2解引用的值
return 0;
}
我们可以看到指针存放的是地址,是指针所绑定的地址。指针有自己的地址。我们可以通过解引用获得指针所绑定的值。
1.2指针的偏移
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p = &arr;//p指向数组的第一个元素
printf("%d\n", *p);//打印p解引用的值
printf("%p\n", p);//打印p存放的地址
(*p)++;//p解引用的值递增加1
printf("%d\n", *p);//打印p解引用的值
printf("%p\n", p);
p++;//p指向的地址偏移一个int型地址
printf("%d\n", *p);//打印p解引用的值
printf("%p\n", p);
return 0;
}
当指针指向数组时,对指针进行相加相减或者递增递减操作,会使指针指向操作后的地址。
1.3指针和数组的区别
1.赋值:同类型的指针可以相互赋值,但数组不可以,只能一个元素一个元素的赋值或着拷贝。
2.sizeof大小不同:sizeof(数组名)求的是数组的大小,sizeof(指针名)求的是指针的大小,在32位机器下,所有指针的大小都为4。在64位机器下,所有指针的大小都为8。
3.传参:数组传参时,会退化为指针。因为C语言只会以拷贝的方式传递参数,在参数传递时,拷贝整个数组会使效率大大降低,并且参数位于栈上,太大的数组拷贝会导致栈溢出。用数组传参时可以把数组名看作一个常量指针,传递的是首元素的地址。
4.数组名可以看作指针常量。
总结
我们用一个经典的冒泡排序来总结今天的知识。
冒泡排序的思想:
1,从待排序的数组首元素开始,比较相邻两个元素得大小,让大的元素向后移动。直到把最大的元素移动到最后的位置。
2,第二轮把次大的数移动到倒数第二个位置。
3,重复上述过程。
void print(int* arr, int size)
{
for (int i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
}
void swap(int* p1, int* p2)//交换元素
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void Bubbling(int *arr,int size)//数组退化为指针,可以直接用指针接受。
{
for (int i = 0; i < size; i++)
{
for (int j = 1; j < size - i; j++)//控制循环时最后的位置,注意不要越界
{
if (arr[j - 1] > arr[j])//找到大的数,如果大的数在前,则执行下面交换操作。
{
swap(&arr[j - 1], &arr[j]);//进行交换
}
}
}
}
int main()
{
int arr[10] = { 9,5,1,7,3,6,8,4,2,0 };
int size = sizeof(arr) / sizeof(arr[0]);
Bubbling(arr, size);
print(arr, size);
return 0;
}