C语言学习之路(黑马)

文章目录

环境搭建

HelloWorld

代码编写

Hello_World.c

c 复制代码
#include<stdio.h>

int main() 
{
	printf("Hello World!");
	return 0;
}

代码分析

  1. '#include' : 引⼊头⽂件专⽤关键字。提前找到需要用到的东西。

  2. <> : ⽤来包裹 库头⽂件名

  3. stdio.h : 使⽤的头⽂件。因为程序中使⽤了 printf() 函数。就必须使⽤该头⽂件。

std:标准:standard

i: input 输⼊。

o: output 输出。

  1. int :main 函数返回值为整型。

  2. main: 整个程序的⼊⼝函数。 任何.c 程序,有且只有⼀个 main 函数。

  3. printf(); C语⾔向屏幕输出字符使⽤的函数。

  4. printf("helloworld\n")

    printf();向屏幕输出一段内容

    helloworld: 待写出的字符串内容。

    \n: 回⻋换⾏。

  5. return 0;

    return 返回。 C程序要求,main 函数要有返回值。借助 return 实现返回。

    0:成功!因为 int ,返回整数。

注意事项

  • 程序中使⽤的所有的字符,全部是 "英⽂半⻆" 字符。
  • 程序中,严格区分⼤⼩写。
  • ";" 代表⼀⾏结束。不能使⽤ 中⽂ ";",必须是英⽂。

执行流程

完成的C语言运行,分为以下4步,在VS中我们直接运行,其实是把中间的步骤给省略了

  • 预处理(这一步后面单独讲解)

    简单理解,就是先找到#include后面的 <stdio.h>这个文件

  • 编译

    把c文件编译成二进制文件后缀名为obj

  • 连接/链接

    把预处理找到的h文件,还有编译之后产生的obj文件打包在一起,产生exe文件

  • 运行

    运行exe文件

核心语法

注释

单行注释

c 复制代码
// 这是单行注释文字

多行注释

c 复制代码
/*
这是多行注释文字
这是多行注释文字
这是多行注释文字
*/
注意:多行注释不能嵌套使用。

注释示例

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

// 主入口
int main() 
{
	// 输出语句
	printf("Hello World!\n");
	/*
		return:程序结束了
		0 程序是正常结束
		这里的0,需要跟上面的int呼应起来
	*/
	return 0;
}

关键字

关键字:在C语言中被赋予了特定含义的英文单词,一共有32个关键字

c 复制代码
auto  break  case  char  const  continue  default  do  double else  enum 
extern float  for  goto  if  int  long  register   return  short  signed
sizeof  static struct  switch  typedef union  unsigned  void  volatile  while
  • 关键字全部小写
  • 在特定的编译器中,关键字是高亮显示的
    vs:蓝色或者紫色
    vs:蓝色

常量

程序运行的过程中,其值永远不会发生改变的数据

实型常量的小细节:

  • 小数点前后,如果只有0,可以省略不写

    c 复制代码
    0.93 可以写成 .93
    18.0 可以写成 18.
  • 科学计数法是实型常量,但是要写E

    c 复制代码
    12340000 可以写成 1.2340000E7
    但是写成 1.234 * 10的7次方就错误了,因为这是一个计算过程,不是最终的数字

输出常量

利用printf集合占位符的方式可以输出常量

格式:

​ printf(参数1,参数2);

​ 参数1:输出内容的最终样式,以字符串的形式体现 (必填)

​ 参数2:填补的内容 (选填)

占位符:

占位符 说明
整形 %d
实型 %f
字符 %c
字符串 %s
  • 输出一个整数

    c 复制代码
    #include <stdio.h>
    int main()
    {
        printf("%d", 10);
        return 0;
    }
  • 输出一个小数

    c 复制代码
    #include <stdio.h>
    int main()
    {
        printf("%f", 1.93);
        return 0;
    }
  • 输出一个字符

    c 复制代码
    #include <stdio.h>
    int main()
    {
        printf("%c", 'A');
        return 0;
    }
  • 输出一个字符串

    c 复制代码
    #include <stdio.h>
    int main()
    {
        // 第一种方式:
        printf("Hello World!");
        // 第二种方式:
        printf("我的名字为:%s","尼古拉斯·纯情·暖男·天真·阿玮");
        return 0;
    }

输出并换行

操作系统 换行符
windows \r\n
mac \r
Linux \n

平时写代码的时候,想要换行直接写\n即可,C语言会根据不同的操作系统解析成对应的换行符

变量

  • 变量的三要素

    1. 变量名:用来在程序中使用。
    2. 变量类型:开辟内存空间大小。
    3. 变量值:存储的实际数据

变量定义:

c 复制代码
类型名 变量名 = 变量值(一般都这么写)

举例:

c 复制代码
int m = 57;

会开辟内存空间给变量。变量声明不会开辟内存空间。

代码演示:

c 复制代码
#include <stdio.h>

int main()
{
	// 1. 定义格式:
    // 数据类型 变量名;
    int a;
    // 2. 赋值/修改值
    // 变量名 = 数据值;
    a = 10;
    
    // 3. 如果定义的时候已经知道了变量中要存储什么样的数据
    // 数据类型 变量名 = 数据值;
    int b = 20;
    return 0;
}

变量的注意事项

  • 只能存一个值
  • 变量名不允许重复定义
  • 一条语句可以定义多个变量
  • 变量在使用之前一定要进行赋值
  • 变量的作用域范围

计算机进制

  • 代码书写

  • 二进制:由0和1组成,代码中以0b开头

  • 十进制:由0~9组成,前面不加任何前缀

  • 八进制:由0~7组成,代码中以0开头

  • 十六进制:由0~9还有 a ~ f 组成,代码中以0x开头

  • 任意进制转十进制

    c 复制代码
    公式: 系数*基数的权次幂 相加
  • 二进制转十进制

  • 十进制转其他进制

  • 总结

数据类型

