您的当前位置:首页正文

轻松理解C语言函数和多维数组(基础篇)

2024-11-24 来源:个人技术集锦


前言


???数组

什么是数组?

数组就是多个相同类型的元素在一个盒子内就成了一个组。

数组的创建

数组: 数组类型 数组名[元素个数]

例子:

int arr[3];

注意: c99前的标准元素个数必须是字面常量,c99及之后才支持变量定义元素个数。

数组初始化

如果不对数组初始化里面的元素都是随机值

  • 完全初始化

    int arr[3] = { 1, 2, 3 };
    
  • 不完全初始化

    int arr[3] = { 1 };//第一个元素初始化成1,其它元素默认初始化成0
    int arr[3] = { 0 };//所有元素默认初始化成0
    
  • 不指定大小初始化

    int arr[] = { 1 };//根据实际元素设置数组大小
    
字符型数组初始化和字符串初始化的区别
什么是字符型数组初始化?

如果初始化的内容都是一个一个“单引号”括起来的字符,那么这个数组就是字符型数组,字符数组的时候是给多少元素就有多少元素,而在打印字符型数组的内容或计算字符型数组大小的时候会出一些问题,因为字符数组计算和打印是根据\0来判断字符的结束,如果没有初始化的时候末尾没有\0那么会出现一些奇怪的现象。

char arr[3] = { 'a', 'b', 'c' };//错误的字符型数组初始化,不会自动+\0需要手动+\0
char arr[4] = { 'a', 'b', 'c', '\0' };//正确的字符型初始化
什么是字符串初始化?

字符串本质也是字符型数组但是它初始化的时候不是单个初始化看起来像一个串,所以这种形式的字符型数组大家都叫为字符串,字符串和字符型数组的区别在于初始化,字符串是以“双引号”括起来的一串字母,而字符型数组是多个“单引号”括起来的单个字符组成的数组,字符串默认初始化会自带\0,前提是空间要给他预留一个否则也会出现字符型的状况。

char arr[] = "abc";
char arr[4] = "abc";//这个形式和上面的效果等价,但是平常使用都是习惯写上面那种的形式
char arr[3] = "abc";//这种形式是错误的,没有给\0预留一个空间会导致出现和字符型数组一样的问题

总结: 字符串和字符型数组本质都是字符型数组,但是字符串会带\0而字符型数组不会带\0,其次字符型数组的初始化太难受了?一般都是使用字符串初始化方便其次会自带\0

多维数组

多维数组其实是存储低维数组的一种数组,理论上来讲只要你想这个数组维度可以是高,实际开发中使用最高的也就是二维数组。

多维数组的创建

多维数组的创建和一位数组一样不过唯一的区别是,维度每高一层就会多一个[] 我们可以根据[]的个数来判断是几维数组。

int arr1[] = { 1, 2, 3 };//一维数组
int arr2[2][3] = { {1, 2, 3 }, {4, 5, 6 } };//二维数组

数组的存储

数组的存储一般都是由低到高这样递增,一个数组的大小是由元素个数×元素类型。

#include <stdio.h>

int main()
{
	int arr[2][3] = { {1, 2, 3 }, { 4, 5, 6 } };

	int i = 0;
	int j = 0;

	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("arr[%d][%d]的地址:%p\n", i, j, &arr[i][j]);
		}
	}

	return 0;
}

数组名不是首元素的两种情况

?goto语句

用于跳转到指定位置,这个功能很强大但也很致命,因为可以随意跳转会导致程序执行顺序改变,从而导致结果补可预测。

用法:

int main()
{
    test1://标签,跳转的地方
    
    while (表达式)
    {
        //...
        while (表达式)
        {
            goto test1;//跳转到标签所在位置,重新执行
        }
    }
    
    return 0;
}

注意: 实际开发中无论如何都不能使用goto语句,它很容易造成致命bug而这种bug是难以解决的!!!

???32个关键字

auto double int struct break else long switch

case enum register typedef char extern return union

const float short unsigned continue for signed void

default goto sizeof volatile do if while static

数据类型关键字

  1. 基本数据类型(5个)

    • void

      声明函数无返回值或无参数,声明无类型指针,显式丢弃运算结果

    • char

      字符型类型数据,属于整型数据的一种

    • int

      整型数据,通常为编译器指定的机器字长

    • float

      单精度浮点型数据,属于浮点数据的一种

    • double

      双精度浮点型数据,属于浮点数据的一种

  2. 类型修饰关键字(4个)

    • short

      修饰int,短整型数据,可省略被修饰的int。

    • long

      修饰int,长整形数据,可省略被修饰的int。

    • signed

      修饰整型数据,有符号数据类型

    • unsigned

      修饰整型数据,无符号数据类型

  3. 复杂类型关键字(5个)

    • struct

      结构体声明

    • union

      共用体声明

    • enum

      枚举声明

    • typedef

      声明类型别名

    • sizeof

      计算类型或类型变量的大小

  4. 存储级别关键字(6个)

    • auto

      指定为自动变量,由编译器自动分配及释放。通常在栈上分配

    • static

      指定为静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部

    • register

      指定为寄存器变量,建议编译器将变量存储到寄存器中使用,也可以修饰函数形参,建议编译器通过寄存器而不是堆栈传递参数

    • extern

      指定对应变量为外部变量,即在另外的目标文件中定义,可以认为是约定由另外文件声明的对象的一个“引用“

    • const

      与volatile合称“cv特性”,指定变量不可被当前线程/进程改变(但有可能被系统或其他线程/进程改变)

    • volatile

      与const合称“cv特性”,指定变量的值有可能会被系统或其他进程/线程改变,强制编译器每次从内存中取得该变量的值

