目录
1.数组名的理解
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));//计算整个数组的大小
return 0;
}
(1)sizeof内部单独放一个数组名的时候,数组名表示为整个数组,计算整个数组的大小,单位为字节;
&数组名:数组名表示整个数组,取出来为整个数组的地址;
除此之外遇到的所有数组名均为首元素地址(方便找到起始位置);
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0] + 1);//+4
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr + 1);//+4
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr + 1);//+40
return 0;
}
//数组在指针中是连续存放的
//arr和&arr[0]都是首元素的地址,+1就是跳过1个字节
//&arr是整个数组的地址,+1就是跳过整个数组
2.使用指针访问数组
(1)arr[i]=*(arr+i)
(2)*(i+arr)=i[arr]=*(i+arr)=arr[i]
(3)p[i]=*(p+i)
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
//输⼊
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//输⼊
int* p = arr;
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);//p本身就是地址,无需进行取地址
//scanf("%d", arr+i);//等价
}
//输出
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));//对地址进行解引用,找到所在空间
//等价于printf("%d ", *(arr + i));
//也等价于printf("%d ",p[i]);
}
return 0;
}
3.一维数组传参的本质
数组传参本质上传递的是数组首元素的地址:
#include <stdio.h>
void test(int arr[])//int arr[]=int *arr
{
int sz2 = sizeof(arr) / sizeof(arr[0]);
printf("sz2 = %d\n", sz2);
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//数组传参时传递的并非数组,而是数组首元素的地址
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
test(arr);//此处数组名为数组首元素地址
//Print(arr,sz)--sz:将元素个数以参数形式传入,“降级”
return 0;
}
总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
4.冒泡排序
#include<stdio.h>
//写一个函数,对一个整型数组的数据进行排序
//冒泡排序
//排序核心:两两相邻的元素进行排序
void BubbleSort(int arr[], int sz)
{
//趟数(例如10个元素只需排序9次,故排序次数为sz-1)
int i = 0;
for (i = 0; i < sz-1; i++)
{
int flag = 1;//假设已经有序
//一趟冒泡排序的过程
int j = 0;
for (j = 0; j < sz-1-i; j++)
{
//一对元素的比较
if (arr[j] > arr[j + 1])
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = 0;//证明此时确实是无序的,需要进行冒泡排序
}
}
if (flag == 1)//已经有序,不需要进行冒泡排序
{
break;
}
}
}
void PrintArr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1 };
//将数组进行排序,排为升序
//冒泡排序:相邻的两个元素进行比较,如果不满足顺序就交换
int sz = sizeof(arr) / sizeof(arr[0]);
BubbleSort(arr, sz);
PrintArr(arr,sz);
return 0;
}
练习:调整数组使奇数全部都位于偶数前面:
//调整数组使奇数全部都位于偶数前面。
//题目:
//输入一个整数数组,实现一个函数,
//来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分,
//所有偶数位于数组的后半部分。
void MoveNum(int arr[],int sz)
{
int* left = arr;
int* right = arr + sz - 1;
while (left < right)//保证数组不越界
{
while (*(left) % 2 != 0)//奇数
{
left++;
}
while (*(right) % 2 == 0)//偶数
{
right--;
}
if (left < right)
{
int tmp = *left;
*left = *right;
*right = tmp;
}
}
}
int main()
{
int i = 0;
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
MoveNum(arr, sz);
for (i = 0; i < sz; i++)
{
printf("%d\n", arr[i]);//遍历数组
}
return 0;
}
5.二级指针
二级指针用于存放指针变量的地址。
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
int a = 10;
int* p = &a;
int** pp = &p;//先对pp解引用找到p,再对p解引用找到a
//pp是二级指针变量,二级指针变量用来存放一级指针变量地址
6.指针数组
(1)整型数组是存放整形的数组,数组中每个元素都为整型类型;
(2)字符数组是存放字符的数组,数组中每个元素都为字符类型;
(3)指针数组是存放指针的数组,数组中每个元素都为指针类型;
(4)指针数组模拟为二维数组
#include <stdio.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
int* parr[3] = { arr1, arr2, arr3 };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)//拿到数组
{
for (j = 0; j < 5; j++)//拿到数组中的元素
{
printf("%d ", parr[i][j]);
//parr[i][j]=*(arri+j)=arri[j]
//arr[i]=*(arr+i)
//arr[i][j]=*(*(arr+i)+j)
}
printf("\n");
}
return 0;
}
上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。
7.字符指针变量
int main()
{
char ch = 'w';
char* pc = &ch;//pc就是字符指针
*pc = 'w';
return 0;
}
#include<stdio.h>
int main()
{
const char* pstr = "abcdef";//加上const以后只要修改值就报错
//不是把字符串\0存放在p中,而是把第一个字符的地址存放在p中
//1.可以将字符串想象为一个数组,但值不可修改
//2.当常量字符串出现在表达式中时,它的值就是首字符地址
printf("%s\n", pstr);
return 0;
}
练习:
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");//yes
if (str3 == str4)
printf("str3 and str4 are same\n");//yes
else
printf("str3 and str4 are not same\n");
return 0;
}
当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
8.数组指针变量
整型指针--指向整型的指针;
字符指针--指向字符的指针;
数组指针--指向数组的指针;
整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
数组指针变量:存放的应该是数组的地址,能够指向数组的指针变量。
int *p1[10];
//p1是数组,数组10个元素,每个元素的类型为int*,故p1为指针数组;
int (*p2)[10];
//p2是指针,指针指向的是数组,数组有10个元素,每个元素类型为int
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int (*parr)[10]=&arr;
//存放的为arr的地址
注:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合。
int(*p)[10] = &arr;
| | |
| | |
| | p指向数组的元素个数
| p是数组指针变量名
p指向的数组的元素类型
9.二维数组传参的本质
#include <stdio.h>
void test(int a[3][5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", a[i][j]);
printf("%d ",*(*(p+i)+j));
//*(p+i)=arr[i]
//*(*(p+i)+j)=arr[i][j]
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7} };
test(arr, 3, 5);
return 0;
}
(1)二维数组传参,形参可以是数组,也可以为指针:
1.写成数组更加直观,方便理解;
2.写成指针是因为数组传参,传递的为数组首元素地址;
(2)二维数组其实是元素为一维数组的数组。二维数组的首元素其实为第一行,首元素的地址为第一行的地址。
Print(arr,3,5);
void Print(int arr[3][5],int r,int c);
10.函数指针变量
(1)函数指针变量用于存放函数地址:
数组名:数组首元素地址;
&数组名:整个数组的地址;
函数名:函数的地址;
&函数名:函数的地址;
#include<stdio.h>
char* test(int a, char c)//test函数的返回类型为char*
{
return NULL;
}
int main()
{
char* (*pt)(int, char) = test;
return 0;
}
int (*pf) (int x, int y)
| | |
| | |
| | pf指向函数的参数类型和个数的交代
| 函数指针变量名
pf指向函数的返回类型
int (*) (int x, int y) //pf函数指针变量的类型
对于函数指针而言:
int ret = (*pf)(a, b)
//等价于int ret = (pf)(a, b)
(2)练习:
此代码为一次函数调用:
(1)把0这个整型值,强制类型转换为一个函数地址,此函数无参数,返回类型为void;
(2)调用0地址处的函数;
此代码为函数声明:
(1)signal为一个函数;
(2)signal函数参数有两个:int整型类型和void(*)(int)函数指针类型;该指针类型指向的函数参数为int,返回类型为void;
(3)signal函数返回类型为这种类型的void(*)(int)类型指针;
(4)去掉signal函数后剩下的主体部分为void(*)(int)函数指针类型;该指针指向函数参数为int,返回类型为void;
(3)typedef关键字
//数组指针类型重命名
//将int(*)[5]重命名为parr_t
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
//函数指针类型重命名
//将void(*)(int)类型重命名为pfun_t
typedef void(*pfun_t)(int);//新的类型名必须在*的右边
//简化练习中的第二个代码:
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
11.函数指针数组
将函数的地址存储到一个数组中,这个数组就叫做函数指针数组,可用于实现回调函数。
int (*parr[3])()
//parr1先和[]结合,说明parr是数组;数组的内容是int (*)()类型的函数指针。
int main()
{
int (*pf)(int, int) = Add;//pf是函数指针
int (*pfArr[])(int, int) = { Add, Sub, Mul, Div};//存放函数指针的数组--函数指针数组
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = pfArr[i](6, 2);//进行一次循环就对应进行一种算术
printf("%d\n", ret);
}
return 0;
}
练习:计算器的实现:
//实现一个计算器
//这个计算器能够计算整数的:加法、减法、乘法、除法
void menu()
{
printf("***************************\n");
printf("*******1.add 2.sub ******\n");
printf("*******3.mul 4.div ******\n");
printf("*******0.exit ******\n");
printf("***************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void calc(int (*pf)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
//局限性:如果增加运算,则重复代码会越来越长
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
calc(Add);//完成计算
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
使用指针的形式实现:(转移表)
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
//使用函数指针数组的方式解决一下
//这里的指针数组,我们称为转移表
int(*pfArr[])(int,int) = {NULL,Add,Sub,Mul,Div};
//一一对应 0 1 2 3 4
printf("请选择:>");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("%d\n", ret);
}
else
{
printf("选择错误,重新选择\n");
}
} while (input);
return 0;
}