数据类型可以决定变量中能存储什么类型的数据, 决定存储空间的大小

  • sizeof 运算符

    语法 1:sizeof(变量名)

    c 复制代码
    int a = 10;
    printf("%zu\n", sizeof(a));//sizeof(a) 获取 a 变量占用内存大小(字节)。可以用 printf 显示出来

    语法 2:sizeof(类型名)

    c 复制代码
    printf("%zu\n", sizeof(double)); // 也可以使用 sizeof 直接查看某种类型占用的内存大小
  • 整型

    数据类型 字节数 格式符 数据范围 最小值宏 最大值宏
    short(短整型) 2 %hd -2^15^ ~ 2^15^-1 (-32768 ~ 32767) SHRT_MIN SHRT_MAX
    int(整型) 4 %d -2^31^ ~ 2^31^-1 (-2147483648 ~ 2147483647) INT_MIN INT_MAX
    long(长整型) 4 %ld -2^31^ ~ 2^31^-1 (-2147483648 ~ 2147483647) LONG_MIN LONG_MAX
    long long(长长整型) 8 %lld -2^63^ ~ 2^63^-1 LLONG_MIN LLONG_MAX
    unsigned short(无符号 短整型) 同 short %hu 0 ~ 2^16^-1 (0 ~ 65535) 0 USHRT_MAX
    unsigned int(无符号 整型) 同 int %u 0 ~ 2^32^-1 (0 ~ 4294967295) 0 UINT_MAX
    unsigned long(无符号 长整型) 同 long %lu 0 ~ 2^32^-1 (0 ~ 4294967295) 0 ULONG_MAX
    unsigned long long(无符号 长长整型) 同 long long %llu 0 ~ 2^64^-1 0 ULLONG_MAX

    上表中列出的占用字节数和取值范围,是大多数情况下各种类型的取值。

    由于,C 标准没有具体规定以上各类数据所占用的字节数。因此,在不同系统、编译器下,数据类型占用的字节数会有所不同。

    比如:int 类型,在 Turbo C 环境占 2 字节,取值范围与 short 相同。 而在 Visual C 环境下是 4 字节。

    再如:long 类型,相同的 gcc 编译器下,在 Windows 系统中占 4 字节,而在 Linux 系统中占 8 字节。

    可以使用 sizeof 查看 数据类型 占用内存的大小。

    可以引入头文件 #include <limits.h> 借助宏来查看 数据类型 在当前平台上 对应的最小、最大值。

    c 复制代码
    // 预处理,程序运行之前,需要提前做的事情
    #include<stdio.h>
    
    // 主入口
    int main() 
    {
    	short a = 10;
    	printf("%hd\n", a);
    	printf("%zu\n", sizeof(a));
     
    	int b = 10;
    	printf("%d\n", b);
    	printf("%zu\n", sizeof(b));
    
    	long c = 10L;
    	printf("%ld\n", c);
    	printf("%zu\n", sizeof(c));
    
    	long long d = 10LL;
    	printf("%lld\n", d);
    	printf("%zu\n", sizeof(d));
    
    	return 0;
    }
  • 实型

    实型表示有符号的十进制小数,在计算机内部以浮点方式表示(小数点是浮动的),因此也叫浮点型。

    常见实型有两种: float (单精度)、 double (双精度)

    实型数据没有八、十六进制,也没有 unsigned 无符号形式。在计算机底层采用的是近似计算,实现比较复杂,且不同平台处理方式不同。我们这里只学习它基本的知识。

    小数的取值范围比整数的要大

    C语言中的小数默认double类型的

    小数不可以和unsigned组合,unsigend只能和整数类型组合。

    c 复制代码
    // 预处理,程序运行之前,需要提前做的事情
    #include<stdio.h>
    
    // 主入口
    int main() 
    {
    	//float:单精度小数 (精确度小数点后6位)windows占4个字节 (38位)
    	float a = 3.14F;
    	printf("%f\n", a);//3.140000
    	printf("%.2f\n", a);//3.14
    	printf("%zu\n", sizeof(a));//4
    
    	//double:双精度小数 (精确度小数点后15位)windows占8个字节 (308位)
    	double b = 1.23456789;
    	printf("%lf\n", b); //1.234568
    	printf("%.2lf\n", b); //1.23
    	printf("%.8lf\n", b); //1.23456789
    	printf("%zu\n", sizeof(b));//8
    
    	//long double 高精度小数 (精确到小数点后18~19位)windows占8个字节(其他12,16)
    	long double c = 3.1415926L;
    	printf("%lf\n",c);//3.141593
    	printf("%.2lf\n",c);//3.14
    	printf("%.7lf\n",c);//3.1415926
    	printf("%zu\n", sizeof(c));//8
    
    	return 0;
    }
  • 字符

    char 取值范围:ASCII码表中的字母,数字,英文符号

    c 复制代码
    // 预处理,程序运行之前,需要提前做的事情
    #include<stdio.h>
    
    // 主入口
    int main() 
    {
    	//char 取值范围:ASCII码表中的字母,数字,英文符号
    	char c1 = 'a';
    	printf("%c\n", c1);
    	//如果用 %d,就显示 其 ASCII 值了。
    	printf("%d\n", c1);//97
    	printf("%zu\n", sizeof(c1));//1
    
    	char c2 = '1';
    	printf("%c\n", c2);
    	printf("%zu\n", sizeof(c2));//1
    
    	char c3 = 'A';
    	printf("%c\n", c3);
    
    	char c4 = '.';
    	printf("%c\n", c4);
    
    	return 0;
    }
  • 字符串

    c 复制代码
    // 预处理,程序运行之前,需要提前做的事情
    #include<stdio.h>
    
    // 主入口
    int main() 
    {
    	printf("请输入一段文字:");
    	/*
    		字符串变量的定义方式:
    			数据类型 变量名[大小] = "字符串";
    			char str[内存占用大小] = "abc";
    		内存占用大小的计算方式:
    			英文:1个字母、符号、数字占用一个字节
    			中文:默认情况下一个中文占两个字节
    			结束标记:1个字节
    	*/
    	
    	char str[4];
    	scanf("%s", &str);
    	printf("%s", str);
    	return 0;
    }
  • 隐式转换


  • 强制转换

标识符

标识符:代码中所有我们自己起的名字。比如变量名、函数名等。

标识符的硬性要求:

以数字、字母、下划线组成

不能以数字开头

不能是关键字

区分大小写

标识符的软性建议:

​ 用英文单词,见名知意

​ 变量名:全部小写

​ 文件名:全部小写,单词之间用下划线隔开

键盘录入

scanf ,是scanner format的缩写,是c语言提供的一个函数。可以结合 格式符 读取各种类型数据。

c 复制代码
//在 CRT 中关闭这些函数的弃用警告
#define _CRT_SECURE_NO_WARNINGS
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

// 主入口
int main() 
{
	int a;
	scanf("%d", &a);
	printf("%d\n", a);

	return 0;
}
c 复制代码
	printf("请输入三个整数,用逗号间隔:");
	int a, b, c;   // 可以不赋初值。
	scanf("%d,%d,%d", &a, &b, &c);
	int d = a + b + c;
	printf("%d", d);

注意事项

  1. 不要在 scanf 的参 1 中,添加类似 printf() 的提示字符串和 \n 换行符。

  2. 键入数据时,数据个数、类型、顺序,必须与参 1 中占位符一一对应。

  3. 键入数据时,数据间的分割符,必须与 参 1 中 占位符的分割符一致。

  4. scanf 的返回值,代表格式符成功匹配数据的次数。(较少使用)

  5. VS2019 以后的版本编译时,会将 scanf 划为 "不安全函数",爆出C4996 错误,推荐你使用 s_scanf() 函数。

    但,学习、练习、测试时,直接使用 scanf 很方便,可暂时屏蔽该错误。

    • 方法 1: 在项目中设置:工程名→右键→属性→C/C++→预处理器→预处理器定义→编辑→将 _CRT_SECURE_NO_WARNINGS 加入"预处理定义" 中
    • 方法 2: 在每个.c文件开头(第一行)添加宏:#define _CRT_SECURE_NO_WARNINGS

运算符

算术运算符

  1. 先 * / %,后 + -。
  2. 除法
    • 两整数相除,结果整数(舍小数部分)
    • 两浮点数相除,结果 double 浮点数
    • 整数和实数相除,自动类型转换为实型
    • 不允许 除 0(与数学一样)
  3. 取模(取余)
    • 运算数必须是整型
    • 对负数取余,结果为余数的绝对值
    • 不允许 除 0(与数学一样)
  4. 小数直接参与计算,结果有可能是不精确的

比较运算符

注意: 判断 "相等",使用 "==", 不能使用 "="(赋值符)

优先级:

  1. 整体,比算术运算符 低。
  2. 整体,比赋值运算符 高。
  3. > >= < <= 高于 == !=

赋值运算符

包括:基本赋值运算符 和 复合赋值运算符( = 与算数运算符 组合而成)。

作用:给变量赋值。 结合性:自右向左

**注意:** 赋值运算符,会修改变量的原始值。 赋值符左侧,必须可修改(变量)。

自增减运算符

注意:++ 和 -- 如果 不是单独使用(如:用在表达式中),前缀和后缀 差异巨大

  • 放在变量前,先 +1、-1 再取值使用。

    c 复制代码
    int a = 10;
    int res = ++a;  // 先 +1,再取 a 值给 res。 (先加再用)
  • 放在变量后,先 取值用,再 +1、-1

    c 复制代码
    int b = 10;
    int res2 = b--; // 先取 b 值给 res2, 而后 b 再 -1  (先用再减)

    注意事项:

    1. 不能用于常量
    2. 不能用于表达式
    3. 优先级:整体高于算数运算符(必然高于比较、赋值);后缀高于前缀
    4. 不要在一个表达式中,对同一变量 多次 ++、-- 运算。可读性差,且不用编译系统结果不同。

逻辑运算符

三元运算符

优先级:

高于 赋值运算符,低于 算数、逻辑、比较运算符。

逗号运算符

运算符的优先级

流程控制语句

顺序结构

顺序结构是程序默认的执行流程
顺序结构是从上往下依次运行的

分支结构

if语句

c 复制代码
	if (关系表达式)
	{
		语句体A;
	}
	else if(关系表达式) 
	{
		语句体B;
	}
	else
	{
		语句体C;
	}

switch语句

把所有的选择一一列举出来,根据不同的条件任选其一。

c 复制代码
	switch (表达式){
		case 值1:
			语句体1;
			break;
		case 值2:
			语句体2;
			break;
		......
		default:
			语句体n;
			break;
	}


循环结构

for循环

