c 语言入门( 24000 字详解 )

在本文中,对每个知识点做简单认识,不做详细讲解,先基本了解 c 语言的基础知识,对c语言有一个大概的认识。

一: 什么是c语言?

  • C 语言是一种通用的、面向过程的编程语言。
  • C 语言以其简洁、高效和可移植性而闻名。它提供了丰富的编程结构和库函数,可以用来开发各种类型的软件,从嵌入式系统到操作系统,从游戏到商业应用。
  • C 语言的语法相对简洁,具有良好的可读性和可维护性。同时,C语言的执行速度也非常快,因为它直接操作计算机的底层硬件。
  • 由于 C 语言的广泛应用和丰富的工具与库支持,它成为了许多其他编程语言的基础,是学习其他高级编程语言的良好起点。

二: 第一个c语言程序

#include <stdio.h>
int main()
{
  printf("hello bit\n");
  printf("he he\n");
  return 0;
}

简单解释一下这段代码的意思:

  • 在我们写代码的时候,有些功能会频繁被我们使用,为了方便我们更加高效的写代码,每个编译器都提供了许多库,每个库中又有许多函数,一个函数实现了对应的一个功能
  • 比如说:printf() 这个函数的功能是将数据按照指定的格式输出到屏幕上。() 中放的是函数的参数,但是 printf() 这个函数是库中的函数,它在 stdio.h 这个库中定义,所以我们要想使用 printf() 这个函数就需要将这个库导入程序中.
  • #include <stdio.h> 就是将 stdio.h 这个库导入程序,这样我们才能够使用程序中的 printf() 函数
  • printf(“hello bit\n”);就是在屏幕上打印 hello bit 并且换行,\n 就是换行的意思,\n 是转义字符,不会打印出来,同理,printf(“he he\n”);就是在屏幕上打印 he he 并换行

2.1 主函数基本格式

int main()
 {
 	return 0;
  }

以上是主函数的基本格式,main() 叫做主函数,它是一个特殊的函数。

注意:

  • 主函数有且仅有一个,它是程序的入口点,c 语言程序的执行从主函数开始,也在主函数结束时终止

其中 int 代表 main() 的返回值类型,return 0;代表的是 main() 的返回值是 0,在 C 语言中,主函数可以不需要接收任何参数,所以 () 中可以留空。

主函数中的 return 0;表示程序执行成功,并返回给操作系统一个退出码,通常约定成功返回 0,其他非零值则表示程序执行出现错误。操作系统可以根据这个退出码来判断程序的执行状态。

三:数据类型

c语言中数据类型有以下 7 种:

符号类型
char字符型
short短整型
int整型
long长整型
long long更长的整型
float单精度浮点数
double双精度浮点数
  • char 是描述字符的
  • short,long,int,long long 是描述整型的
  • float,double 是描述浮点型的

那什么是字符,什么是整型,什么是浮点型呢

  • 字符就是我们在键盘上打出的任意一个符号,字符的表示需要在这个符号的基础上加上单引号 ’ ’ ,比如 ‘a’ ‘t’ ‘!’ ‘@’ ‘~’ ‘3’(这是字符3,不是数字3)
  • 整形顾名思义就是整数类型的数据,也就是整数
  • 浮点型就是小数,小数被称为浮点数或浮点型是因为它们在内存中的表示方式是通过使用浮点表示法。

为什么我们对整型和浮点型的描述需要这么多类型呢?

  • 主要原因是因为我们需要对内存空间进行合理的分配,

short,long,int,long long 虽然都是描述整型的,但是他们的取值范围不一样,在内存中所占的空间大小也就不一样,选择使用哪种整型类型取决于你对内存空间和数值范围的需求。

如果你需要处理的数值较小,可以选择使用 short 或者 int 类型,而如果需要处理的数值范围较大,你可以选择使用 long 或者 long long 类型。

同理,浮点型也一样,

  • double 的精度更高,但是占用的内存空间会更大
  • 而float的精度低,但是占用的内存空间比较小。

注意:浮点型在内存中是无法精确存储的,这与浮点型在内存中的存储方式有关,只能对浮点型的精度进行控制,控制误差,无法准确的存储浮点型数据。

3.1 数据类型的大小

数据类型类型名字内存中占用的空间大小
char字符型1个字节
short短整型2个字节
int整型4个字节
long长整型4个或者8个字节
long long更长的整型8个字节
float单精度浮点数4个字节
double双精度浮点数8个字节

在此我们需要了解一下内存存储的单位:

在这里插入图片描述

8 个二进制位等于 1 个字节,而 1024 个字节又等于 1 千字节,1024 个千字节等于 1 兆………以此类推

四: 变量与常量

变量即稳定不变的量,而常量就是会发生改变的量,常量和变量的概念都源于生活,生活中有些东西是不会改变的,比如说圆周率,血型等,当然生活中也存在一些会改变的东西,比如说年龄,体重等……

4.1 定义变量的方法

int age = 150;
float weight = 45.5;
char ch = 'w';

在此解释一下 int age = 150;这段代码的意思:

  • int 是数据类型,表示 age 的类型是 int
  • age 则是变量的名字,变量的名字你可以随便取,最好要有实际意义
  • = 是赋值符号而不是等于符号,赋值符号是从右向左执行的
  • 这段代码先声明了一个变量 int age 接着通过 = 这个赋值符号,从右向左将 150 赋给了 age 这个变量,注意:赋给 age 的值要与 age 的类型对应上,
  • 注意:字符 w 应该写成 ‘w’ 而不是 w(我们需要通过 ‘ ’ 来标识一个字符)

4.2 变量的命名

  • 只能由字母(包括大写和小写)、数字和下划线组成。

  • 不能以数字开头。

  • 变量名中区分大小写的。

  • 变量名不能使用关键字。

变量名区分大小写的意思就是说 Abandon 和 abandon 是两个不同的变量名,ABandon 和 abandon 也同样是两个不同的变量名

C 语言中的关键字是具有特殊含义的标识符,它们被用于特定的语法结构和功能。比如说 int,short,long 等都是关键字,不能将关键字拿来命名

4.3变量的分类

变量分类一般有两种:

  • 局部变量
  • 全局变量

在了解局部变量和全局变量前,我们先了解一下什么是变量的作用域和生命周期

  1. 作用域

作用域是指在程序中某个特定位置声明的变量所能被访问的范围,变量在其作用域内可以被引用和修改,而在其作用域外则无法访问。

  1. 生命周期

生命周期是指变量存在的时间范围,即变量从创建到销毁的整个过程。每个变量都有其特定的生命周期。

那么我们如何知道一个变量是局部变量还是全局变量呢?

#include <stdio.h>
int global = 2019;
int main()
{
  int local = 2018;
  printf("global = %d\n", global);
  return 0;
}

一个变量的作用域就是最靠近这个变量的左右括号所括起来的范围,如果有括号将变量括起来了,那么这个变量就是局部变量,反之则是全局变量

  • 局部变量的作用域是变量所在的局部范围
  • 全局变量的作用域是整个项目

例如:上述代码中的 local 就是一个局部变量,它的作用域是主函数,而 global 则是一个全局变量,它的作用域是整个项目,注意不要在一个项目中定义两个重名的全局变量,他们的作用域都是整个项目,如果重名了,则会报错

  1. 变量创建的本质

先在内存中开辟一块空间,存放数据,出了作用域变量就无法使用了,当变量的生命周期结束了,就会被销毁(即被操作系统回收,不是将内存给销毁了,只是将内存还给操作系统了)

但是有时候我们可能会很粗心,不小心将全局变量和局部变量的名字重名了,c 语言允许这种情况发生吗?

#include <stdio.h>
int global = 2019;
int main()
{
  int local = 2018;
  int global = 2020;
  printf("global = %d\n", global);
  return 0;
}

