从内存布局到算法思维:二维数组、VLA、函数基础与二分查找的实战指南
二维数组
在前面的学习中我们认为二维数组是下图所示多行多列这样存储的

但事实上并不是这样存储的,接下来我们验证一下

上图我们可以看出每一行内部每个元素都是相邻的,地址之间相差4个字节,跨行位置处的两个元素之间也是相差四个字节,所以二维数组中的每个元素都是连续存放的.

因此二维数组其实是一维数组的数组,因此第一行的数组名就是a[0],后面以此类推,整个数组名叫a,每一行的数组名为整个数组名+行号
C99(99年给的C语言标准)中的变长数组
在C99标准之前,C语言在创建数组的时候,数组大小的指定只能使用常量,常量表达式或者如果我们初始化数据可以直接省略数组大小
int a[10];
int b[]={1,2,3,4};
int c[1+5};
这样写可能会导致我们创建数组不够灵活,又时候创建的大了或者小了
C99中给出一个变长数组(VLA)的新特性,允许我们使用变量指定数组大小
int n=a+b;
int i[n];
这里有一个需要注意的点就是vs2022默认使用的msvc这个编译器,它不支持C99中的变长数组,但是gcc和苹果的clang支持,vs2022中可以使用clang
首先我们在电脑上找到visual studio installer



安装成功后进入vs2022然后进入项目右击属性,按照上图步骤修改即可使用clang

上面示例中数组a就是变长数组,因为它的长度取决于n的值,编译器无法事先确定只能运行时才知道n为多少
变长数组的本质特征就是数组长度只有运行时才知道,所以变长数组无法初始化,他的好处是程序员不用在开发时随意为数组指定一个估计长度,承宣布可以在运行时给数组分配精确的长度,有一个需要注意的就是,变长数组不是说数组的大小是可以变的,数组的大小一旦确定就不可以再变化了