c 复制代码
	/*
		for(初始化语句; 条件判断语句; 条件控制语句)
		{
			循环体语句;
		}
	*/
	//打印3遍Hello
	for (int i = 0; i < 3; i++)
	{
		printf("Hello\n");
	}

while语句

c 复制代码
	/*
		初始化语句
		while(条件判断语句)
		{
			循环体语句
			条件控制语句
		}
	*/
	//打印3遍Hello
	int i = 0;
	while (i<3)
	{
		printf("Hello\n");
		i++;
	}

do...while语句

循环高级

  • 无限循环

  • 跳转控制语句

    • break:不能单独书写,只能写在switch,或者循环中,表示结束、跳出的意思。
    • continue:只能写在循环中,表示跳过本次循环,继续下次循环
  • 循环嵌套

goto

可以跳转到任意地方。但一般只用于跳出循环。

c 复制代码
	//外循环
	for (int i = 1; i <= 3; i++) {
		//内循环
		for (int j = 1; j <= 3; j++) {
			printf("打印内循环\n");
			break;//只能跳出内循环
		}
		printf("内循环结束!!!\n");
	}
c 复制代码
	//外循环
	for (int i = 1; i <= 3; i++) {
		//内循环
		for (int j = 1; j <= 3; j++) {
			printf("打印内循环\n");
			//break;//只能跳出内循环
			goto a;
		}
		printf("内循环结束!!!\n");
	}

	//标号
	a:printf("外循环结束---");

函数

  • 函数:函数就是程序中独立的功能。

函数的定义格式

c 复制代码
	返回值类型 函数名(形参1, 形参2...) 
	{
		函数体;
		return 返回值;
	}

函数的定义调用

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

//定义一个函数
void test()
{
	printf("hello\n");
}

// 主入口
int main()
{
	//调用test()函数
	test();

	return 0;
}
c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

//定义一个求和函数
int sum(int num1, int num2) {
	return num1 + num2;
}

// 主入口
int main()
{
	//调用sum()函数
	int count = sum(2, 3);
	printf("%d\n", count);
	return 0;
}
c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

int sum(int num1, int num2);

// 主入口
int main()
{
	//调用sum()函数
	int count = sum(2, 3);
	printf("%d\n", count);
	return 0;
}


//定义一个求和函数
int sum(int num1, int num2) {
	return num1 + num2;
}

函数的注意事项

C语言中常见的函数

c 复制代码
https://zh.cppreference.com/
  • math.h

    c 复制代码
    // 预处理,程序运行之前,需要提前做的事情
    #include<stdio.h>
    #include<math.h>
    
    // 主入口
    int main()
    {
    
    	/*
    		math.h
    		pow()	幂
    		sqrt()	平方根
    		ceil()	向上取整
    		floor()	向下取整
    		abs()	绝对值
    	*/
    
    	double res1 = pow(2, 3);
    	printf("%lf\n", res1);//8.000000
    
    	double res2 = sqrt(9);
    	printf("%lf\n", res2);//3.000000
    
    	double res3 = ceil(9.1);
    	printf("%lf\n", res3);//10.000000
    
    	double res4 = floor(12.7);
    	printf("%lf\n", res4);//12.000000
    
    	int res5 = abs(-5);
    	printf("%d\n", res5);//5
    
    	return 0;
    }
  • time.h

    c 复制代码
    // 预处理,程序运行之前,需要提前做的事情
    #include<stdio.h>
    #include<time.h>
    
    // 主入口
    int main()
    {
    
    	/*
    		time.h
    		time()	获取当前时间戳
    		形参:表示获取的当前时间是否需要在其他地方进行存储,
    			  一般来说不需要在其他地方进行存储。NULL
    		返回值:long long
    	*/
    
    	long long res = time(NULL);
    	printf("%lld\n", res);
    
    	return 0;
    }

随机数


c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>
#include<stdlib.h>


// 主入口
int main()
{
	//设置种子
	srand(1);
	//获取随机数
	int num = rand();
	//输出打印
	printf("%d\n", num);

	return 0;
}
c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

// 主入口
int main()
{
	/*
		随机数的弊端:
			1.种子不变,随机数结果是固定的,种子默认是1
			2.随机数的范围:默认0~32767

			目标:任意范围之内取一个随机数
			[1-100]  [7-23]  [8-49]
			1.范围包头不包尾,包左不包右,左闭右开  1-101  7-24  8-50
			2.拿着尾巴 - 开头  101-1=100  24-7=17  50-8=42
			3.修改代码 
	*/
	//用一个变化的数据充当种子
	srand(time(NULL));
	//获取随机数
	int num1 = rand()%100 + 1; // 0~99 +1   >   1~100
	int num2 = rand()%17 + 7; // 0~16 +7   >   7~23
	int num2 = rand()%42 + 8; // 0~42 +8  >   8~50
	//输出打印
	printf("%d\n", num1);
	printf("%d\n", num2);

	return 0;
}

数组

数组是一个容器,可以用来存储同种数据类型的多个值。

数组的定义

c 复制代码
数据类型 数组名[长度];
	int arr[3];
  • 连续的空间
  • 一旦定义,长度不可变

数组的初始化

初始化:定义数组的时候,第一次给数组赋值。

c 复制代码
数据类型 数组名[长度] = {数据值,数据值...}
c 复制代码
//长度省略:数据值的个数就是数组长度
int arr1[] = {1,2,3};
c 复制代码
//长度未省略:数据值的个数<=长度
//未赋值的部分有默认值
//整数:0   小数:0.0  字符:'\0'  字符串:NULL
int arr2[5] = {1,2,3};

元素访问

c 复制代码
	int arr1[] = {1,2,3};
	//数据类型 变量名 = 数组名[索引]
	int a = arr1[1];
	printf("%d\n",a);//2

数组遍历

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

// 主入口
int main()
{

	int arr[] = {1,2,3};
	int len = 3;//数组的长度
	for(int i=0; i<len; i++)
	{
		printf("%d\n", arr[i]);
	}

	return 0;
}

内存中的数组

内存:软件在运行时,用来临时存储数据的。

内存地址:内存中申请空间的编号,用来快速管理内存空间。

64位操作系统:以64位二进制表示,书写时转成十六进制。

  • 变量的内存地址

    c 复制代码
    	int a = 10;
    	printf("%p\n", &a);//000000F9C27AF9E4
  • 数组的内存地址

    c 复制代码
    	int arr[3] = { 1,2,3 };
    	printf("%p\n", &arr);//数组首地址:000000C1E88FFB18
    	printf("%p\n", &arr[0]);//数组第1个元素地址:000000C1E88FFB18
    	printf("%p\n", &arr[1]);//数组第2个元素地址:000000C1E88FFB1C
    	printf("%p\n", &arr[2]);//数组第3个元素地址:000000C1E88FFB20

数组的常见问题

  • 数组作为函数的形参

    实际上传递的是数组的首地址,如果要在函数中对数组进行遍历的话,要把数组的长度传递过去。

    定义处:arr表示完整的数组。

    函数中的arr:只是一个变量,用来记录数组的首地址。

    c 复制代码
    // 预处理,程序运行之前,需要提前做的事情
    #include<stdio.h>
    void printArr(int arr[],int len);
    
    // 主入口
    int main()
    {
    	int arr[3] = { 1,2,3 };
    	printf("%p\n", &arr);//000000178F9EFB48
    
    	int len = sizeof(arr) / sizeof(int);
    	printf("%p\n", &len);//000000178F9EFB74
    
    	printArr(arr,len);
    
    	return 0;
    }
    
    void printArr(int arr[],int len)
    {
    	printf("%p\n", &len);//000000178F9EFB28
    	printf("%zu\n", sizeof(len));//4
    
    	printf("%p\n", arr);//000000178F9EFB48   在函数里面打印arr地址时,不需要&
    	printf("%zu\n", sizeof(arr));//8  8个字节,这是内存地址值的大小
    
    	for (int i = 0; i < len; i++)
    	{
    		printf("%d\n", arr[i]);
    	}
    }
  • 数组的索引越界

    最小索引:0

    最大索引:长度-1

数组查找算法

基本查找/顺序查找

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

int order(int arr[], int len, int num);