流程控制关键字

  1. 跳转结构(4个)

    • return

      用在函数体中,返回特定值(或者是void值,即不返回值)

    • continue

      结束当前循环,开始下一轮循环

    • break

      跳出当前循环或switch结构

    • goto

      无条件跳转语句

  2. 分支结构

    • if

      条件语句

    • else

      条件语句所有条件不满足默认执行它和if搭配使用

    • switch

      开关语句

    • case

      开关语句的标记

    • default

      开关语句默认执行,当其它的条件不满足就执行它。

  3. 循环结构

    • for

      for循环结构,for(1;2;3)4;的执行顺序为1->2->4->3->2…循环,其中2为循环条件

    • do

      do循环结构,do 1 while(2);的执行顺序是1->2->1…循环,2为循环条件

    • while

      while循环结构,while(1) 2;的执行顺序是1->2->1…循环,1为循环条件

✨✨✨函数

什么是函数?

C语言里面的函数是一个子程序,一个大型项目是由多个子程序组成的项目,而每个子程序是用于执行某个特定的任务。

函数的组成

函数是由三个模块组成,返回值、参数、函数名

返回值

什么是返回值?

返回值是一种结果,当一个函数执行完了必须返回一个结果,这个结果通常是给调用者看的。

例子:

我让你去帮我买一杯奶茶,而你买完奶茶回来的时候只有两种结果。

  1. 买到奶茶
  2. 没买到奶茶

而我让你去买奶茶我不会关心你怎么去买的,我只关心你有没有买回来这就是一个结果,而返回值和现实生活中的一些行为很类似。

参数

什么是参数?

参数在函数里可以有也可以没有具体需不需要参数,由函数的内容决定。

例子:

我让你去买一杯奶茶,如果你有钱我就不需要给你钱去买,如果你没有钱我就需要给你钱去买,而这个钱在函数里面相当于参数而要不要给参数需要根据不同情况而定。

函数名

什么是函数名?

函数名相当于要执行的指定任务里面的主要内容,当然你也可以随便取名但是在你使用的时候会容易分不清这个函数里面的内容,通常我们给函数取名都是根据他执行的任务有关的名字。

例子:

我要喝奶茶,我让你去买。

这个任务的主要内容是买奶茶,所以这个函数名我们会取成买奶茶而不是和任务内容无关的名字。

函数调用

什么是函数调用?

函数调用指的是你让那个函数去完成。

实参

什么是实参?

实参是指你在调用它的时候给它的参数,而在这里的参数我们都称之为实参。

形参

什么是形参?

形参指的是函数那接收的参数,而这个参数只是形式上的参数,实际上是不存在的,只有当你真的给它参数的时候才会存在。

传参

什么是传参?

当实参把数据传给形参的时候,这个过程我们称之为传参。

值传递
什么是值传递?

值传递指的是实参传递的时候只传递它里面的内容,这个行为叫做值传递。

例子:

我有个盒子,我把盒子里面的东西给你看一下,然后你直接去买一个一摸一样的盒子里面的东西你也装的和我一样,而这个时候你想要有和我一样的盒子你也买了一个一摸一样的盒子盒子里面装的一摸一样的东西,而这个时候会有两个一摸一样的盒子,但是这个盒子里面装的虽然是同一个东西但你的盒子不是原装的,你对你盒子里面的东西随便改随便捣鼓都不会影响到我盒子里面的东西,因为这是两个盒子不是同一个盒子,虽然东西一样盒子的样子也一样,但不是同一个东西。

#include <stdio.h>

void Swap(int a, int b)
{
    int temp = a;
    a = b;
    b = temp;
}

int main()
{
    int a = 100;
    int b = 3;

    printf("交换前:a:%d\tb:%d\n", a, b);

    Swap(a, b);

    printf("交换后:a:%d\tb:%d\n", a, b);

    return 0;
}

int add(int a, int b)
{
    int ret = a + b;

    return ret;
}

址传递

什么是址传递?

址传递指的是我把我这个实参给你了,这个行为是址传递。

例子:

我有个盒子,我把这个盒子借你用,你是直接拿到我这个盒子,然后你把盒子里面的东西换了或者弄坏了,会影响到我因为我借你的东西我拿回来不一样了,因为我们两用的是同一个盒子所以会影响到我。

#include <stdio.h>