很明显,两个 global 的作用域在 main 函数中都存在,那 printf() 打印的结果是什么呢?是否会报错呢,让我们看看这段代码的运行结果:

在这里插入图片描述

我们发现程序运行良好,没有报错,并且 global 打印的值是 2020,那么我们可以得出一个很重要的结论:

  • 当局部变量和全局变量同名的时候,局部变量优先使用

4.4 printf() 和 scanf()

4.4.1 printf()

printf() 是一个C语言中的标准库函数,用于格式化输出,它被定义在 stdio.h 头文件中。

printf 的全名是 “print formatted”(格式化打印),当我们用 printf() 函数时,需要用这段代码导入 stdio 这个库:

#include <stdio.h>

导入这个库之后我们才能使用 printf() 这个函数

下面通过一个例子来说明 printf() 函数的作用以及使用方法

#include <stdio.h>
int main()
{
  int global = 2020;
  printf("global = %d\n", global);
  return 0;
}
  • printf() 中,括号放入函数的参数,
  • “global = %d\n” 这是一个格式化字符串,它包含了占位符 “%d"。这个占位符用来表示一个整数(decimal),它将被替换为变量 global 的值。“\n” 表示换行符,用于在输出中创建一个新的行。
  • global 则是 %d,这个占位符被替换的值
  • 替换完后将字符串打印在屏幕上

这段代码的输出结果是:
在这里插入图片描述

字符串就是一个个字符组成的集合,为了和其他数据区分开来,需要用双引号" "和别的数据类型区分开来 ,比如 “1456346346” “asfih325” “sfafkjh” 等都是字符串

4.4.1.1 占位符

接着我们来了解一下printf()函数中的占位符:

符号含义
%c字符的占位符
%s字符串的占位符
%d整型的占位符
%ffloat的占位符
%lfdouble的占位符
%p地址的占位符
4.4.1.2 printf() 函数设置精度,宽度,以及对齐方式

在使用 C 语言中的 printf 函数进行格式化输出时,可以使用格式控制符来指定宽度、精度和填充字符。

  • 指定宽度:使用数字指定输出的最小字符数。例如,%5d 表示输出至少 5 个字符宽度的整数值,如果不足 5 个字符,则在左边用空格填充。
  • 指定精度:对于浮点数,使用 .n 来指定小数位数,其中 n 为希望输出的小数位数。例如,%.2f 表示输出 2 位小数的浮点数。
  • 填充字符:使用 %[填充字符] 来指定填充字符,将填充字符放在宽度和格式化字符之间。例如,%06d 表示使用 0 作为填充字符,在输出宽度不足时用 0 进行填充。

下面是一个示例:

#include <stdio.h>
int main()
{
	int num = 42;
	float pi = 3.14159;

	printf("%5d\n", num);   
	printf("%-5d\n", num);  
	printf("%.2f\n", pi);  
	printf("%06d\n", num); 
	return 0;
}

运行结果如图所示:
在这里插入图片描述

4.4.2 scanf()

scanf() 是一个C语言中的标准库函数,用于输入数据。

它的主要作用是将用户输入的数据存储到变量中,以供程序进一步处理或使用,它被定义在 stdio.h 头文件中,使用 scanf() 之前需要用这段代码导入 stdio 这个库:

#include <stdio.h>

导入后我们才能使用scanf()这个函数。

4.4.2.1 scanf() 函数用法

语法:scanf(format, variable);

  • format 是一个包含占位符的格式化字符串,用于指定输入数据的类型和格式。
  • variable 是要存储输入数据的变量,需要提供变量的地址(即使用&运算符)。

下面通过一个例子来演示 scanf() 函数的用法

#include <stdio.h>
int main()
{
	int num = 0;
	printf("请输入一个数字赋值给num:");
	scanf("%d", &num);
	printf("num = %d\n", num);
	return 0;
}

运行结果如图所示:

在这里插入图片描述

下面对代码进行解析:

scanf("%d", &num);

这段代码的意思是先读取用户输入的数据,并将这个数据赋给 num 这个变量,

  • &是取地址的意思,scanf 函数需要变量的地址作为参数,因为它需要知道变量在内存中的位置才能够将输入的数据存储到正确的变量中。
  • 在 C 语言中,变量是存储在内存中的一块特定的空间,每个变量都有一个唯一的地址。当我们将变量的地址传递给 scanf 函数时,它就可以通过这个地址找到变量所在的内存位置,并将输入的数据直接写入到这个内存位置。
  • 如果我们不将变量的地址传递给 scanf 函数,而是直接传递变量本身,那么 scanf 将无法确定变量在内存中的位置,也就无法将输入的数据正确地存储到变量中。
4.4.2.2 scanf() 函数的注意事项
  1. 输入数据时候的分隔符要和scanf()函数中的分隔符一样,比如:

scanf(“%d,%d”&num1,&num2);

  1. %d,%d之间有一个逗号,这个逗号就是分隔符),那么我们在输入两个数据的时候也应该加上逗号,比如:100,200(切记不要加空格)

scanf(“%d %d”&num1,&num2); %d,%d之间有一个空格(这个空格就是分隔符),那么我们在输入两个数据的时候也应该加上比如:100 200(切记不要加逗号)

4.4.2.3 scanf()意义:
  • 允许用户从键盘或其他输入设备输入数据,实现与用户的交互。

4.4.3 printf() 和 scanf() 的返回值

  • printf() 函数的返回值是打印到标准输出流的字符数,即成功打印的字符数。
  • scanf() 函数的返回值是成功读取的输入项的个数,即成功匹配并读取的输入项数量。如果读取失败则返回EOF(End Of File也就是-1)

4.5 常量

常量一般有三种:

  • const 修饰的常变量
  • #define 定义的标识符常量
  • 枚举常量

4.5.1 const 修饰的常变量

const int n = 100;

此时 n 就是一个常量,因为 n 被 const 修饰了,所以 n 具有了常属性,不能被修改,如果后面有对 n 的值进行修改的代码,那么这个代码会报错,但是 n 此时还是一个变量,只不过有了常属性了而已,故又称作常变量

const 修饰的常变量只是在语法层面限制了变量 n 不能被直接改变,但是 n 的本质上还是一个变量,只是具有了常属性

4.5.2 #define定义的标识符常量

#include <stdio.h>
#define PI 3.14159

int main() {
    printf("The value of PI is: %f\n", PI);
    return 0;
}

下面我来解释一下这段代码的意思:

  • #define PI 3.14159 表示将标识符 PI 定义为一个常量,在此处的值为 3.14159。 #define 是一个预处理指令,用于在编译代码之前进行文本替换。当我们在代码中使用 PI 时,它将被预处理器替换为 3.14159,就像我们直接写出 3.14159 一样。
  • printf(“The value of PI is: %f\n”, PI); 所以在打印这句话的时候PI会自动被替换为 3.14159,并且 PI 的值无法修改

通过 #define 定义常量的意义:

  1. 这种定义常量的方式可以让我们在代码中使用可读性更好的符号来表示特定的常量值。在这种情况下,通过使用 PI 代替直接写出具体的数值,我们能够清晰地表达这个数值是圆周率的近似值。这让数值有了具体的意义,而不只是简单的一个数字
  2. 这种定义常量的方式还有一个好处是,如果我们需要在代码中修改这个常量的值,只需要在 #define 行上修改一次,而不需要在代码中的所有出现的地方都进行修改。这样可以提高代码的可维护性和灵活性。

4.5.3 枚举常量

enum Sex
{
	MALE,
	FEMALE,
	SECRET
};

这段代码定义了一个枚举类型 Sex,它包含三个枚举成员:MALE、FEMALE 和 SECRET。每个枚举成员都表示一个特定的性别选项。