// 主入口
int main()
{

	/*
		数组的基本查找
		核心思路:从0索引开始依次往后找,如果找到了则返回对应索引,如果没找到就返回-1
	*/

	//1.定义数组
	int arr[] = { 11,22,55,77,44 };
	int len = sizeof(arr) / sizeof(int);

	//2.定义一个要查找的数据
	int num = 55;

	//3.调用函数查找数据
	int index = order(arr, len, num);
	//4.输出索引
	printf("%d\n", index);//2


}


int order(int arr[], int len, int num)
{
	for (int i = 0; i < len; i++)
	{
		if (arr[i] == num) {
			return i;
		}
	}
	return -1;
}

二分查找/折半查找

前提条件:数组中的数据必须是有序的。如果是乱的,先排序再查找的话没意义,索引变了,只能确定是否存在。

核心逻辑:每次排除一半的查找范围。

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

int binarySearch(int arr[], int len, int num);

// 主入口
int main()
{

	//1.定义数组
	int arr[] = { 7,23,79,81,103,127,131,147 };
	int len = sizeof(arr) / sizeof(int);

	//2.定义一个要查找的数据
	int num = 131;

	//3.调用函数查找数据
	int index = binarySearch(arr, len, num);

	//4.输出索引
	printf("%d\n", index);//2

}

int binarySearch(int arr[], int len, int num)
{
	int min = 0;
	int max = len - 1;
	int mid;

	while (min <= max) {
		//确定中间位置
		mid = (max + min) / 2;
		if (arr[mid] < num) {
			//要查找的数据在当前mid右边
			min = mid + 1;
		}
		else if (arr[mid] > num) {			
			//要查找的数据在当前mid左边
			max = mid - 1;
		}
		else {
			return mid;
		}
	}

	//如果min>max,表示数据不存在,返回-1
	return -1;
}

插值查找

分块查找

数组特点:无序中透露着有序。


哈希查找

树表查找

斐波那契查找

数组排序算法

冒泡排序

相邻的数据两两比较,小的放前面,大的放后面。

c 复制代码
	//1.定义数组
	int arr[] = { 7,5,6,3,1,2,4 };
	int len = sizeof(arr) / sizeof(int);

	//2.排序
	//内循环:找到本次循环的最大值,再把最大值放到了最右边,即上一次最大值的左边
	//外循环:把上面这个动作重复了len-1次
	for (int i = 0; i < len - 1; i++)
	{
		for (int j = 0; j < len - 1 - i; j++)
		{
			//比较相邻元素,小的在前面,大的在后面
			if (arr[j] > arr[j + 1]) {
				int temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
	}

	//3.输出
	for (int i = 0; i < len; i++)
	{
		printf("%d\n", arr[i]);
	}

选择排序

从0索引开始,拿着当前索引上的元素跟后面每一个元素依次比较,小的放前面,大的放后面。

c 复制代码
	//1.定义数组
	int arr[] = { 7,5,6,3,1,2,4 };
	int len = sizeof(arr) / sizeof(int);

	//2.排序,小的在前面,大的在后面
	for (int i = 0; i < len - 1; i++)
	{
		//i表示数组中的每一个索引
		for (int j = i+1; j < len; j++)
		{
			//j表示i索引后的每一个索引
			if (arr[i] > arr[j]) {
				int temp = arr[j];
				arr[j] = arr[i];
				arr[i] = temp;
			}
		}
	}

	//3.输出
	for (int i = 0; i < len; i++)
	{
		printf("%d\n", arr[i]);
	}

指针

指针其实是一个指针变量,里面存放的是内存地址。

指针定义格式

指针的作用


c 复制代码
// 主入口
int main()
{
	//利用指针去获取变量中的数据 / 修改数据

	//定义一个变量
	int a = 10;

	//定义一个指针指向变量a
	int* p = &a;

	//利用指针获取变量中的数据
	printf("%d\n", *p);//10

	//利用指针修改变量中的数据
	*p = 200;

	//利用指针获取变量中的数据
	printf("%d\n", *p);//200

	//直接输出变量a
	printf("%d\n", a);//200
}

操作其他函数中的变量

如果不使用指针,那么交换的只是传递过去的参数的值。

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

void swap(int num1, int num2);

// 主入口
int main()
{
	//定义两个变量,要求交换变量中记录的值
	//注意:交换代码卸载一个新的函数swap中
	int a = 10;
	int b = 20;

	printf("调用前:%d,%d\n", a, b);

	swap(a, b);

	printf("调用后:%d,%d\n", a, b);
	return 0;
}

void swap(int num1, int num2)
{
	//仅仅交换的是num1和num2的值
	int temp = num1;
	num1 = num2;
	num2 = temp;
}
c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

void swap(int* p1, int* p2);

// 主入口
int main()
{
	//定义两个变量,要求交换变量中记录的值
	//注意:交换代码卸载一个新的函数swap中
	int a = 10;
	int b = 20;

	printf("调用前:%d,%d\n", a, b);

	swap(&a, &b);

	printf("调用后:%d,%d\n", a, b);
	return 0;
}

void swap(int* p1, int* p2)
{
	//此时交换的是p1指针和p2指针指向的变量的值
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}


函数返回多个值

如果用数组返回的话,你怎么区分最大最小值呢

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

void getMaxAndMin(int arr[], int len, int* max, int* min);

// 主入口
int main()
{
	//函数返回多个值
	//定义一个函数,求数组的最大值和最小值,并进行返回

	//1.定义一个数组
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int len = sizeof(arr) / sizeof(int);

	//2.调用getMaxAndMin函数求最大值和最小值
	int max = 0;
	int min = 0;

	printf("调用前:%d,%d\n", max, min);

	getMaxAndMin(arr, len, &max, &min);

	printf("调用后:%d,%d\n", max, min);
	return 0;
}

void getMaxAndMin(int arr[], int len, int* max, int* min)
{
	*max = arr[0];
	for (int i = 1; i < len; i++)
	{
		if(arr[i] > *max)
		{
			*max = arr[i];
		}
	}

	*min = arr[0];
	for (int i = 1; i < len; i++)
	{
		if (arr[i] < *min)
		{
			*min = arr[i];
		}
	}
}

函数的结果和计算状态分开

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

int getRemainder(int num1, int num2, int* res);

// 主入口
int main()
{
	//函数结果和计算状态分开
	//定义一个函数,将两数相除,获取余数

	//1.定义变量
	int a = 10;
	int b = 3;
	//结果
	int res = 0;

	//2.调用函数获取余数
	int flag = getRemainder(a, b, &res);

	if (!flag) {
		printf("余数:%d\n", res);
	}

	return 0;
}

//返回值:表示计算状态 0正常 1不正常
int getRemainder(int num1,int num2,int* res)
{
	if (num2 == 0)
	{
		//停止
		return 1;
	}
	*res = num1 % num2;
	return 0;
}

方便的操作数组和函数

指针高级

指针的使用细节


指针高级

指针计算

c 复制代码
	int a = 10;
	int* p = &a;
	printf("%p\n", p);  //000000911D0FFB84
	printf("%p\n", p+1);//000000911D0FFB88
	printf("%p\n", p-1);//000000911D0FFB80
c 复制代码
	//数组,内存空间是连续的
	int arr[] = { 1,2,3,4,5,6,7,8,9 };

	//获取0索引的内存地址
	int* p1 = &arr[0];

	printf("%d\n", *p1);
	printf("%d\n", *p1+1);


	//获取5索引的内存地址
	int* p2 = &arr[5];

	//间隔步长
	printf("%p\n", p2);//000000C03ED5F86C
	printf("%p\n", p1);//000000C03ED5F858  相差20个字节
	printf("%d\n", p2-p1);//步长5

指向不明的指针

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

int* method();

// 主入口
int main()
{
	int a = 10;
	int* p1 = &a;
	printf("%p\n", p1);//00000013746FF524
	printf("%d\n", *p1);//10

	//p2 野指针:指针指向的空间未分配
	int* p2 = p1 + 10;
	printf("%p\n", p2);//00000013746FF54C
	printf("%d\n", *p2);//其他数字

	//p3 悬空指针:指针指向的空间已分配,但是被释放了
	int* p3 = method();
	printf("%p\n", p3);//000000986ED8F514
	printf("%d\n", *p3);//其他数字

	return 0;
}

int* method()
{
	int num = 10;
	int* p = &num;
	return p;
}

没有类型的指针

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

void swap(void* p1, void* p2, int len);

// 主入口
int main()
{
	int a = 10;
	short b = 20;

	int* p1 = &a;
	printf("%p\n", p1);//000000899D6FF804
	printf("%d\n", *p1);//10

	short* p2 = &b;
	printf("%p\n", p2);//000000899D6FF824
	printf("%d\n", *p2);//20

	//不同类型的指针之间,是不能互相赋值的
	char* p3 = (char*)p1;//warning C4047: "=":"short *"与"int"的间接级别不同
	printf("%p\n", p3);//000000899D6FF804
	printf("%d\n", *p3);//10

	//void类型的指针打破了上面的观念
	//void没有任何类型
	//好处:可以接收任意类型指针记录的内存地址
	//缺点:无法获取变量里面的数据,也不能进行加减计算
	void* p4 = p1;

	//那有什么用呢?
	//可以把之前的swap优化成交换任意两个类型的
	//让函数更具有通用性
	int c = 10;
	int d = 20;
	swap(&c, &d, sizeof(c));
	printf("%d\n", c);//20
	printf("%d\n", d);//10
	return 0;
}

void swap(void* p1, void* p2, int len)
{
	//把void类型的指针,转换成char类型的指针
	char* pc1 = p1;
	char* pc2 = p2;

	char temp = 0;

	//以字节为单位,一个字节一个字节进行交换
	for (int i = 0; i < len; i++)
	{
		temp = *pc1;
		*pc1 = *pc2;
		*pc2 = temp;

		pc1++;
		pc2++;
	}

}

二级指针和多级指针

作用:二级指针可以操作一级指针记录的地址。

c 复制代码
	//定义变量
	int x = 10;
	printf("%p\n", &x);//000000D5AF5AF6C4 变量x的内存地址
	int y = 20;
	printf("%p\n", &y);//000000D5AF5AF6E4 变量y的内存地址 
	//定义一级指针
	int* p = &x;
	printf("%p\n", p);//000000D5AF5AF6C4 指针p指向变量x的内存地址
	printf("%d\n", *p);//10  指针p指向变量x  解引用出来是10
	printf("%p\n", &p);//000000D5AF5AF708 指针变量p的内存地址
	//定义二级指针
	int** pp = &p;
	printf("%p\n", pp);//000000D5AF5AF708 二级指针pp指向指针p的内存地址
	printf("%p\n", *pp);//000000D5AF5AF6C4 二级指针pp指向指针p 解引用出来是x的内存地址
	printf("%p\n", &pp);//000000D5AF5AF728 二级指针变量pp的内存地址

	//利用二级指针修改一级指针里面记录的内存地址
	//二级指针pp指向指针p的内存地址
	//指针p那块空间存的其实是变量x的内存地址
	//现在把指针p那块空间存的内容改为变量y的内存地址
	*pp = &y;

	//输出打印
	printf("%p\n", p);//000000D5AF5AF6E4 
	printf("%d\n", *p);//20 
	printf("%d\n", **pp);//20 

数组和指针

c 复制代码
	//利用指针遍历数组
	//定义数组
	int arr[] = { 10,20,30,40,50 };
	int len = sizeof(arr) / sizeof(int);

	//获取数组的指针
	//实际上获取的是数组的首地址
	int* p1 = arr;
	int* p2 = &arr[0];
	printf("%p\n", p1);//000000992BF0FAF8 
	printf("%p\n", p2);//000000992BF0FAF8 

	for (int i = 0; i < len; i++)
	{
		//printf("%d\n", *(p1 + i));
		printf("%d\n", *p1);
		p1++;
	}
c 复制代码
	//定义数组
	int arr[] = { 10,20,30,40,50 };

	//sizeof运算的时候,不会退化,arr还是整体
	printf("%zu\n", sizeof(arr));//20 个字节
	
	//arr参与计算的时候,会退化为第一个元素的指针,记录的是内存地址的第一个元素首地址,也是数组的首地址,步长:数据类型 int 4 字节
	// &arr获取地址的时候,不会退化,记录的内存地址是第一个元素的首地址,步长:数据类型 * 数组的长度 20 个字节

	printf("%p\n", arr);//00000043205FFA88
	printf("%p\n", &arr);//00000043205FFA88

	printf("%p\n", arr+1);//00000043205FFA8C
	printf("%p\n", &arr+1);//00000043205FFA9C



c 复制代码
	//定义二维数组
	int arr[3][2] = 
	{
		{1,2},
		{3,4},
		{5,6} 
	};

	//利用索引遍历
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 2; j++)
		{
			printf("%d ", arr[i][j]);
		}			
		printf("\n");
	}