void Swap(int* a, int* b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main()
{
    int a = 100;
    int b = 3;

    printf("交换前:a:%d\tb:%d\n", a, b);

    Swap(&a, &b);

    printf("交换后:a:%d\tb:%d\n", a, b);

    return 0;
}

int add(int a, int b)
{
    int ret = a + b;

    return ret;
}

库函数

什么是库函数?

库函数你可以想象成是一个工具箱,这个工具箱里面有很多工具你想要用某个工具你就去装这个工具的工具箱里面拿。

为什么要有库函数?

因为很多人都经常用到某个工具,但是这个工具没有工具箱而没有工具箱你这个工具就会消失,而一旦消失就要重做每次开发不同项目要重做之前的常用工具,这会导致效率很慢而且你的工具也容易出问题。

假设C标准委员会是设计图纸的人,而编译器的厂商是制作工具的人,C标准委员会看到打家经常用某个工具,C标准委员会决定定制这个工具的图纸(标准),而这个图纸(标准)一旦发布那么厂商(编译器的开发团队)就会按照这个图纸(标准)制作相关的工具并放到工具箱内,来供开发者使用这样开发者只需要去指定的工具箱里面拿工具,也不用担心自己做的工具会出问题,同时还节省了时间让开发者更专注的开发。

自定义函数

什么是自定义函数?

自定义函数就是指自己创建的函数,说人话就是你自己做的一个工具,这个工具不经常使用一般只有你一个人或极少数人用的到,所以你需要自己做一个这种工具。

函数的创建与调用

假设我们做个加法器工具,这个加法器只能进行简单的运算。

#include <stdio.h>

int add(int a, int b)
{
    int ret = a + b;

    return ret;
}

int mian()
{
    int a = 10;
    int b = 20;

    int ret = add(a, b);

    printf("%d\n", ret);

    return 0;
}

函数的声明与定义

什么是函数的声明?

函数的声明是当编译器找不到该函数的定义的时候,对函数进行声明。因为编译器再进行编译的时候会对语法检查而它检查的方式是从上往下依次执行,当执行到调用函数的位置的时候还没看到过函数的定义,那么它会对此进行一个报错此error为:未找到定义的xxxx函数。

而函数声明的作用相当于提前跟编译器说如果你在看到这个函数名前没看到函数的定义的话请往下继续找,这个行为只是和编译器说了一声但是真的有没有不是你声明说的算,如果没有的话编译阶段不会报错但是调用阶段会报错此error为:链接错误。

例子:

编译器:这个函数我在前面没见过啊!

我:这个函数在后面你往后找就能找到了。

编译器:好,那我先给你编译通过。

链接ing……

找不到的情况:

编译器:你说后面有这个东西我也没找到啊!

编译器:error链接失败!

找到的情况:

编译器:我找到了!

编译器:链接成功,生成可执行程序!


总结: 一般在使用声明的时候,定义要么在函数调用后面要么就是在其它源文件内,为了编译通过会在该函数调用前和编译器说一声,而这个行为我们叫做声明。

什么是函数的定义?

函数的定义就是函数具体的实现细节,里面的实现细节我们称之为定义。

例子:

我们定义一个加法函数,不是说我们直接写个函数名和参数就能实现加法的,需要把实现的过程写在里面这就是实现方法也叫函数的定义。

函数递归

什么是函数递归?

函数递归指的是函数的定义里面有重复调用自己来完成某种特殊的任务,这种行为我们称之为递归。

类似大事化小,小事化了。把一个大型任务分解成诺干个小型任务再把若干个小型任务分解成微型任务直到任务无法再分解再交给自己完成。

相当于火影里面的鸣人能有很多个自己的分身让分身去完成不同的任务,这样就达到分解一个大型任务的效果,当然计算机使用这能力也是有代价的就是因为分裂的越多那么内存占用也就越大,效率也就降低了因为管一两个人很好管,如果管一个学校的所有学生不好管也没那个精力全都管到,如果硬要全都管到那么代价就是时间成本增高。

函数递归的必要条件

  • 条件一

    结束函数递归的条件,如果没有这个条件函数就会无休止的调用下去直至程序崩溃。

  • 条件二

    在函数每执行一次递归,函数和终止递归的条件距离就缩短一步。如果没有这个条件那么函数也会无休止的递归下去直到程序崩溃。

函数递归的优点与缺点

优点
  1. 一个复杂的任务,用递归来完成代变会变的非常简单
  2. 代码量少
缺点
  1. 对于人来说任务变简单了,但是对于机器来说任务变的有很多不必要的重复,从而导致任务执行效率变低了。
  2. 代码量少了,只是占用磁盘空间变小了,但是内存占用变大的了。
  3. 由于一直分解导致有很多个自己在执行不同的任务,使得程序员推演代码的结果成本变高了,一旦出问题调式起来就是噩梦。

总结: 当你对程序效率没要求且这个程序用递归实现不会出问题,那么这个时候推荐用递归因为递归代码量少代码逻辑也不复杂,当用递归的结果会出问题或对效率有要求就使用循环,显然循环代码量大且代码逻辑会有点复杂但是是最优解不管是性能上还是其它方面。


???求个一键三连

显示全文