枚举常量如果不赋值,那么第一个枚举成员默认为 0,接着第二个枚举成员默认为 1,接着第三个枚举成员默认为 2

在这个情况中 MALE 的值为 0,FEMALE 的值为 1,SECRET 的值为 2 (程序员是从 0 开始数数的)

enum Sex
{
	MALE = 6,
	FEMALE,
	SECRET
};

而在这种情况下 MALE 的值为 6,FEMALE 的值为 7,SECRET 的值为 8

enum Sex
{
	MALE = 6,
	FEMALE = 9,
	SECRET= 13
};

当然也可以分别对 MALE,FEMALE,SECRET 分别赋值,在这种情况下 MALE 的值为 6,FEMALE 的值为 9,SECRET 的值为 13

4.5.3.1 枚举常量的注意事项
  • 枚举成员的默认值是从 0 开始的,并且从上至下依次递增
  • 枚举成员之间是以逗号隔开的,而不是以分号
  • 枚举列完之后有一个分号,千万不要漏了
4.5.3.2 枚举常量的意义

与 #define 定义的标识符常量类似,枚举常量也让数字具有了意义,而不只是单纯的一个数字,例如:

enum Sex
{
	MALE = 6,
	FEMALE = 9,
	SECRET= 13
};

在这个例子中,我们可以用 MALE 来代替6来使用,使用枚举能够让代码更具可读性,因为每个枚举成员都有一个有意义的名字。在代码中使用枚举成员,可以更清晰地表达其含义,而不是使用数字或其他形式的常量

五:字符串+转义字符+注释

5.1字符串

字符串顾名思义就是一串字符的集合,字符串被双引号 " " 引住

注意:

  • ‘w’ 与 “w” 不同,‘w’ 是字符,“w” 是字符串

并且字符串是以 \0 为结束标志的,这个标志我们无法肉眼看见,需要通过调试才能发现,当然 \0 在计算字符串长度的时候是不被计算的,而在计算字符串的内存大小时则会被计算进去。

char只能存储单个字符,字符串的存储需要通过字符数组来实现(有个印象即可)

5.2转义字符

转义字符是由 反斜杠 \ 后面跟着一个特定字符组成的字符序列。

它们的存在是为了在字符串中表示一些特殊字符、字符序列或者不可打印的字符。转义字符的目的是用来改变字符原本的含义,让这些字符具有特殊的功能。

因为键盘上的按键都是我们使用最频繁的按键,但是我们的键盘上能打印的字符有限,但是又有一些符号在某些情况下我们可能需要用到比如说 29中的 9 上标,所以有了转义字符,这就是转义字符的意义

以下是常用的转义字符:

符号作用
\?在书写连续多个问号时使用,防止他们被解析成三字母词
\’用于表示字符常量’
\"用于表示一个字符串内部的双引号
\\用于表示一个反斜杠,防止它被解释为一个转义序列符
\a警告字符,蜂鸣
\b退格符
\f进纸符
\n换行
\r回车
\t水平制表符
\v垂直制表符
\dddddd表示1~3个八进制的数字。 如: \130 X
\xdddd表示2个十六进制数字。 如: \x30 0

下面有两个问题:

  • 问题1:写个程序在屏幕上打印一个单引号 ,怎么做?
  • 问题2:写个程序在屏幕上打印一个字符串,字符串的内容是一个双引号 ,怎么做?