c 复制代码
	//定义二维数组
	int arr1[3] = { 1,2,3 };
	int arr2[4] = { 2,5,4,9 };
	int arr3[5] = { 5,6,8,9,4 };

	int* arr[3] = { arr1, arr2, arr3 };

	//利用索引遍历
	for (int i = 0; i < 3; i++)
	{
		//行不通,arr[i]参与计算时会退化为指向第一个元素的指针
		//int len = sizeof(arr[i]) / sizeof(int);
		for (int j = 0; j < len; j++)
		{
			printf("%d ", arr[i][j]);
		}			
		printf("\n");
	}
c 复制代码
	//定义二维数组
	int arr1[3] = { 1,2,3 };
	printf("%zu\n", sizeof(arr1));//12
	int arr2[4] = { 2,5,4,9 };
	int arr3[5] = { 5,6,8,9,4 };

	int* arr[3] = { arr1, arr2, arr3 };
	//在数组指针中,arr1使用数组名进行计算时,退化为指向第一个元素的指针,此时不在表示数组那个整体了
	//指针 --- 内存地址  64位win 8个字节
	printf("%zu\n", sizeof(arr[0]));//8

	int len1 = sizeof(arr1) / sizeof(int);
	int len2 = sizeof(arr2) / sizeof(int);
	int len3 = sizeof(arr3) / sizeof(int);

	int* lenArr[3] = {len1, len2, len3};

	//利用索引遍历
	for (int i = 0; i < 3; i++)
	{
		//行不通,arr[i]参与计算时会退化为指向第一个元素的指针
		//int len = sizeof(arr[i]) / sizeof(int);

		for (int j = 0; j < lenArr[i]; j++)
		{
			printf("%d ", arr[i][j]);
		}			
		printf("\n");
	}
c 复制代码
	//定义二维数组
	int arr[3][2] =
	{
		{1,2},
		{3,4},
		{5,6}
	};

	// 数组内部元素的类型
	// 数据类型 * 指针名 = arr
	// int[2] * p
	// 书写习惯:int * p[2]
	// 怕误解为定义了一个数组,所以加括号 int(*p) [2]
	int(*p)[2] = arr;

	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 2; j++)
		{
			//遍历一维数组
			printf("%d ", *(*p + j));
		}
		printf("\n");
		//移动二维数组的指针
		p++;
	}
c 复制代码
	//定义三个一维数组
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 11,22,33,44,55 };
	int arr3[5] = { 11,222,333,444,555 };

	//把三个一维数组的内存地址放入二维数组中
	int* arr[3] = { arr1,arr2,arr3 };

	//获取指针
	int** p = arr;

	//遍历数组
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", *(*p + j));
		}
		//换行
		printf("\n");
		//移动指针
		p++;
	}

函数和指针

c 复制代码
void method1();
int method2(int num1, int num2);

// 主入口
int main()
{
	//定义指针指向两个函数
	void (*p1)() = method1;
	int (*p2)(int, int) = method2;

	p1();
	int num = p2(2,3);
	printf("%d\n",num);

	return 0;
}


void method1()
{
	printf("method1\n");
}
int method2(int num1, int num2)
{
	printf("method2\n");
	return num1 + num2;
}
c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

int add(int num1, int num2);
int sub(int num1, int num2);
int mutiply(int num1, int num2);
int divide(int num1, int num2);

// 主入口
int main()
{
	/*
		定义加、减、乘、除四个函数
		用户键盘录入三个数字
		前两个表示参与计算的数字
		第三个表示调用的函数
		1:加法
		2:减法
		3:乘法
		4:除法
		细节:只有形参完全相同而且返回值相同的函数,才能放到同一个函数指针数组中
	*/

	//定义一个数组去装四个函数的指针
	//函数指针数组
	int (*arr[4])(int, int) = {add, sub, mutiply, divide};

	//让用户键盘录入三个数据
	printf("请录入两个数字参与计算:");
	int num1;
	int num2;
	scanf_s("%d%d", &num1, &num2);

	printf("函数方法:\n");
	printf("1:加法\n2:减法\n3:乘法\n4:除法\n");
	int choose;
	printf("请选择要调用的函数方法:");
	scanf_s("%d", &choose);

	//根据用户的选择,来调用不同的函数
	int res = (arr[choose - 1])(num1, num2);

	printf("计算结果:%d\n", res);

	return 0;
}