我们在代码运行的时候输入我们所需要的长的即可
数组小练习
- 要求:多个字符从两端移动向中间汇聚
int main ()
{
char a[]="hello world";
char b[]="***********";
int l=0;
//计算数组中的元素有多少个有两种方法,一种是使用strlen函数它不会将/0算入,strlen(数组名)-1,第二种是使用sizeof(数组名)/sizeof(数组首元素)-2,因为其会计入/0因此要减2
int r=strlen(a)-1;
while(l<=r)
{
b[l]=a[l];
b[r]=a[r];
printf("%s\n",b);
sleep(1000);//睡眠函数1000毫秒需要包含头文件stdlib.h
system("cls");//清理屏幕信息,需要包含头文件windows.h
l++;
r++;
}
printf("%s\n",b);
return 0;
}
- 二分查找
给定一个升序的整型数组,在这个数组中查找到指定的值n,找到了就打印n的下标,找不到就打印找不到
int main ()
{
int a[]={1,2,3,4,5,6};
int n=3;//在数组中找到n的值并打印
//最简单的方式就是一个个去比较
int i=0;
int u=sizeof(a)/sizeof(a[0]);
for(i=0;i<u;i++)
{
if(n==a[i])
{ printf("找到了");
break;
}
}//无论找到还是没找到最终都会回到这里因此我们需要再判断一下到底是找到了还是没找到
if(i==u)
pritnf("没找到");
return 0;
}
上面是比较暴力的方法就是一个个去对比然后得出最后的结果,在这个基础上我们其实可以优化一下代码,使用二分查找的方法去寻找这样效率会大大提高
二分查找的步骤如下
1.确定被查找范围的左右下标L和R
2.求中间元素的下标mid
3.锁定中间元素和n作比较
中间元素a[mid]<n,那么说明我们要找的元素在中间原色的右边那么此时我们的查找范围就会更新让左下标等于中间元素后面第一个元素的下标L=mid+1
中间元素a[mid]>n,说明我们要找的元素在中间元素的左边此时我们就要更新我们的右下标让其等于中间元素前一个元素的下标即R=mid-1
中间元素a[mid]=n说明找到了
下面就来实践一下
int main ()
{
int a[]={1,2,3,4,5,6};//创建数组
int n=5;//在数组中找到n的值并打印
//这里我们使用二分查找的方法去寻找目标
int r =sizeof(a)/sizeof(a[0])-1;//右下标right
int l =0;//左下标left
int u=0;//标识符
while(l<=r)
{
int mid=(r+l)/2
if (a[mid]>n)
{
r=mid-1;
}
else if(a[mid]<n)
{
l=mid+1;
}
else
{
printf("找到了n的下标为%d",mid);
break;
}
}
if(u==0)
printf("没找到");
return 0;
}
二分查找使用的前提条件是:有序
二分查找的效率很高
函数
函数的概念
数学中我们就学过函数的概念,比如一次函数y=ax+b,其中a,b都是常数,给一个任意的x就可以得到一个y
在C语言中也有**函数(function)**的概念,也叫:子程序.C语言中的函数就是一个完成某项特定的任务的一小段代码,这段代码是由特殊的写法和调用方法的.
C语言程序就是由无数个子程序组合而成的
在C语言中我们一般会见到两种函数:
- 库函数
- 自定义函数
库函数
- 标准库和头文件
C语言标准中规定了C语言的各种语法规则,C语言并不提供函数,C语言的国际标准ANSI C规定了一些常用的函数的标准,被称为标准库,不同的编译器厂商根据ANSI提供的C语言标准就给出了一系列函数的实现,这些函数就被称为库函数
我们之前学到的printf,scant 都是库函数,库函数也是函数,这些函数都是现成的,只需要学会就可以使用,有了库函数,我们的效率就大大提高.
各种编译器的标准库中提供了一系列的库函数这些库函数根据功能的划分,在不同的头文件中进行了声明
库函数相关头文件:https://zh.cppreference.com/c/header
其中有关于数学的,字符串的,日期等,每一个头文件中都包含了相关的函数和类型等信息.
- 库函数的使用方法
库函数的学习和查看工具有很多例如下面这两个:
- C/C++官方链接:https://zh.cppreference.com/c/header
- cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
double sqrt (double x);
//double 是返回值类型表示函数计算的结果是double类型的值
//sqrt 是函数名
//x是函数的参数,第二个double表示需要输入的x的类型
- 头文件包含
库函数是在标准库中对应的头文件中声明的,所以库函数的使用必须包含对应的头文件,不包含头文件可能会出现报错和计算错误.
-
库函数文档的一般格式
-
函数原型
-
函数功能介绍
-
参数和返回类型说明
-
代码举例
-
代码输出
自定义函数
- 函数的语法形式
自定义函数和库函数是一样的
ret_type fun_name(形式参数)
{
}
- ret_type 是函数返回类型
- fun_name 函数名字
- {}括起来的是函数体,函数体就是完成计算的过程
ret_type使用来表示函数计算结果的类型,有时候返回类型可以是void表示什么都不返回
函数的参数也可以是void表示函数没有参数,如果有参数要交代清楚参数的类型和名字还有参数的个数
//函数的定义
int add(int x,int y)//写清楚参数的类型和个数
{
int a=0;
a=x+y;
return a;
}
int main()
{
int a =0;
int b =0;
scanf("%d %d",&a,&b);
int c =add(a,b);
printf("%d",c);
return 0;
- 形参和实参
依旧拿上面的代码例子来给大家解释
//函数的定义
int add(int x,int y)//形式参数,简称形参
{
int a=0;
a=x+y;
return a;
}
int main()
{
int a =0;
int b =0;
scanf("%d %d",&a,&b);//真实传递给函数的参数,a,b是实际参数,简称实参
int c =add(a,b);
printf("%d",c);
return 0;
实参
在上面代码中传递给函数的参数a,b称为实际参数实际参数就是真实传递给函数的参数
形参
在定义代码的时候,在函数名后面的括号中的x,y称为形式参数,简称形参
如果只是定义了函数但不去调用的话函数的参数x,y只是形式上存在的,不会向内存中申请空间,不是真实存在的,所以称之为形式参数,形式参数只有在函数被调用的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化
- 形参和实参的关系

通过上图我们监视实参和形参可以得到如下结论
1.形式参数和实际参数各自有独立的空间
2.形参的修改,不会影星实参
3.形参是实参的一个临时拷贝
4.实参和形参的名字可以相同,但是依旧是不同的空间
return语句
在函数设计中我们会经常用到return语句
-
return 后面可以是一个数值,也可以是一个表达式,如果是表达式那么先执行表达式然后返回表达式的结果
-
-
return 后面也可以不跟任何东西,直接写return这种写法适合函数返回值为void的情况

- return语句执行后,函数就彻底返回,后面的代码不会执行

在函数中符合x+y<10执行了return,后面的代码都不执行了
-
return返回的值和函数返回类型不一样,系统会自动将返回的值隐式转换为函数的返回类型

-
如果函数中存在分支语句那么要保证每种情况下都有return返回,否则会出现编译错误

出现提示不是所有的控件路径都返回值的意思是其他情况该返回什么?这里存在逻辑漏洞
- 函数的返回类型如果不写,编译器会默认函数的返回值类型是int,建议大家如果有明确的返回类型就直接写清楚是什么类型,如果没有直接写void即可
- 函数写了返回类型,但是函数中没有使用return返回值那么函数的返回值是未知的.
总结
本文系统地介绍了C语言中数组和函数的核心概念,主要内容总结如下:
一、二维数组
- 存储方式:二维数组在内存中是连续存放的,并非按行列独立存储。
- 本质理解:二维数组实际上是“一维数组的数组”,
a[0]表示第一行的数组名,a表示整个数组名。
二、C99变长数组(VLA)
- 特性:允许使用变量指定数组大小,提高了数组创建的灵活性。
- 限制:
- 变长数组无法初始化,因为其长度在运行时才能确定。
- 数组大小一旦确定就不可再变(“变长”指创建时长度可变,而非运行时可变)。
- 编译器支持:VS2022默认的MSVC编译器不支持VLA,但可通过配置Clang编译器来使用。
三、数组小练习
- 字符汇聚动画:通过从两端向中间逐步替换字符,实现动态显示效果。
- 二分查找算法:
- 前提:数组必须有序。
- 步骤:确定查找范围 → 计算中间下标 → 比较并缩小范围。
- 优势:相比顺序查找,效率大幅提升。
四、函数
- 基本概念:函数是完成特定任务的一段代码,是C程序的构建模块。
- 库函数:
- 由C标准库提供,如
printf、scanf。 - 使用前必须包含对应的头文件。
- 可通过官方文档学习使用。
- 由C标准库提供,如
- 自定义函数:
- 语法:
返回类型 函数名(参数列表) { 函数体 } - 形参与实参:
- 形参:函数定义时的参数,是实参的临时拷贝。
- 实参:函数调用时实际传递的值。
- 两者拥有独立的内存空间,形参修改不影响实参。
- 语法:
- return语句:
- 用于返回函数结果或提前结束函数。
- 返回值类型需与函数声明一致(不一致时会隐式转换)。
- 函数中所有分支都应有明确的返回值。
学习要点
- 数组:理解其内存布局和访问方式,掌握变长数组的适用场景。
- 函数:区分库函数与自定义函数,理解参数传递机制,熟练使用return控制流程。
- 实践:通过编写小练习(如二分查找)加深对概念的理解和应用。
掌握这些基础知识,将为后续学习指针、结构体等更复杂的C语言特性打下坚实基础。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)