可能有人会觉得这题很简单,不假思索就说出了:

  • printf(“%c”,');
  • printf(“%s”,");

然而这种写法是错误的:

在C语言中,单引号和双引号都具有特殊的含义。单引号用于表示一个字符字面量,而双引号用于表示一个字符串字面量。

如果你直接将一个单引号或双引号放在 printf 函数的格式字符串中进行打印,编译器会将其解释为字符串的起始或结束,并且期望在引号之后提供相应的内容。这样会导致编译错误,因为你提供的内容是不完整的。

正确的做法:

include <stdio.h>
int main()
{
  
  printf("%c\n", '\'');
  printf("%s\n", "\"");
  return 0;
}

正确的做法是通过转义字符,通过转义字符来防止程序将单引号解析成一个字符字面量,同理双引号也一样

5.3 注释

注释是程序代码中用于给人阅读和理解的文字说明。注释可以被编译器或解释器忽略,不会影响代码的执行。注释的目的是提供代码解释、补充说明、指导团队合作和代码维护等方面的信息

c 语言的注释有两种:

  • 单行注释: // 内容
  • 多行注释: /* 内容 */

对于单行注释来说, // 只能注释掉 // 所在行的代码,注释掉的是一行的代码

而对于多行注释来说, / * */ 可以注释掉 / * */ 之间的所有内容,但是要注意多行注释不能够嵌套,即 / * / * */ */,因为 /*会先和与它最近的 */ 进行匹配,所以/ * / * */ */,就相当于只有 */了

六: 选择语句 if

if 的基本结构:

if (表达式) 
{
  // 当条件为真时执行的代码块
}
else
{
	//当上述条件不满足时执行这个代码
}

如果表达式的值为真,那么则执行 if 中的代码,如果表达式的值为假,则不执行 if 中的代码,此时如果有 else,则执行 else 中的代码

下面是 if 的基本使用方法:

#include <stdio.h>
int main()
{
  int coding = 0;
  printf("你会去敲代码吗?(选择1 or 0):>");
  scanf("%d", &coding);
  if(coding == 1)
 {
   prinf("坚持,你会有好offer\n");
 }
  else
 {
   printf("放弃,回家卖红薯\n");
 }
  return 0;
}
  • 这段程序先打印 “你会去敲代码吗?(选择1 or 0):>” 这句话,然后读取用户输入的信息(0或者1)
  • 如果是 1,那么coding == 1这个条件为真,所以执行 if 中的代码
  • 如果是 0.那么则执行 else 中的代码

七: 循环语句 while

while 循环是 C 语言中的一种迭代控制结构,用于反复执行一段代码,直到给定条件变为假。

while 循环的基本语法如下:

while (条件) 
{
    // 循环体,会重复执行的代码块
}
  • 首先,计算条件的值。如果条件为真,则执行循环体中的代码块;
  • 然后再次计算条件的布尔值。如果条件仍然为真,则再次执行循环体中的代码块。
  • 这个过程会一直重复,直到条件变为假为止。
  • 如果初条件的值为假,那么循环体中的代码块将不会被执行。则会继续执行接下来的其他代码。

以下是一个使用 while 循环的示例,它打印数字 1 到 5:

#include <stdio.h>

int main() {
    int i = 1;
    
    while (i <= 5) {
        printf("%d\n", i);
        i = i + 1;
    }
    
    return 0;
}

  1. 在上述示例中,我们定义了变量 i 并将其初始化为 1。
  2. 接着,遇到了 while 循环,先判断条件 i <= 5是否满足,i = 1,
  3. 很明显满足条件,于是我们打印了 i 的值,此时 i 的值是 1,所以我们打印 1,
  4. 接着, i +1 等于2,并且将这个值赋给了 i ,此时 i == 2;(=是赋值符号而不是等于号,并且是将右边的值赋给了左边的值),
  5. 执行完代码块的代码后又进行 while 中的条件判断,因为此时i等于 2,依然满足条件,所以继续执行代码块中的代码,打印 2,并且将 i 变成 3
  6. 在每次循环迭代中,我们打印变量 i 的值,并递增 i 的值。循环将继续执行,直到 i 的值不满足条件,即 i 大于 5

while 循环可以用在满足某个条件时重复执行某块代码。在编写 while 循环时,务必要确保循环的条件最终会变为假,否则会导致无限循环。在上述代码中,我们通过控制 i 的值来控制条件,没有造成死循环

死循环是指在程序中一个循环条件始终为真,导致循环永远不会终止的情况。也就是说,循环中的代码会一直执行下去,直到程序被手动中断或发生其他异常。循环才能够结束

八: 函数

一个函数完成某一个特定的功能,此前我们学习的 printf() 函数的功能就是将字符串打印在屏幕上, scanf() 函数的功能就是读取用户输入的数据,并赋值给某个特定的变量,一个函数完成一个特定的功能,这就是函数

在此我们简单了解函数即可,后续会对函数及其他各个知识点进行深度展开

除了库中已经写好的许多函数,我们还可以自己自定义一个函数,因为库中的函数不可能无穷无尽,将功能覆盖到各个方面,所以这时就需要我们自定义一个函数使用,这可以大大的增加程序的灵活性和可扩展性,让我们能够在特定的需求下完成特定的功能

8.1: 函数四要素

  1. 函数名:函数名是函数的标识符,用于唯一标识函数并在其他地方调用它。函数名应当具有描述性,能够清晰地表达函数的功能和用途。

  2. 参数列表:函数可以接受零个或多个参数,参数用于传递数据给函数。参数列表定义了函数可以接受的参数的类型、顺序和数量。

  3. 返回值类型:函数可以返回一个值作为函数的输出结果。返回值类型定义了函数返回值的数据类型。函数体中 return 返回值的类型必须要和返回值类型 (函数名前的类型) 一样。

  4. 函数体:函数体是函数的具体实现。它包含了函数的操作和逻辑,通过一系列的语句来完成特定的任务。

8.2: 实现两个整型相加的简单函数

接着我们来简单实现一个函数,这个函数的功能用于相加两个整型

  • 因为要对两个整型进行相加,所以我们需要有两个变量,暂且设为 x 和 y,并且 x 和 y 都应该是整型的,
  • 函数的名字就起做 Add,
  • 因为两个整数相加,结果也应该是整型,所以返回值类型应该是 int 类型的

所以现在函数的大致样子应该是 :

int Add(int x,int y)
{

}
  • 接着就是对代码的具体实现了,首先我们应该再定义一个数,这个数应该是整型的,用于接收 x 和 y 的和,所以有 int z = x + y;
  • 此时 z 就是 x 和 y 的和了,所以我们现在将 z 作为返回值返回即可 即 return z;

最终函数如下:

int Add(int x, int y)
{
	int z = x + y;
	return z;
}

以上便是我们通过分析自定义的一个对两个整型相加的简单函数,接着我们通过主函数调用一下这个函数,代码如下:

#include <stdio.h>
int Add(int x, int y)
{
	int z = x + y;
	return z;
}

int main()
{
	int a = 10;
	int b = 22;
	int c = Add(a, b);
	printf("c = %d", c);
	return 0;
}

打印结果为:

在这里插入图片描述

可能有同学对 int c = Add(a, b);这一行代码不是很理解,我在这简单解释一下不做过多的展开,以后会深入讲解,

  • 在c语言中,我们调用自定义函数的方式是通过 函数名(参数列表)

  • 举个简单的例子,我们调用 Add 函数是通过 Add(参数列表)

  • 因为我们在自定义的时候为 Add 函数设置了两个整型变量,所以当我们用这个函数的时候,也需要用两个整型的变量当作参数进行传递,并且变量顺序和类型都要一致

  • 所以我们调用Add函数应该写 Add(a,b);a 和 b 就是我们传给 Add 函数的两个参数,Add 函数将 a 的值赋给了 x,将 b 的值赋给了 y,然后进行运算,

  • 接着将 z 作为返回值返回,

  • 此时 Add(a,b) 的值也就是 Add 这个函数的返回值了,也就是 z,所以此时 c 就是 a 和 b 的和

  • 至此 Add 函数完成了一个简单的两个整数相加的功能

如果此时我们还需要进行另外两个整数的相加,只需要再调用一次Add即可,这也就是函数很重要的作用之一::代码复用

九:数组

在生活中,我们有一张钞票,我们可以用手拿着,但是如果钞票多了,变成几十万了,我们该怎么存储呢?我们可以用钱包存储钱,此时钱包里存储就是钱的集合,同理,在c语言中,我们可以用一个 int 存储一个整型,那么我们该用什么类型来存储很多个整型呢?

答案是整型数组

那么什么是数组呢?,c 语言给出规定:数组是一组相同类型元素的集合,这一句话中强调了两个方面的因素

  1. 相同类型元素
  2. 集合

必须是一组类型相同的元素并且是集合,才能够叫做数组

9.1:数组的定义

int arr[10] = {1,2,3,4,5,6,7,8,9,10};

这是一个数组的简单定义方式,接下来我来解释一下这行代码的意思:

  • 一个变量去掉变量名剩下的就是这个变量所属的类型
  • 比如说 int a = 100,a 的类型就是 double b =1.25;b 的类型就是 double,
  • 同理可得,上述数组的类型是 int [10],如果数组同时去掉数组名和 [ x ] 的话,剩下的就是数组中所存储的元素
  • 其中 int 表示数组中元素的数据类型为整数,[ 10 ] 表示数组的长度为 10,即数组中可以存储 10 个整数元素,不能超过 10
  • 而我们对数组的初始化需要通过 { }
  • 元素与元素之间需要通过逗号隔开
  • 所以上述数组对数组中的 10 个元素分别赋值为1,2,3,4,5,6,7,8,9,10

9.2: 数组的下标

c 语言规定:数组的每个元素在数组中都有一个下标,并且下标是从 0 开始的(程序员从0开始数而不是1),并且数组中的元素可以通过下标来访问

int arr[10] = {0,0,0,0,0,0,0,0,0,0};

表示数组中有 10 个元素,所以下标的范围是 0-9(从0开始数 0,1,2,3,4,5,6,7,8,9一共有 10 个)

在这里插入图片描述

我们访问数组中的元素是通过 [ ] 来访问的,下面通过一个例子来说明 [ ] 的使用

例如:arr[3]

  • [ ]有两个操作数,一个是 arr,另一个是 3,
  • arr 代表要访问的数组
  • 而 3 代表访问数组中元素的下标
  • 所以 arr[3] 表示访问 arr 这个数组中下标为 3 的元素,
  • 也就是访问 arr 数组中第 4 个元素,千万不要误以为是第 3 个元素

数组的元素如果不初始化的话,默认值是 0

注意:括号中只能放常量,而不能放变量,在 c99 标准中引入了变长数组的概念,这时候允许数组的大小是变量,但是这种数组不能直接初始化,需要后面再对这个数组赋值

十: 操作符

10.1:算数操作符 + - * / %

加减与我们平常用的加减乘没什么区别,在这就不过多讲解了

10.1.1 :乘号操作符 *

乘号的用法与我们平时所用基本上没区别,只需要注意在 c 语言中乘号是 * 而不是 ×

10.1.2:除号操作符 /

/ 表示进行除法运算,但是不同于我们平时的除法,c 语言中的除法运算有两种,一是整数除法,二是浮点数除法

  1. 整数除法(向下取整):

7 / 2 = 3
8 / 2 = 4
9 / 2 = 4

  1. 小数除法:

7.0 / 2 = 3.5
7 / 2.0 = 3.5
7.0 / 2.0 = 3,5
8.0 / 2 = 4.0(4 和 4.0 的意义不一样,不要混淆)

在 C 语言中,存在整数除法和浮点除法两种不同的除法方式,是为了满足不同的需求和应用场景。

  • 整数除法(整数除以整数)是指将两个整数相除,且结果只保留整数部分,舍弃小数部分。
  • 浮点除法(两个数中有一个浮点数即可)是指将两个数相除,且结果将包含小数部分。

这两种除法方式的选择取决于需要的精度。如果只关心整数部分并希望结果是一个整数,那么可以使用整数除法。而如果需要精确到小数部分的结果,那么就需要使用浮点除法。

10.1.3: 取模操作符 %

取模操作符又叫做取余操作符,只能适用于整数(包括正整数和负整数),如果又小数则会报错, % 就是取出余数作为结果,

10 % 3 = 1
10 % 2 = 2
10 % 1 = 0

需要注意的是,如果对 n 进行取模,那么 n 的取值范围在 0 ~ (n-1),多利用好这个性质,对后面编程很有帮助

10.2: 赋值操作符

10.2.1: 赋值操作符 =

在C语言中,= 操作符用于将一个值赋给一个变量。它将右侧的表达式的结果赋值给左侧的变量。比如:

 int num = 10;   // 定义一个整数变量 num,并将值 10 赋给它
 int ret = num   //将 num 的值赋给 ret
  • 等号操作符的左侧必须是一个可修改的变量,而右侧可以是任何合法的表达式。
  • 等号操作符的赋值过程是从右到左的。也就是说,先计算右侧表达式的值,然后将结果赋给左侧的变量。
  • 等号操作符执行的是赋值操作,不是数学运算中的等于。
  • 在连续赋值语句中,等号操作符的结合性是从右到左的。例如,a = b = c将会先将c赋值给b,然后将b的值赋值给a。
  • 等号操作符的返回值是被赋值的变量的值。也就是说,赋值表达式本身的值是被赋值的变量的值。

对于最后一点可能有同学很难理解,这里举一个简单的代码例子示例说明


#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
int main()
{
	int a = 5;
	int b = (a = 10);  // 将 10 赋值给 a,赋值表达式的值是 10,然后将 10 赋值给 b
	printf("%d\n", a); // 输出 10
	printf("%d\n", b); // 输出 10
	return 0;
}

10.2.2 : += 操作符

int a = 10;
a = a +18;

这段代码很好理解,首先是定义了一个int 类型的变量 a 并将 10 赋值给了 a,接着第二行代码表示先将 a 加上 10,再将 a +10 这个值赋给 a,也等价于 a = 10+18 即 将 28 赋给 a,所以 a 等于 28

  • a = a +18 有一种缩写形式,即 a += 18;

在编程中,a = a + 18和a += 18是等价的,两者都会将变量a的值加上18,并将结果赋值给a。这两种写法的意义在于简化代码和提高可读性。

使用+=操作符可以使代码更加简洁和易于理解。它是一种常见的缩写形式,当你需要多次对一个变量进行累加操作,使用+=可以让代码变得更简洁,方便开发人员编写更简洁的代码。

所以你可以将 a += 18理解为,在 a 的基础上,再加上 18

10.2.2 : -= 操作符

同上,a = a -10 可以缩写成 a -= 10,可以理解为在 a 的基础上再减 10

10.2.2 : *= 操作符

同理,a = a * 9 可以缩写成 a *= 9,可以理解为在 a 的基础上,再乘上 9

10.2.2 : /= 操作符

同理, a = a / 6 可以缩写成 a /= 6,可以理解为在 a 的基础上除 6

10.3:单目操作符

10.3.1: !操作符

在c语言中,0 表示假,而非 0 的值表示真,举个例子:

  • 0是假
  • 1是真
  • 2是真
  • -1是真
  • 1.2是真
  • -1.2是真

而 ! 操作符表示逻辑反操作,就是真变成假,假变成真,

  • ! 0 是真
  • !1是假
  • !2是假
  • !(-1)是假
  • !1.2是假
  • !(-1.2)是假

这就是 !操作符的作用

10.3.2: &操作符

& 它用于获取一个变量的内存地址,这个地址表示了变量在计算机内存中存储的位置,而在 C 语言中,地址是用于标识内存中某个特定位置的值的位置。每个变量和每个数据在内存中都有唯一的地址。你可以将地址视为内存中的房间号,而变量和数据就是存放在这些房间中的内容。

比如说:

  1. int a = 5;
  2. double b = 1.25;
  3. char c = ‘w’;

在这里插入图片描述
对于每个变量来说,变量不仅有对应的值,还要唯一对应的空间,值属性和址属性是一个变量最基本的两个属性,可以将地址视为内存中的房间号,而变量和数据就是存放在这些房间中的内容。在此不过多深入

10.3.3: sizeof 操作符

需要注意的是 sizeof 是一个操作符,而不是函数,许多人误以为 sizeof 是一个函数,sizeof 操作符用于获取变量或数据类型的大小(以字节为单位),下面举一个简单的例子

#include <stdio.h>

int main()
{
	int a = 10;
	int c = sizeof (a);
	printf("a 占用的内存大小是(单位:字节) %d", c);
	return 0;
}

程序的运行结果如下:

在这里插入图片描述

这段代码中,首先定义了一个整型变量,接着通过 sizeof 操作符求出a的大小(以字节为单位),赋给了 c,接着打印 c 的值便可知道 a 所占的内存大小

sizeof 的基本用法就是将要求所占空间大小的变量或者类型放在括号内即可

int a = 18

如果要求 a 所占空间大小可以用 sizeof ,下面是求 a 所占空间的多种写法:

  1. sizeof(a)
  2. sizeof a
  3. sizeof (int)

注意! sizeof int 是错误的写法,要避免,著有当括号内是变量时,括号才能够省略

10.3.4: ++操作符

在C语言中,++操作符用于递增变量的值。每次递增 1 ,它有两种形式:前缀形式和后缀形式

  1. 前缀形式:++var 前缀形式会先将变量的值加1,然后返回递增后的值。例如:
#include <stdio.h>

int main()
{
	int x = 5;
	int y = ++x;// 现在x的值为6,y的值也为6
	printf("x = %d  y = %d", x, y);
	return 0;
}

运行结果如图所示:

在这里插入图片描述

我来解释一下这两条语句:

  • 首先我们定义了一个整型变量x,x的值为5

  • 在第二行代码中,++在x的前面,故称作前置++,前置++会先自增 1 ,再进行运算

  • 所以第行代码就等价与 int y = 6;所以x和y的值都为6

注意int y = ++x;与int y = x +1并不等价,int y = ++x;不仅改变了 y 的值,还改变了 x 的值,让 x 自增了 1 ,而 int y = x +1 只是改变了y的值,x 的值并未发生改变,请注意区分

  1. 后缀形式:var++ 后缀形式会先使用变量的当前值,然后再将其加 1。例如:
#include <stdio.h>

int main()
{
	int x = 5;
	int y = x++;// 现在x的值为6,y的值为5
	printf("x = %d  y = %d", x, y);
	return 0;
}

运行结果如图所示:

在这里插入图片描述

接着我来解释一下这两条语句:

  1. 首先我们定义了一个变量x,x的值为5
  2. 接着因为++是在x的后面,即x++,这是后置++,后置++会先进行运算,运算完后再自增 1
  3. 因为在int y = x++;中,此时 x 的值为5,我们先进行运算,再对 x 自增 1
  4. 所以此时 y 的值就是 5,运算完后,x 的值自增 1,x 变为了 6

10.3.5:- -操作符

与++操作符类似,- -操作符也有两种前置- -和后置- -两种形式

  1. 前缀形式:–var 前缀形式会先将变量的值减1,然后返回递减后的值。例如:
#include <stdio.h>

int main()
{
	int x = 5;
	int y = --x;// 现在x的值为4,y的值也为4
	printf("x = %d  y = %d", x, y);
	return 0;
}

运行结果如图所示
在这里插入图片描述
和++操作符类似,因为 - -在x的前面,是前置 - -,我们先自减 1 ,在进行运算

  1. 首先我们定义了一个变量 x,x 的值为 5
  2. 接着因为 - -在 x 的前面,是前置 - -,我们先自减 1,再进行运算
  3. int y = --x;因为此时x的值是 5,x 自减后变为了4,并将4 赋值给了y

所以最后 x 和 y 的值都是4

  1. 后缀形式:var- -,后缀形式会先使用变量的当前值,然后再将其减1。例如:
#include <stdio.h>

int main()
{
	int x = 5;
	int y = x--;// 现在x的值为4,y的值为5
	printf("x = %d  y = %d", x, y);
	return 0;
}

运行结果如图所示:

在这里插入图片描述

  • 首先我们定义了一个变量 x,x 的值为 5
  • 接着因为 - - 在 x 的后面,是后置 - -,先进行运算再自减1
  • int y = x–;,因为此时 x 的值为 5,所以 x 先进行运算,将5赋给了 y,接着 x 再自减一

所以此时 x 的值为 4,y 的值为 5

10.3.6: ( 类型)强制类型转换操作符

C语言中的强制类型转换操作符用于将一个数据类型转换为另一个数据类型。它的语法形式是:

  • (要转换的目标数据类型) 要转换的变量

请注意,强制类型转换可能会丢失精度或导致未定义的行为,因此在使用时需要谨慎。应该确保转换的结果是合理和可预期的。

( 类型 )操作符是 c 语言中的强制类型转换操作符,请不要将强制类型转换操作符与函数中的()混淆,比如 Add()

下面举一个简单的代码例子示例强制类型转换操作符的作用:

#include <stdio.h>

int main() {
    float num1 = 3.14;
    int num2 = 2;
    int result;
    result = (int)num1 + num2; // 强制将浮点数 num1 转换为整数
    printf("Result: %d\n", result);
    return 0;
}

在上面的代码中,我们定义了一个浮点数变量 num1,一个整数变量 num2,以及一个整数变量 result。我们将 num1 使用强制类型转换操作符 (int) 转换为整数,然后与 num2 相加赋值给 result。最后,我们打印出 result 的值。

输出结果将为 Result: 5,因为 3.14 被转换为整数 3,然后与整数 2 相加得到结果 5。

10.4:关系操作符

C 语言中的关系操作符用于比较两个值的大小关系,并返回一个布尔值(0或1)。以下是 C 语言中常用的关系操作符:

  • 相等(==):判断两个操作数是否相等,如果相等则返回 1,否则返回 0。
  • 不相等(!=):判断两个操作数是否不相等,如果不相等则返回 1,否则返回 0。
  • 大于(>):判断左操作数是否大于右操作数,如果是则返回 1,否则返回 0。
  • 小于(<):判断左操作数是否小于右操作数,如果是则返回 1,否则返回 0。
  • 大于等于(>=):判断左操作数是否大于等于右操作数,如果是则返回 1,否则返回 0。
  • 小于等于(<=):判断左操作数是否小于等于右操作数,如果是则返回 1,否则返回 0。

注意,在c语言中相等符号是 = =,而 = 是赋值符号,请不要混淆赋值符号和相等符号

10.4:逻辑操作符

C 语言中的逻辑操作符用于对布尔表达式进行运算,并返回一个布尔值(0或1)。以下是 C 语言中常用的逻辑操作符:

  • 逻辑与(&&):当两个操作数都为真(非零)时,返回真(1),否则返回假(0)。
  • 逻辑或(||):当两个操作数中至少有一个为真(非零)时,返回真(1),否则返回假(0)。
  • 逻辑非(!):对操作数的逻辑值取反,如果操作数为真(非零),返回假(0),否则返回真(1)。

逻辑操作符常用于条件判断和控制流程中。例如,在 if 语句中,可以使用逻辑与和逻辑非操作符组合对多个条件进行判断。

我们可以将逻辑与 && 理解成我们生活中的并且,将逻辑或 | | 理解成我们生活中的或者

  • &&只有当并且两边的条件都满足了,这个条件才被满足了
  • | | 只要有一边的条件满足了,这个条件就被满足了
  • 而 !就是对结果取反,真变成假,假变成真

10.5:条件操作符

在 C 语言中,条件操作符(也被称为三元运算符)被用于根据一个条件的结果来选择两个值中的一个。它的语法形式为:

condition ? value1 : value2

  • “condition” 是一个条件表达式,它的结果为真或者为假
  • 如果条件为真,不会计算 value2 的值,只计算 value1 的值,并且整个表达式的结果将为 “value1”
  • 如果条件为假,则不会计算 value1 的值,只计算 value2 的值结果为 “value2”。

我们可以通过条件操作符来实现一个简单的双分支,下面通过代码和画图的形式帮助读者理解:

#include <stdio.h>

int main() {
    int a = 88;
    int b = 100;
    int c = (a > b) ? a : b;
    printf("a和b中的较大值是%d",c);
    return 0;
}

运行结果如图所示:

在这里插入图片描述

流程图如图所示:

在这里插入图片描述

我们可以通过条件操作符实现一个双分支,通过这个特性我们可以很快求出 a 和 b 中的最大值,并赋给 c,最后打印出来

10.6: 逗号表达式

在 C 语言中,逗号表达式是一种特殊的表达式,它允许在一个语句中同时使用多个表达式,并以逗号分隔。逗号表达式的结果是最后一个表达式的值。

逗号表达式的语法如下:

(expr1, expr2, expr3, …, exprn)

  • 逗号表达式的执行过程是从左到右,依次计算每个表达式

  • 并且整个逗号表达式的结果为最后一个表达式的值。

下面举个简单的代码例子演示逗号表达式的使用:

#include <stdio.h>

int main() {
    int a = 10;
    int b = 18;
    int c = 22;
    int d = (a + b, b - c, c + a);
    printf("d的值是:%d", d);
    return 0;
}

这段代码是输出结果是:

在这里插入图片描述

int d = (a + b, b - c, c + a);在这段代码中,

  • 先计算 a + b 的值,接着计算 b + c 的值,接着计算 c + a 的值
  • 并将 c + a 的值作为逗号表达式整体的值,赋给 d

虽然这段代码中直接计算 c + a 也等于 32,那我们是不是遇见逗号表达式就直接计算最后一个式子就可以了呢?答案是否定的,接下来再看一个代码例子:

#include <stdio.h>

int main() {
    int x = 1, y = 2, z = 3;
    int result = (x++, y++, z++, x + y + z);
    
    printf("result的值是:%d\n", result);
    printf("x的值是:%d\n", x);
    printf("y的值是:%d\n", y);
    printf("z的值是:%d\n", z);
    
    return 0;
}

该代码会输出:

result 的值是:9
x 的值是:2
y 的值是:3
z 的值是:4

如果直接计算最后一个表达式,逗号表达式的结果应该是 6,这说明了逗号表达式的每个操作都被顺序执行,并且返回最后一个表达式的结果。所以在实际编程中,不能直接计算逗号表达式的最后一个结果作为逗号表达式的结果

十一: typedef重命名

在 C 语言中,typedef 关键字用于创建类型别名。它可以帮助我们简化复杂的类型声明,并增加代码的可读性。

typedef 的基本语法如下:

  • typedef 原类型 新类型名;

下面是一些 typedef 的使用示例:

#include <stdio.h>

int main() {
    typedef int Age;//创建自定义类型别名
    Age myAge = 25;
    printf("%d", sizeof(Age));
    return 0;
}

这段程序的输出结果是 4

  • 在这个示例中,我们对 int 类型进行重命名,创建了一个名为 Age 的类型别名,它代表了 int 类型。
  • 然后我们使用该别名来声明一个变量 myAge,并将其初始化为 25。
  • 接着我们通过 sizeof()来查看它的大小,很明显,Age 类型的大小和 int 类型的大小一样

十二: static 关键字

在 c 语言中,static 关键字有三种用法

  1. static 修饰局部变量
  2. static 修饰全局变量
  3. static 修饰函数

12.1: static 修饰局部变量

在 C 语言中,使用 static 关键字修饰局部变量具有下面两个作用:

  1. 延长局部变量的生命周期:通常情况下,局部变量在函数执行完毕后就会被销毁。但是,当我们使用 static 关键字修饰局部变量时,它的生命周期会变得更长,使得它在整个程序运行期间都有效。
  2. 保留变量的值:普通的局部变量每次函数执行时都会重新初始化,而使用 static 关键字修饰的局部变量在多次函数调用之间会保留其上一次的值。

下面是一个简单的示例代码,演示了 static 关键字修饰局部变量的作用:

#include <stdio.h>

void example() {
    static int count = 0; // 使用static关键字修饰局部变量
    int normalCount = 0; // 普通的局部变量

    count++;
    normalCount++;

    printf("Static Count: %d\n", count);
    printf("Normal Count: %d\n", normalCount);
}

int main() {
    example(); // 调用example函数
    example(); // 再次调用example函数
    example(); // 再次调用example函数

    return 0;
}

当我们运行上述代码时,输出将会是:

  • Static Count: 1 Normal Count: 1
  • Static Count: 2 Normal Count: 1
  • Static Count: 3 Normal Count: 1

每次调用 example 函数时,count 变量都会自增,并且保留上一次的值。而 normalCount 变量则会在每次函数调用时重新初始化为 0。

可以看到,使用 static 关键字修饰的 count 变量在多次函数调用之间保持了其值,而普通局部变量 normalCount 则每次函数调用时都重新初始化为 0。

  • static 修饰局部变量,本质上是将变量的存储空间由栈区转移到静态区
  • 栈区上变量的特点是:进入作用域创建,出作用域销毁(注意,销毁只是把空间还给了操作系统,不是让空间报废)
  • 静态区上变量的特点:创建好后,直到程序结束才销毁

注意,static 修饰局部变量只是改变了变量的生命周期,并没有改变变量的作用域,static 只是让变量出了作用域仍然存在,直到程序结束,生命周期才结束

12.2: static 修饰全局变量

基本格式是:

static int x;

当在 C 语言中使用 static 修饰全局变量时,它会改变该全局变量的链接属性,让全局变量的外部连接属性改成了内部连接属性,限制其作用域只在声明它的源文件内部可见,无法被其他源文件访问。

此时,该全局变量的作用类似于一个私有变量,只能在声明它的源文件内部使用。

通俗一点说,就是我们在文件 1 中定义的全局变量被 static 修饰后,那么在文件 2 中就无法使用这个全局变量,无法使用也就修改不了全局变量的值了

使用 static 修饰全局变量主要有以下几个作用:

  • 隐藏全局变量:通过将全局变量的作用域限制在单个源文件内部,可以避免与其他源文件中同名全局变量发生冲突。
  • 保护变量的访问:由于静态全局变量只能被声明的源文件内部访问,外部无法直接访问,可以增加全局变量的安全性,避免被其他部分非法修改。

12.2: static 修饰函数

基本格式是:

static void func() {
    // 函数体
}

和修饰全局变量类似,函数也具有外部链接属性,函数被 static 修饰之后,外部链接属性就变成了内部连接属性,使得函数只能在本文件中使用,从而隐藏函数,使其无法在其他源文件中访问。

通俗一点说,就是我们在文件 1 中定义的函数被 static 修饰后,那么在文件 2 中就无法使用这个函数了

12.3: extern 的用法

extern 用于声明函数或者变量,基本用法如下:

  • 对于变量的声明: extern 数据类型 变量名;
  • 对于函数的声明:extern 返回值类型 函数名(参数列表);

在 C 语言中,extern 关键字有以下两个主要用途:

  1. 声明外部全局变量:

extern 关键字可以用于在一个源文件中声明在其他源文件中定义的全局变量。这样,在当前源文件中就能够使用该全局变量,而不需要重新定义它。

举个例子,假设我们有两个源文件 file1.c 和 file2.c,其中 file1.c 定义了一个全局变量 int globalVar = 10;。在 file2.c 中可以使用 extern int globalVar; 声明,来引用 file1.c 中定义的全局变量 globalVar。

// file1.c
int globalVar = 10;

// file2.c
extern int globalVar;

int main() {
    printf("%d\n", globalVar); // 输出: 10
    return 0;
}

  1. 声明外部函数:

extern 关键字可以用于声明在其他源文件中定义的函数。这样,在当前源文件中就能够使用该函数,而不需要重新定义它。

举个例子,假设我们有两个源文件 file1.c 和 file2.c,其中 file1.c 定义了一个函数 void foo() { … }。在 file2.c 中可以使用 extern void foo(); 声明,来引用 file1.c 中定义的函数 foo()。

// file1.c
void foo() {
    printf("Hello from foo!\n");
}

// file2.c
extern void foo();

int main() {
    foo(); // 输出: Hello from foo!
    return 0;
}

需要注意的是,extern 关键字只能用于变量或函数的声明,而不能用于定义。extern 关键字的目的是告诉编译器该变量或函数是在其他地方定义的,并在当前文件中引用它。

12.4空间内存分布图

内存空间分布

在这里插入图片描述
在 C 语言中,程序运行时会使用三种不同的内存空间:栈区、堆区和静态区(全局区)。

  1. 栈区
  2. 堆区
  3. 静态区

栈区(Stack):

  • 栈区用于存储局部变量以及函数调用时的参数和返回地址。
  • 栈区的内存分配是自动进行的,变量的生命周期与其所在的函数或代码块的执行周期相对应。
  • 栈区的内存管理是由编译器自动完成的,无需手动分配或释放。
  • 栈区的内存空间有限,一旦超出限制,就会导致栈溢出错误。

堆区:

  • 堆区用于存储动态分配的内存,如使用 malloc、calloc、realloc 等函数分配的内存。
  • 堆区的内存分配是手动进行的,需要手动申请和释放内存。
  • 堆区的内存可以在程序的任意位置进行分配和释放。
  • 堆区的内存空间相对较大,但是需要开发人员自行管理,否则可能导致内存泄漏或者内存溢出等问题。

静态区(Static):

  • 静态区用于存储全局变量和静态变量。
  • 静态区的内存分配在程序运行之初就完成,直到程序结束才会释放。
  • 全局变量和静态变量在整个程序运行期间都存在,具有静态生命周期。
  • 静态区的内存管理由编译器负责,无需手动分配或释放。

了解即可,后面会详细展开

下面是计算机各大硬件的访问速度和价格比较:

在这里插入图片描述

13.1: #define 定义常量和宏

在C语言中,#define 是一个预处理指令,用于定义常量和宏。

  • define 定义标识符常量: #define MAX 1000

  • define 定义宏 #define ADD(x, y) ((x)+(y))

和标识符常量类似,宏也会被替换,比如

  • #define ADD(x, y) ((x)+(y))

ADD(x,y)就会被替换为 ((x)+(y)),注意 ADD(x, y) 是由 #define 定义的宏,而不是函数,请注意区分。

下面通过一个简单代码例子来说明宏的作用:

#define ADD(x, y) (x)+(y)
#include <stdio.h>

int main()
{
  int sum = ADD(2, 3);
  printf("sum = %d\n", sum);
 
  sum = 10*ADD(2, 3);
  printf("sum = %d\n", sum);
 
  return 0;
}

宏定义是使用 #define 关键字指定的,宏的名称是 ADD,宏的替换体是 (x)+(y)。这个宏定义表示在代码中使用 ADD(x, y) 时,会将其替换为 (x)+(y)。

在 main 函数中,有两个使用了宏的行。

  • int sum = ADD(2, 3);,

表示将宏 ADD 中的 x 被替换为 2,y 被替换为 3,得到的替换结果是 (2)+(3),这个结果为 5。然后将计算结果赋值给变量 sum。

  • sum = 10 * ADD(2, 3);,

表示将宏 ADD 中的 x 替换为 2,y 替换为 3,得到的替换结果是 (2)+(3)。

请注意! sum = 10 * ADD(2, 3); 中的 ADD(2,3)被替换后 式子变成了 10 *(2)+(3)这个结果等于23,请不要误以为是 2 和 3 相加后再乘 10,宏只是简单的进行替换,并不运算

当使用 ADD(x, y) 时,预处理器会将其替换为 (x)+(y)。在替换过程中,预处理器会将参数 x 和 y 直接替换到宏定义中的对应位置。这种替换是简单的文本替换,没有类型检查或计算

十三: 指针

  • 内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。
  • 所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是 1 个字节。
  • 为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。

一个小格为一个比特位,一个字节就是 8 个小格:

在这里插入图片描述

因为一个二进制位可以存放 0 和 1,有两种组合方式,一个字节有 8 位,所以一个字节可以有 28= 256 中组合方式

在这里插入图片描述

在每一个字节的起始位置处,都有一个地址,我们通过这个地址就可以找到这个空间在内存中的位置

注意,每个地址都是唯一的

13.1: 指针和地址的大小

目前计算机主要分为 32 位计算机和 64 位计算机两种,

  • 在 32 位计算机中,指针的大小为 4 字节,
  • 在 64 位计算机中,指针的大小为 8 字节

在计算机上,有地址线电线产生的高低电平信号,这些信号会转换成数字信号 0 和 1, 32 位机器顾名思义就是有 32 根地址线,而64位机器就是有 64 根地址线

  • 对于一根地址线,它有两种组合,即0和1
  • 对于两根地址线,它有4种组合,即00 01 10 11
  • 对于三根地址线,它有8种组合,即 000 001 010 011 100 101 110 111
  • ……

以此类推

  • 那么 32 根地址线,就有 232 种组合
  • 64 根地址线,就有 264 种组合

所以我们要想储存一个变量的地址,

  • 在 32 位机器种只需要 32 个二进制位
  • 在 64 位机器种只需要 64 个二进制位即可

而一个字节又等于 8 个比特位,所以

  • 在 32 位机器中,指针的大小为 4 字节(指针是用于存放地址的变量)
  • 在 64 位机器中,指针的大小为 8 字节

注意,指针和地址是两个不同的概念,指针是用于存放地址的变量,而地址是变量中的很重要的一个属性

13.2: 指针的使用

那么我们该如何通过指针来存放一个变量的地址呢?

#include <stdio.h>
int main()
{
	int num = 10;
	int* p = &num;
	*p = 20;
	printf("%d", num);
	return 0;
}

运行结果如图所示:

在这里插入图片描述

接下来我将通过画图的形式讲解

10 的二进制是1010,如果补齐 64 位的话,就是

  • 00000000 00000000 00000000 00000000
  • 00000000 00000000 00000000 00001010

这就是 10 在内存中存储的数据

在这里插入图片描述

如图所示,我们定义了一个数值为 10 的空间,接着我们通过取地址符 &,将 num 的地址取出来,注意,取地址取出来的是开头处的地址,然后把地址赋给了专门存放地址的变量,指针 int *p,此时 p 就存放了 num 开头的地址了

那么此时我们就有了 num 的地址,并将地址存放在指针变量 p 中,我们该如何通过 p 来找到 num 呢?

这时又另外一种操作符 :解引用操作符(这个解引用操作符和 p 上的 * 意义不同,不要混淆)

解引用操作符就是取地址操作符的逆运算:

  • &num 就是通过 num 找到 num 的地址
  • 而*p 则是通过 num 的地址,找到 num(此时p存放的是num的地址)

我们说过,一个变量它有值属性和址属性两种属性

那么,这两个属性有什么关联吗?答案是有的,我们可以通过值属性找到址属性,也可以通过址属性找到值属性

在这里插入图片描述

那么我们再来解释一下上面的代码:

在这里插入图片描述

首先我们定义了一个 int 类型的 num 变量,并赋值为 10,接着将 num 的地址取出来,并将地址赋给了指针 p,接着我们通过解引用操作符 * 来找到 num 的值,并将 20 赋给它,所以 num 的值也就被改成了 20

十四: 结构体

在生活中,基本数据类型可以描述绝大多数的物体,比如说名字,身高,体重,但是还有一部分物体还不足够被描述,比如说我们该如何完整的描述一本书呢?一本书包括书的名字,价格,作者。页数,等等,由此便有了结构体这个概念

C 语言中的结构体是一种自定义的数据类型,它允许我们将不同类型的数据组合在一起,形成一个逻辑上相关的数据单元。结构体可以包含不同的数据类型,如整型、字符型、浮点型、指针等,并且可以根据需要添加多个成员变量。

结构体的定义使用关键字 struct,后面跟着结构体的名称和花括号。在花括号中定义结构体的成员变量,每个成员变量由数据类型和名称组成,中间用分号分隔。

例如,下面是一个定义了一个简单的学生结构体的例子:

struct Student {
    int id;
    char name[20];
    float score;
};

其中 Student 是结构体的名字,int id; char name[20]; float score;是结构体的成员

14.1: 结构体的赋值

那么我们该怎么对结构体赋值呢?

struct Stu s = {"张三"20"男""20180101"};

这样顺序赋值即可,后面会深入讲解结构体,目前了解这种方式即可

14.1: 结构体成员的访问

结构体成员的访问有两种方式:

  • 第一是通过 . 操作符
  • 第二是通过 -> 操作符

下面是代码示例:

#include <stdio.h>

struct Stu
{
	char name[20];
	int age;
	char sex[5]; 
	char id[15];
};

int main()
{
	struct Stu s = { "张三", 20, "男", "20180101" };
	printf("name = %s age = %d sex = %s id = %s\n", s.name, s.age, s.sex, s.id);

	struct Stu* ps = &s;
	printf("name = %s age = %d sex = %s id = %s\n", ps->name, ps->age, ps->sex, ps -> id);

	return 0;
}

运行结果为:

在这里插入图片描述

本章完,后续会陆续更新 c 语言的知识,如果本文中出现错误,欢迎指出!!

  • 46
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
13万C语言保姆教程是一本非常详尽和全面的学习C语言的教材。C语言是一种广泛应用于嵌入式系统和计算机编程的高级编程语言,掌握C语言对于计算机专业人士至关重要。 这本教程通过系统地介绍C语言的基础知识和高级特性,帮助读者从零开始学习,逐步提升编程能力。教程内容包括但不限于以下几个方面: 1. C语言基础:介绍C语言的概念、语法规则、数据类型、运算符、控制语句等基本知识,为后续学习打下坚实基础。 2. C语言进阶:深入讲解C语言的指针、内存管理、函数指针、文件操作等高级特性,帮助读者理解更复杂的程序设计和优化技巧。 3. 实例演示:通过大量的实例演示,展示C语言在实际应用中的强大能力。从简单的计算器、学生成绩管理系统,到复杂的网络编程和多线程程序,读者将学会如何用C语言解决各种实际问题。 4. 常见问题解答:针对读者在学习过程中可能遇到的问题,提供详细的解答和思路,帮助读者充分理解和消除疑虑。 该教程的13万涵盖了C语言的方方面面,适合初学者和有一定编程基础的读者。通过系统学习这本教程,读者可以掌握C语言的基本概念、语法规则和常用编程技巧,为其今后从事计算机编程相关的工作打下坚实的基础。无论是准备进入计算机专业领域,还是对编程有兴趣者,都可以从这本教程中受益匪浅。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ice___Cpu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值