int add(int num1, int num2) 
{
	return num1 + num2;
}
int sub(int num1, int num2) 
{
	return num1 - num2;
}
int mutiply(int num1, int num2) 
{
	return num1 * num2;
}
int divide(int num1, int num2)
{
	return num1 / num2;
}

字符串

定义字符串

c 复制代码
	/*
		两种定义字符串的方式
	*/
	
	//1.利用字符数组 + 双引号的方式定义字符串
	char str1[10] = "asdfghjkl";
	printf("%s\n", str1);//asdfghjkl
	str1[0] = 'w';
	printf("%s\n", str1);//wsdfghjkl
	//细节1:在底层实际存储的时候,c语言还是会帮我们把字符串转换成字符数组进行保存,并且还会在最后加上'\0'
	//细节2:数组的长度要么不写,写的话要把结束标记的空间预留出来
	//细节3:字符数组 + 双引号的方式定义字符串,内容可以发生改变

	//2.利用指针 + 双引号的方式定义字符串
	char* str2 = "zxcvbnm";
	printf("%s\n", str2);
	//细节1:在底层实际存储的时候,c语言还是会帮我们把字符串转换成字符数组进行保存,并且还会在最后加上'\0'
	//细节2:会把底层的字符数组放在只读常量区(不可修改、可以复用)
	
	//str2[0] = 'p';//错误代码为 -1073741819
	//printf("%s\n", str2);

	char* str3 = "zxcvbnm";
	printf("%p\n", str2);//00007FF67936AC10
	printf("%p\n", str3);//00007FF67936AC10

遍历字符串

c 复制代码
	/*
		1.键盘录入一个字符串
		底层逻辑:
			程序在运行时,首先会创建一个长度为100的字符数组str
			在键盘录入时,会把每一个字符存入str数组中,并加上结束标记\0
			在这个过程中,需要修改字符数组的内容,所以第一种方式可以,第二种方式不可以
	*/
	char str[100];
	printf("请输入字符串:");
	scanf("%s", str);

	printf("输入的字符串为:%s\n", str);

	//遍历字符串得到每一个字符
	char* p = str;
	while (1)
	{
		//利用指针获取字符串的每一个字符,直到遇到\0为止
		char a = *p;
		if (a == '\0') break;
		printf("%c\n", a);
		p++;
	}

遍历字符串数组

c 复制代码
	/*
		定义一个数组存储5个学生名字并遍历
	*/
	char nameArr[5][20] = 
	{
		"zhangsan",
		"lisi",
		"wangwu",
		"zhaoliu",
		"tianqi",
	};


	for (int i = 0; i < 5; i++)
	{
		char* str = nameArr[i];
		printf("%s\n", str);
	}


	//第二种方式
	//把五个字符串的指针放入一个数组中
	char* strArr[5] = 
	{
		"zhangsan",
		"lisi",
		"wangwu",
		"zhaoliu",
		"tianqi",
	};

	for (int i = 0; i < 5; i++)
	{
		char* str = strArr[i];
		printf("%s\n", str);
	}

字符串常用函数

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>
#include<string.h>


// 主入口
int main()
{
	char* str1 = "abc";//只读常量区,不可修改
	char str2[100] = "Abc";
	char str3[5] = { 'q','w','e','r','\0' };

	printf("----------strlen(长度)----------\n");
	//细节1:strlen这个函数在统计长度时不计算结束标记
	//细节2:在windows中,默认一个中文占两个字节
	//int len1 = strlen(str1);//3
	//int len2 = strlen(str2);//3
	//int len3 = strlen(str3);//4
	//printf("%d\n", len1);
	//printf("%d\n", len2);
	//printf("%d\n", len3);

	printf("----------strcat(拼接)----------\n");
	//细节1:把第二个字符串拷贝到第一个字符串的末尾
	//前提:第一个字符串可修改,第一个字符串剩余空间足够
	//strcat(str2,str3);
	//printf("%s\n", str2);//abcqwer
	//printf("%s\n", str3);//qwer


	printf("----------strcpy(拷贝)----------\n");
	//细节1:把第二个字符串拷贝到第一个字符串,把原有内容覆盖
	//前提:第一个字符串可修改,第一个字符串空间足够
	//strcpy(str2, str3);
	//printf("%s\n", str2);//qwer
	//printf("%s\n", str3);//qwer


	printf("----------strcmp(比较)----------\n");
	// 完全一样:0
	// 只要有一个不一样:非0
	// 细节:顺序不一致也是不一样,空格也是不一样
	//int res = strcmp(str2, str3);
	//printf("%d\n", res);//-1

	printf("----------strlwr(变小写)----------\n");
	//细节:不能转换中文
	_strlwr(str2);
	printf("%s\n", str2);	
	
	printf("----------strupr(变大写)----------\n");
	//细节:不能转换中文
	_strupr(str2);
	printf("%s\n", str2);

	return 0;
}

结构体

  • 结构体可以理解位自定义的数据类型
  • 它是由一批数据组合而成的结构型数据
  • 里面的每一个数据都是结构体的成员

结构体格式

定义结构体

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>
#include<string.h>

struct Girl
{
	char name[20];
	int age;
	char gender;
	double height;
};

// 主入口
int main()
{
	/*
		结构体:
			自定义的数据类型
			就是由很多数据组合成的一个整体
			每一个数据,都是结构体的成员
		定义的位置:
			函数里面:局部位置,只能在本函数中使用
			函数外面:全局位置,在所有函数中都可以使用
	*/

	//使用结构体
	//定义一个女孩变量
	struct Girl lucy;
	//lucy.name = "露丝";//字符串赋值
	strcpy(lucy.name, "露丝");
	lucy.age = 23;
	lucy.gender = 'X';
	lucy.height = 1.63;

	printf("名字:%s\n", lucy.name);
	printf("年龄:%d\n", lucy.age);
	printf("性别:%c\n", lucy.gender);
	printf("身高:%.2f\n", lucy.height);

	return 0;
}

结构体数组

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

struct Student
{
	char name[20];
	int age;
};

// 主入口
int main()
{
	//定义三个学生并进行赋值
	struct Student stu1 = { "zhangsan",23 };
	struct Student stu2 = { "lisi",24 };
	struct Student stu3 = { "wangwu",25 };

	//把三个学生放入数组
	struct Student stuArr[] = { stu1,stu2,stu3 };

	//遍历数组得到每一个元素
	for (int i = 0; i < 3; i++)
	{
		struct Student temp = stuArr[i];
		printf("学生的信息为:姓名:%s\t年龄:%d\n", temp.name, temp.age);
	}

	return 0;
}

结构体别名

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

//Student大名可省略
typedef struct Student
{
	char name[20];
	int age;
} STU;

// 主入口
int main()
{

	STU stu = { "zhangsan",23 };

	printf("学生的信息为:姓名:%s\t年龄:%d\n", stu.name, stu.age);

	return 0;
}

结构体作为函数参数

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

//Student大名可省略
typedef struct Student
{
	char name[20];
	int age;
} STU;

//函数要写在结构体下面
void method(STU st);

// 主入口
int main()
{
	/*
		定义一个函数,修改学生信息	
	*/
	STU stu = { "zhangsan",23 };

	printf("学生的初始信息为:姓名:%s\t年龄:%d\n", stu.name, stu.age);

	method(stu);

	printf("学生的信息修改为:姓名:%s\t年龄:%d\n", stu.name, stu.age);


	return 0;
}


//void method(struct Student stu) 起别名前
/*
	细节:如果函数中写的是结构体类型的变量,相当于定义了一个新变量
	此时时把main函数中stu的数据,传递给了method的变量st
	在函数中仅仅修改了变量st中的值,对于main函数中stu的值没有修改
*/
void method(STU st)
{
	printf("接收到的学生的初始信息为:姓名:%s\t年龄:%d\n", st.name, st.age);

	printf("请输入要修改的学生姓名:");
	scanf("%s", st.name);
	printf("请输入要修改的学生年龄:");
	scanf("%d", &(st.age));

	printf("在函数中修改后的学生的初始信息为:姓名:%s\t年龄:%d\n", st.name, st.age);

}
c 复制代码
学生的初始信息为:姓名:zhangsan        年龄:23
接收到的学生的初始信息为:姓名:zhangsan        年龄:23
请输入要修改的学生姓名:lisi
请输入要修改的学生年龄:25
在函数中修改后的学生的初始信息为:姓名:lisi    年龄:25
学生的信息修改为:姓名:zhangsan        年龄:23
c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

//Student大名可省略
typedef struct Student
{
	char name[20];
	int age;
} STU;

//函数要写在结构体下面
STU method(STU st);

// 主入口
int main()
{
	/*
		定义一个函数,修改学生信息	
	*/
	STU stu = { "zhangsan",23 };

	printf("学生的初始信息为:姓名:%s\t年龄:%d\n", stu.name, stu.age);

	//接收函数返回值并赋值给stu
	stu = method(stu);

	printf("学生的信息修改为:姓名:%s\t年龄:%d\n", stu.name, stu.age);


	return 0;
}



STU method(STU st)
{
	printf("接收到的学生的初始信息为:姓名:%s\t年龄:%d\n", st.name, st.age);

	printf("请输入要修改的学生姓名:");
	scanf("%s", st.name);
	printf("请输入要修改的学生年龄:");
	scanf("%d", &(st.age));

	printf("在函数中修改后的学生的初始信息为:姓名:%s\t年龄:%d\n", st.name, st.age);

	return st;
}
c 复制代码
学生的初始信息为:姓名:zhangsan        年龄:23
接收到的学生的初始信息为:姓名:zhangsan        年龄:23
请输入要修改的学生姓名:lisi
请输入要修改的学生年龄:26
在函数中修改后的学生的初始信息为:姓名:lisi    年龄:26
学生的信息修改为:姓名:lisi    年龄:26
c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

//Student大名可省略
typedef struct Student
{
	char name[20];
	int age;
} STU;

//函数要写在结构体下面
void method(STU* p);

// 主入口
int main()
{
	/*
		定义一个函数,修改学生信息	
	*/
	STU stu = { "zhangsan",23 };

	printf("学生的初始信息为:姓名:%s\t年龄:%d\n", stu.name, stu.age);
	
	//传递内存地址
	method(&stu);

	printf("学生的信息修改为:姓名:%s\t年龄:%d\n", stu.name, stu.age);

	return 0;
}


//如果要在函数中修改stu的值,此时就不要再定义新的变量了
//直接接收stu的内存地址
//指针p里卖弄记录的是main函数中stu的内存地址
void method(STU* p)
{
	printf("接收到的学生的初始信息为:姓名:%s\t年龄:%d\n", (*p).name, (*p).age);

	printf("请输入要修改的学生姓名:");
	scanf("%s", (*p).name);
	printf("请输入要修改的学生年龄:");
	scanf("%d", &((*p).age));

	printf("在函数中修改后的学生的初始信息为:姓名:%s\t年龄:%d\n", (*p).name, (*p).age);

}
c 复制代码
学生的初始信息为:姓名:zhangsan        年龄:23
接收到的学生的初始信息为:姓名:zhangsan        年龄:23
请输入要修改的学生姓名:lisi
请输入要修改的学生年龄:25
在函数中修改后的学生的初始信息为:姓名:lisi    年龄:25
学生的信息修改为:姓名:lisi    年龄:25

结构体嵌套

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>
#include<string.h>

typedef struct Message
{
	char phone[13];
	char mail[100];
} MESS;

//Student大名可省略
typedef struct Student
{
	char name[20];
	int age;
	MESS mess;
} STU;

// 主入口
int main()
{

	STU stu;
	strcpy(stu.name, "zhangsan");
	stu.age = 23;
	strcpy(stu.mess.phone, "13572321981");
	strcpy(stu.mess.mail, "13572321981@qq.com");

	printf("学生的初始信息为:姓名:%s\t年龄:%d\t手机号:%s\t邮箱:%s\t\n", stu.name, stu.age, stu.mess.phone, stu.mess.mail);
	
	strcpy(stu.name, "lisi");
	stu.age = 26;
	strcpy(stu.mess.phone, "199");
	strcpy(stu.mess.mail, "199@qq.com");
	printf("学生的信息修改为:姓名:%s\t年龄:%d\t手机号:%s\t邮箱:%s\t\n", stu.name, stu.age, stu.mess.phone, stu.mess.mail);

	//批量赋值
	/*
		STU stuu;
		stuu = { "lisi",25,{"15596330708","123@qq.com"} };//不可以在已经定义的结构体中批量赋值
	*/
	STU stu2 = { "lisi",25,{"15596330708","123@qq.com"} };//在对其声明的时候才可以批量赋值
	printf("批量赋值的信息为:姓名:%s\t年龄:%d\t手机号:%s\t邮箱:%s\t\n", stu2.name, stu2.age, stu2.mess.phone, stu2.mess.mail);

	return 0;
}

内存对齐

想当然是如下图,但其实不是。。。

实际上

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

struct Num1
{
	double a;	//0 1 2 3 4 5 6 7
	char b;		//8 
				//补空白字节 9 10 11
	int c;		//12 13 14 15
	char d;		//16
				//补空白字节 17 18 19 20 21 22 23 24 (补至最大字节8的倍数)
};
struct Num2
{
	double a;	//0 1 2 3 4 5 6 7
	char b;		//8
	char d;		//9
				//补空白字节 10 11
	int c;		//12 13 14 15
				//补空白字节 16 (补至最大字节8的倍数)
};

// 主入口
int main()
{
	/*
		内存对齐:
			不管是结构体,还是普通的变量都存在内存对齐
		规则:
			只能放在自己类型整数倍的内存地址上
		简单理解:
			内存地址 % 占用字节 = 0
		举例:
			int存放的位置,内存地址一定能被4整除

		结构体的内存对齐:
			结构体在上面的基础上又多了一点,结构体的大小是最大类型的整数倍
			用来确定最后一个数据补位的情况
		切记:对齐的时候会补空白字节,但是不会改变原本字节的大小
		心得:把小的数据类型写在最上面,大的数据类型写在下面

	*/
	int x = 10;//4个字节
	printf("%d\n", &x);
	struct Num1 n1;
	printf("%zu\n", sizeof(n1));//24
	struct Num2 n2;
	printf("%zu\n", sizeof(n2));//16

	return 0;
}

共用体

共用体的使用

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>
#include<string.h>

//也可以起别名
union MoneyType
{
	int moneyi;
	double moneyd;
	char moneystr[100];
};

// 主入口
int main()
{
	//需求:金融项目中,钱有可能是整数、小数、字符串
	//利用共同体定义钱的变量
	union MoneyType money;

	//赋值
	//整数moeneyi 小数moneyd 字符串moneystr
	//而且每次只能赋一个值
	//money.moneyi = 100;
	//money.moneyd = 100.32;
	strcpy(money.moneystr,"100万");

	//printf("%d\n", money.moneyi);
	//printf("%lf\n", money.moneyd);
	printf("%s\n", money.moneystr);

	return 0;
}

共用体的特点

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>
#include<string.h>

//也可以起别名
union MoneyType
{
	int moneyi;			//4
	double moneyd;		//8
	char moneystr[100];	//100个1
};

// 主入口
int main()
{
	//需求:金融项目中,钱有可能是整数、小数、字符串
	//利用共同体定义钱的变量
	union MoneyType money;

	//所有的变量都使用同一个内存空间
	printf("%p\n", &(money.moneyi));	//000000DF8397F4D0
	printf("%p\n", &(money.moneyd));	//000000DF8397F4D0
	printf("%p\n", &(money.moneystr));	//000000DF8397F4D0

	//所占的内存大小 = 最大成员的长度的整数倍
	printf("%zu\n", sizeof(money));	//104 
	printf("%zu\n", sizeof(money.moneyi));	//4
	printf("%zu\n", sizeof(money.moneyd));	//8
	printf("%zu\n", sizeof(money.moneystr));	//100

	//第二次复制会覆盖原有的数据
	money.moneyi = 99;
	printf("%d\n", money.moneyi);//99
	money.moneyd = 1.32;
	printf("%d\n", money.moneyi);//1374389535
	printf("%.2lf\n", money.moneyd);//1.32

	return 0;
}

结构体和共用体的区别

  • 结构体:一种事务中包含多个属性
  • 共用体:一个属性有多种类型

动态内存分配

常用函数

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>
#include<stdlib.h>

// 主入口
int main()
{
	/*
		<stdlib.h>
		malloc	申请空间(连续)
		calloc	申请空间 + 数据初始化
		realloc	修改空间大小
		free	释放空间
	*/

	//1.利用malloc函数申请一片连续的空间
	//存储100个int类型的整数
	//返回这片空间的首地址
	int* p = malloc(sizeof(int) * 100);

	printf("%p\n", p);

	//赋值
	for (int i = 0; i < 100; i++)
	{
		//第一种赋值
		//*(p + i) = i + 1;
		//第二种赋值 底层 p[i] ---> p+i
		p[i] = i + 1;
	}

	//遍历
	for (int i = 0; i < 100; i++)
	{
		printf("%d\n",*(p + i));
	}

	return 0;
}
c 复制代码
	//2. calloc	申请空间 + 数据初始化 
	//int* p = malloc( 100, sizeof(int) );
c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>
#include<stdlib.h>



// 主入口
int main()
{
	/*
		<stdlib.h>
		malloc	申请空间(连续)
		calloc	申请空间 + 数据初始化 
		realloc	修改空间大小
		free	释放空间
	*/

	//1.利用malloc函数申请一片连续的空间
	//存储10个int类型的整数
	//返回这片空间的首地址
	int* p = malloc(sizeof(int) * 10);

	printf("%p\n", p);//0000024CA963D0A0

	//赋值
	for (int i = 0; i < 10; i++)
	{
		//第一种赋值
		//*(p + i) = i + 1;
		//第二种赋值 底层 p[i] ---> p+i
		p[i] = i + 1;
	}

	//遍历
	for (int i = 0; i < 10; i++)
	{
		//printf("%d\n",*(p + i));
		printf("%d\n", p[i]);
	}

	//3. 扩容 把原来的拷贝到新的空间
	int* p2 = realloc(p, sizeof(int) * 20);
	
	printf("%p\n", p2);//0000024CA9635CF0

	//追加赋值
	for (int i = 10; i < 20; i++)
	{

		//第二种赋值 底层 p2[i] ---> p2+i
		p2[i] = i + 1;
	}
	//遍历
	for (int i = 0; i < 20; i++)
	{
		printf("%d\n", *(p2 + i));
	}

	//4.释放原来的p指针内存空间
	free(p2);

	return 0;
}

动态内存分配的细节

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

// 主入口
int main()
{

	//1,malloc创建空间的单位是字节
	int* p1 = malloc(sizeof(int) * 10);//4*10 也就是40字节

	//2,malloc返回的是void类型的指针,没有步长的概念,也无法获取空间中的数据,需要强转
	int* p2 = (int*)malloc(sizeof(int) * 10);//c语言底层帮我们转了

	//3,malloc返回的仅仅是首地址,没有总大小,最好定义一个变量记录总大小
	int size = 10;//元素的个数
	int* p3 = malloc(sizeof(int) * size);//4*10 也就是40字节	//赋值
	for (int i = 0; i < size; i++)
	{
		//*(p3 + i) = i + 1;
		p3[i] = i + 1;
	}
	for (int i = 0; i < size; i++)
	{
		//printf("%d\n", *(p3 + i));
		printf("%d\n", p3[i]);
	}

	//4,malloc申请的空间不会自动消失,如果不能正确释放,会导致内存泄露
	//5,malloc申请的空间过多,会产生虚拟内存
	//6,malloc申请的空间没有初始化值,需要先赋值才能使用
	//7,free释放完空间之后,空间中数据叫做脏数据,可能被清空,可能被修改为其他值
	//8,calloc就是在malloc的基础上多一个初始化的动作
	//9,realloc修改之后的空间,地址值有可能发生变化,也有可能不会改变,但是原本的数据不会丢失
	//10,realloc修改之后,无需释放原来的空间,函数底层会进行处理

	return 0;
}

C语言的内存结构

变量数组

全局变量和static变量

字符串

malloc函数

文件

路径

转义字符

表示改变 \ 后面这个符号原本的含义,把它变成一个普通的字符。

c 复制代码
	/*
		绝对路径
			C:\aaa\a.txt
		相对路径:
			参照物:当前项目
			不以盘符开始:aaa\a.txt
	*/
	char* file = "C:\\aaa\\a.txt";
	printf("%s\n", file);

读写模式



读取文件

c 复制代码
// 预处理,程序运行之前,需要提前做的事情
#include<stdio.h>

// 主入口
int main()
{

	//1.打开文件
	FILE* file = fopen("F:\\C\\program\\a.txt","r");

	//2.读取数据 

	// fgetc 一次读取一个字符,读不到返回-1
	int c = fgetc(file);
	printf("%c", c);

	//int c;
	//while ((c = fgetc(file))!=-1) {
	//	printf("%c", c);
	//}


	//fgets 一次读取一行,读不到返回null
	//每次读一行,以换行符为准
	//char arr[1024];
	//char* str = fgets(arr, 1024, file);
	//printf("%s", arr);
	//printf("%s", str);

	//char arr[1024];
	//char* str;
	//while ((str = fgets(arr, 1024, file))!=NULL)
	//{
	//	printf("%s", str);
	//}


	//fread 一次读多个
	//细节:abc占一个字节
	// 中文windows64位默认占两个字节
	//第一次:读取前面20个字节,把数组装满,函数返回20
	//第二次:读取后面20个字节,把数组装满,函数返回20
	//第三次:读取剩余的9个字节,把数组装满,函数返回9
	//第四次:没东西可读了,函数返回0
	char arr[1024];//装读到的数据
	//int n = fread(arr, 1, 1024, file);
	//printf("%d", n);
	//printf("%s", arr);

	int n;
	while ((n = fread(arr, 1, 1024, file))!=0) {
		for (int i = 0; i < n; i++)
		{
			printf("%c", arr[i]);
		}
	}


	//3.关闭文件
	fclose(file);

	return 0;
}

写出数据

c 复制代码
	/*
		fputc:一次写一个字符,返回写出的字符
		fputs:一次写一个字符串,写出成功返回非负数,一般忽略返回值
		fwrite:一次写多个,返回写出的字节个数
	*/
	
	//1.打开文件
	FILE* file = fopen("F:\\C\\program\\b.txt", "w");

	//2写出数据
	//fputc:一次写一个字符,返回写出的字符
	//int c = fputc(97, file);
	//printf("%c", c);//a

	//fputs:一次写一个字符串,写出成功返回非负数,一般忽略返回值
	//int d = fputs("你好世界", file);
	//printf("%d", d);//0

	//fwrite:一次写多个,返回写出的字节个数
	char arr[] = { 97,98,99,100 };
	int n2 = fwrite(arr,1,4,file);
	printf("%d\n", n2);//4

	//3.关闭文件
	fclose(file);

拷贝文件

相关推荐
m0_7482561411 分钟前
前端 MYTED单篇TED词汇学习功能优化
前端·学习
忒可君16 分钟前
C# winform 报错:类型“System.Int32”的对象无法转换为类型“System.Int16”。
java·开发语言
GuYue.bing26 分钟前
网络下载ts流媒体
开发语言·python
IT古董30 分钟前
【机器学习】机器学习的基本分类-半监督学习(Semi-supervised Learning)
学习·机器学习·分类·半监督学习
StringerChen34 分钟前
Qt ui提升窗口的头文件找不到
开发语言·qt
数据小爬虫@40 分钟前
如何利用PHP爬虫获取速卖通(AliExpress)商品评论
开发语言·爬虫·php
jbjhzstsl1 小时前
lv_ffmpeg学习及播放rtsp
学习·ffmpeg
青い月の魔女1 小时前
数据结构初阶---二叉树
c语言·数据结构·笔记·学习·算法
网络安全(king)1 小时前
网络安全攻防学习平台 - 基础关
网络·学习·web安全
java1234_小锋1 小时前
MyBatis如何处理延迟加载?
java·开发语言