c语言基础总结

发布于:2024-04-19 ⋅ 阅读:(296) ⋅ 点赞:(0)

1. c语言概述

c语言是计算机编程语言的一种,编程语言用于人和机器交流。

1.1 c语言特点

简洁

​ c语言的语法简单,语句清晰明了,使得程序易于阅读和理解

高效

​ c语言的执行效率高,可以用于开发需要高性能的应用程序

可移植

​ c语言可以在不同硬件平台和操作系统上运行,具有较高的可移植性

模块化

​ c语言支持函数和结构体等模块化编程方法,使得程序的复杂性得到有效控制。

标准化

​ c语言的语法和标准库已经被ISO和ANSI标准化,具有广泛的应用和兼容性。

1.2 c语言应用领域

系统软件

嵌入式系统

网络设备

游戏开发

1.3 c语言编译器

GCC

MinGW

Cygwin

MSVC

2. 基础语法

2.1 数据类型

c语言中数据类型有3种,分别为基本数据类型,构造数据类型,指针数据类型

基本类型:

​ 整型: int、short、long

​ 字符型:char

​ 浮点型:float(单精度浮点)、double(双精度浮点)

构造类型:

​ 数组类型

​ 结构类型

​ 联合类型

​ 枚举类型

指针类型:

​ char * int* int**等

数据类型的作用: 编译器预算数据分配的内存空间大小。

2.2 变量

变量是用来存储数据的一个内存区域,并用一个名字来表示这个区域。

特点:

​ 变量在使用前必须先定义,定义变量前必须要有相应的数据类型;

​ 在程序运行过程中,它的值可以改变。

语法说明:

在这里插入图片描述

2.2.1 char类型

char表示为字符类型,用于存储单个字符,每个字符变量都有8个bit位构成,在内存中就是1字节。

2.2.2 布尔类型

布尔类型是一种处理逻辑的类型,有两个值,true和false,在内存中只占用1个字节。

早期c语言没有布尔类型数据,以0代表false,非0代表真。

C99标准定义了新的关键字_Bool,提供了布尔类型,或者也可以使用stdbool.h中的bool

2.3 数据类型的长度

字节:就是计算机存储的最小单位,以字节(byte)为单位。

2.3.1 基本数据类型长度

数据类型的长度会受到操作系统平台的影响。所以在不同平台下基本数据类型的长度是不一样的。

ps:在单片机开发中,int在8位的单片机中长度为2个字节,在32位的单片机中长度为4个字节

2.3.2 可移植类型

  • C语言在可移植类型头文件 stdint.h 和 inttype.h 中规定了精确宽度整数类型,以确保C语言的类型在各系统内功能相同。

2.4 常量

与变量不同,常量的值在程序运行时不会改变

2.4.1 自定义常量

常量的定义方式有两种:

// 预处理常量
#define PI 3.14

// const常量
const double pi2 = 3.14;

2.5 数值表示

✨2.5.1 进制

十进制 二进制 八进制 十六进制
0 0 0 0
1 1 1 1
2 10 2 2
3 11 3 3
4 100 4 4
5 101 5 5
6 110 6 6
7 111 7 7
8 1000 10 8
9 1001 11 9
10 1010 12 A
11 1011 13 B
12 1100 14 C
13 1101 15 D
14 1110 16 E
15 1111 17 F
16 10000 20 10
2.5.1.1 二进制

二进制用0和1两个数来表示

基数为2,进位规则是逢二进一,借位规则是借一当二

数据在计算机中主要是以补码的形式存储的

十进制转换二进制的方法:

​ 用十进制除以2,分别取余数和商数,商数为0时,余数倒着数就是转化后的结果

​ 除二取余,倒序排列

2.5.1.2 八进制

以8为基数的计数法,采用0,1,2,3,4,5,6,7八个数字,逢八进1

  • 八进制的数和二进制数可以按位对应(八进制一位对应二进制三位)
    在这里插入图片描述

  • 十进制转化八进制的方法:
    在这里插入图片描述

2.5.1.2 十六进制

由0-9,A-F组成,字母不区分大小写

10进制的对应关系是:0-9对应0-9,A-F(或a-f)对应10-15

十六进制的数和二进制数可以按位对应(十六进制一位对应二进制四位)

  • 十六进制和二进制互转:

在这里插入图片描述

十进制转化十六进制的方法

在这里插入图片描述

2.6 c语言表示相应进制数

十进制 以正常数字1-9开头,如15
八进制 以数字0开头,如017
十六进制 以0x或0X开头,如0xf
二进制 以0b或0B开头,如0b1111
#include <stdio.h>

int main() {
    // 十进制方式赋值
    int a = 15;
    // 八进制方式赋值
    int b = 017;
    // 十六进制方式赋值
    int c = 0xf;
    // 二进制方式赋值
    int d = 0b1111;
    printf("%d, %d, %d, %d\n", a, b, c, d);

    return 0;
}

// 十进制以正常数字1-0开头 如15;

// 八进制以数字0开头,如 015;

// 十六进制 以0x或0X开头,如 0x0F;

// 二进制以0b或0B开头,如 0b0001;

2.7 数值的存储方式

2.7.1 原码

十进制数按照除二取余、倒序排列得到的就是原码

例如:

  • 10 -> 0000 1010

  • -10 -> 1000 1010

  • -1 -> 1000 0001

  • 1 -> 0000 0001

    **问题:**正负数的加法运算,以及零的问题

2.7.2 反码

正数的反码就是原码本身

负数的反码就是按位取反(但符号位不变,符号位就是最左边第一个数字)

例如:

  • 1 -> 0000 0001 -> 0000 0001
  • -1 -> 1000 0001 -> 1111 1110
  0000 0001
+ 1111 1110
-----------------
  1111 1111

1111 1111 是运算完之后的结果,但要注意,这时还是反码,需要重新返回来:1000 0000 。

反码解决了正负数加法问题,但正负零的问题还是存在。

2.7.3 补码

正数的补码就是原码本身

负数的补码就是在反码的基础上+1;

例如:

  • 1 -> 0000 0001 -> 0000 0001 -> 0000 0001
  • -1 -> 1000 0001 -> 1111 1110 -> 1111 1111
  0000 0001
+ 1111 1111
----------------
  0000 0000

补码在正负数加减法运算时没问题,也不会出现正负零占两个二进制。但 1000 0000 不表示为负零,计算机默认把8位有符号二进制 1000 0000 表示为 -128 ;

总结:

十进制数转化为二进制就是原码

正数的反码和补码就是原码本身

负数的反码就是按位取反(符号位不变,符号位就是最左边第一个数字)

负数的补码就是在反码的基础上+1

2.8 输入和输出

2.8.1 输出:
  • 输出:将程序的运行结果输出到控制台或终端窗口中

语法格式:

#include <stdio.h>

int main() {
 // %d %c %f %s字符串

 int num = 15;
 char a = 's';
 float f = 154.223;
 short mm = 123123;

 // %#X 占位符 八进制和十六进制可以加上前缀#
 printf("%d\n", num);
 printf("%d\n", sizeof(int));
 printf("%c\n", a);
 printf("%p\n", &num);

 return 0;
}
  • 格式化占位符:

    下面为总的占位符

  • 打印格式 对应数据类型 含义
    %c char 字符型,输入的数字按照ASCII码相应转换为对应的字符
    %hd short int 短整数
    %hu unsigned short 无符号短整数
    %d int 接受整数值并将它表示为有符号的十进制整数
    %u unsigned int 无符号10进制整数
    %ld long 接受长整数值并将它表示为有符号的十进制整数
    %f float 单精度浮点数
    %lf double 双精度浮点数
    %e,%E double 科学计数法表示的数,此处"e"的大小写代表在输出时用的"e"的大小写
    %s char * 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符)
    %p void * 以16进制形式输出指针
    %o unsigned int 无符号8进制整数
    %x,%X unsigned int 无符号16进制整数,x对应的是abcdef,X对应的是ABCDEF
  • 打印格式 对应数据类型 含义
    %c char 字符型,输入的数字按照ASCII码相应转换为对应的字符
    %d int 接受整数值并将它表示为有符号的十进制整数
    %f float 单精度浮点数
    %lf double 双精度浮点数
    %s char * 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符)
    %p void * 以16进制形式输出指针

只需记住第二张表的几个就行。

2.8.2 输入:
  • 输入:接收用户输入的数据的过程

  • scanf语法格式:

#include <stdio.h>

int main() {
 // scanf("格式化字符串", &变量1, &变量2,...);
 // 输入年龄然后打印
 // 数据是给定变量的地址

 int age;
 printf("请输入你的年龄:");
 scanf("%d",&age);
 printf("你的年龄是:%d\n",age); 

 return 0;
}

2.9 运算符:

2.9.1 算术运算符
运算符 术语 示例 结果
+ 10 + 5 15
- 10 - 5 5
* 10 * 5 50
/ 10 / 5 2
% 取模(取余) 10 % 3 1
++ 前自增 a=2; b=++a; a=3; b=3;
++ 后自增 a=2; b=a++; a=3; b=2;
前自减 a=2; b=–a; a=1; b=1;
后自减 a=2; b=a–; a=1; b=2;
#include <stdio.h>

int main() {
 float m = 5;
 int n = 2;
 // 整数类型
 printf("res = %d\n", m / n);
 printf("res = %f\n", m / n);
 // 强烈不建议使用强制类型转换

 // 前++  先加再运算
 int i = 0;
 ++i;
 int d = ++i;
 printf("d = %d\n", i);

 // 后++  先运算再加
 int data = 0;
 int sum = data++;
 printf("sum = %d\n", sum);
 printf("data = %d\n", data);

 return 0;
}

2.9.2 赋值运算符
运算符 术语 示例 结果
= 赋值 a=2; b=3; a=2; b=3;
+= 加等于 a=0; a+=2;等同于 a = a + 2; a=2;
-= 减等于 a=5; a-=3;等同于 a = a - 3; a=2;
*= 乘等于 a=2; a*=2;等同于 a = a * 2; a=4;
/= 除等于 a=4; a/=2;等同于 a = a / 2; a=2;
%= 模等于 a=3; a%=2;等同于 a = a % 2; a=1;
#include <stdio.h>

int main() {
    int a = 10;

    int b = a;

    a += 10;
    b += 20;
    printf("%d\n", b);
    return 0;
}

2.9.3 比较运算符

C 语言的比较运算中, “真”用数字“1”来表示, “假”用数字“0”来表示。

运算符 术语 示例 结果
== 相等于 4 == 3 0
!= 不等于 4 != 3 1
< 小于 4 < 3 0
> 大于 4 > 3 1
<= 小于等于 4 <= 3 0
>= 大于等于 4 >= 1 1
#include <stdio.h>

int main() {
 // 比较的结果是 0  1
 int m= 10;
 int n = 30;

 printf("m>n = %d\n", m>n);
 printf("m<n = %d\n", m<n);
 printf("m>=n = %d\n", m>=n);
 printf("m<=n = %d\n", m<=n);
 printf("m!=n = %d\n", m!=n);
 printf("m==n = %d\n", m==n);


 return 0;
}
2.9.4 逻辑运算符
运算符 术语 示例 结果
! !a 如果a为假,则!a为真;如果a为真,则!a为假。
&& a && b 如果a和b都为真,则结果为真,否则为假
|| a || b 如果a和b有一个为真,则结果为真,二者都为假时,结果为假。

✨✨✨**3. 位运算符

常见的位运算符号有&、|、^、~、>>、<<,分别代表着如下含义:

运算符 术语 示例 结果
& 按位与运算 011 & 101 2个都为1才为1,结果为001
| 按位或运算 011 | 101 有1个为1就为1,结果为111
^ 按位异或运算 011 ^ 101 不同的为1,结果为110
~ 取反运算 ~011 100
<< 左移运算 1010 << 1 10100
>> 右移运算 1010 >> 1 0101

ps:取反、左右位移运算需要在补码的基础上运算。

& 按位与运算
011  3 
101  5
---------------
001
结论: 按位与运算  相同为1  结果为1


| 按位或运算
011  3 
101  5
---------------
111
结论: 按位或运算  只要有一个为1  结果就为1

^ 按位异或运算
011  3 
101  5
---------------
110
结论:  按位异或运算  不同的为1  相同为0


~ 按位取反运算
~ 011
  100
结论: 取反运算  1为0 0为1

<< 左移运算
1010 << 1
10100
结论:左移就是所有位 向左移动 后面补0

>> 右移运算
1010 >> 1
101
结论:右移就是所有位 向右移动 前面补0
#include <stdio.h>
#include <inttypes.h>
int main() {
 // & 按位与运算
 // 例子:  
           //  011  3 
           //  101  5
          // ---------------

 printf("%d\n", 3 & 5); // 1


 // 按位或
 printf("%d\n", 3 | 5);  // 7

 // 按位异或
 printf("%d\n", 3 ^ 5);   // 6  


 // 取反
 printf("%d\n", ~3);  // -4

// 需要注意: 做高位取反
int8_t num = 3;
printf("%d\n", ~num); // -4

// 左移
// 0001  1000
printf("%d\n", 1 << 3);  // 8

// 右移
// 0101 0010
printf("%d\n", 5 >> 1);  // 2


 return 0;
}

#include <stdio.h>
#include <inttypes.h>

int main() {
 // 将变量a的第2位设置为1,其他位保持不变
// uint8_t a = 0b10110011; // 0xb3;
// a = ob1011  0111 
// 0b0000 0100

 uint8_t a = 0xb3;

printf("置位结果:%#x\n", a | 0x4);      // 0xb7
printf("置位结果:%#x\n", a | (1 << 2));  // 0xb7

// 将变量b的第2位、第6位设置为1,其他位保持不变
// uint8_t b = 0b10110011; // 0xb3;
// 0b1011 0011   0xb3
// 0b0100 0100   0x44
// 0b1111 0111   0xF7
uint8_t b = 0xb3;
printf("置位结果:%#x\n", b | 0x44);                    // 0xf7
printf("置位结果:%#x\n", a | (1 << 2 | 1 << 6));        // 0xf7

// 将变量c的第5位设置为0,其他位保持不变
// uint8_t c = 0b1011 0011;  // 0xb3;
//  0b1011 0011   0xb3
//  0b1101 1111   0xdf
//  0b1001 0011   0x93
uint8_t c = 0xb3;
printf("置位结果:%#x\n", c & 0xdf);                  // 0x93
printf("置位结果:%#x\n", c & ~(1 << 5));              // 0x93

// 将变量d的第0~3位设置为0,其他位保持不变
// uint8_t d = 0b11111111;  // 0xff;
//  0b1111 1111   0xff
//  0b1111 0000   0xf0
//  0b0000 1111    0x0f

uint8_t d = 0xff;
printf("置位结果:%#x\n", d & 0xf0);                                // 0xf0
printf("置位结果:%#x\n", d & ~(1<< 0 | 1 << 1 |1<< 2| 1<< 3));     // 0xf0

// 将变量e的第2位取反,其他位保持不变
// uint8_t e = 0b1011 0011;  // 0xb3;
// 0b1011 0011  0xb3
// 0b0000 0100  0x4
// 0b1011 0111  0xb7
uint8_t e = 0xb3;

printf("置位结果:%#x\n", e ^ 0x4);         // 0xb7
printf("置位结果:%#x\n", e ^ (1 << 2));    // 0xb7

// 将变量f取出8-15位
// uint32_t f = 0x12345678;
// 0001  0010  0011  0100  0101  0110  0111  1000  0x12345678
// 0101  0110  5  6   0x56                         0x00000ff00
uint32_t f = 0x12345678;
printf("取出8-15位:%#x\n", (f & 0x00000ff00) >> 8);  // 0x56
 return 0;
}

总结:

& -(与运算)

按位与(&)运算:位与位进行比较,如果都为1,则为1,否则为0;

| -(或运算)

按位或(|)运算:位与位进行比较,如果都为0,则为0,否则为1;

^ -(异或运算)

按位异或运算:位与位进行比较,相同为0,不同为1;

~ -(取反运算)

按位取反运算:补码取反,再将取反后的补码转为原码;

ps:无符号的数据,取反后最高位为1,也不需要逆运算。

/**
 * 按位取反运算:补码取反,再将取反后的补码转为原码。
 *      1、正数取反:由于正数的原码和补码是相同的,取反的方式简便了些。
 *              补码(原码) -> 取反 -> 补码逆运算 -> 反码逆运算(符号位不变) -> 取反后的原码
 *      2、负数取反:
 *              原码 -> 反码 -> 补码 -> 取反 -> 取反后的补码即原码
 * 示例:
 *            原码(补码)  取反的补码   补码逆运算-1  反码逆运算
 *      ~40 = 0010 1000 -> 1101 0111 -> 1101 0110 -> 1010 1001 = -41
 *
 *            原码(补码)  取反的补码   补码逆运算-1  反码逆运算
 *      ~15 = 0000 1111 -> 1111 0000 -> 1110 1111 -> 1001 0000 = -16
 *
 *                原码         反码          补码          取反
 *      ~-15 = 1000 1111 -> 1111 0000 -> 1111 0001 -> 0000 1110 = 14
 */
printf("~40 = %d\n", ~40);
printf("~15 = %d\n", ~15);
printf("~-15 = %d\n", ~(-15));
<< -(左移运算符)

将数字的二进制补码全部向左移动,空出来的位置补0,超出范围的二进制数丢弃

有符号的数据左移后最高位如果为1,则需要进行逆运算

  • 无符号的数据,左移后最高位为1,也不需要逆运算;
  • -128:1000 0000 特殊情况也不需要逆运算
/**
     * 示例:
     *      40 << 4 = 0010 1000 << 4 = 1000 0000 = -128 (特殊的不需要逆运算)
     *      41 << 4 = 0010 1001 << 4 = 1001 0000 = 1000 1111 = 1111 0000 = -112
     *       7 6 5 4 3 2 1 0
     *       1 0 0 1 0 0 0 0
     */

    int8_t p = 40;
    p <<= 4;    //  p = p << 4;
    printf("40 << 4 = %d\n", p);
>> -(右移运算符)

将数字的二进制补码全部向右移动,空出来的位置补什么,取决于原来的最高位是什么。原来的最高是1就补1, 原来的最高位是0 就补0 。也可以转化成这样的一句话: 正数补0, 负数补1

	
	/*
	  23: 0001 0111【原码】 ----  0001 0111【反码】 ----  0001 0111 【补码】
											  >> 2
	  -----------------------------------------------
										  0000 0101【补码】 --->  5
	 */
	printf(" 23 >> 2 = %d \n" , 23 >> 2) ; 
	
	
	/*
	  123: 1001 0111【原码】 ----  1110 1000【反码】----  1110 1001【补码】
											  >> 2
	  -----------------------------------------------
											1111 1010【补码】 --->  1111 1001【反码】- ----- 1000 0110 【原码】===> -6
	 */
	printf(" -23 >> 2 = %d \n" , -23 >> 2) ; 
#include <stdio.h>
#include <inttypes.h>

int main() {
    uint8_t a = 3;          // 0000 0011
    uint8_t b = 10;         // 0000 1010
    // 打印显示2个字符,个数不够,左边补0
    printf("%02x\n", a & b); // 0000 0010,16进制为02
    printf("%02x\n", a | b); // 0000 1011,16进制为0b
    printf("%02x\n", a ^ b); // 0000 1001,16进制为09

    uint8_t c = 10;          // 0000 1010
    uint8_t temp = ~c;       // 1111 0101
    printf("%02x\n", temp);   // 1111 0101,16进制为f5
    printf("%02x\n", c << 1); // 0001 0100,16进制为14
    printf("%02x\n", c >> 1); // 0000 0101,16进制为05

    return 0;
}

在这里插入图片描述
用这个方法进行进制间的计算较为通俗易懂

3.1 进制转换

数据有不同的类型,不同类型数据之间进行混合运算时涉及到类型的转换问题。

  • 转换的方法有两种:

    • 自动转换(隐式转换):遵循一定的规则,由编译系统自动完成
    • 强制类型转换:把表达式的运算结果强制转换成所需的数据类型
      • 语法格式: (类型)变量或常量
  • 类型转换的原则:

    • 占用内存字节数少(值域小)的类型,向占用内存字节数多(值域大)的类型转换,以保证精度不降低。
#include <stdio.h>

int main() {
	// 隐式转换(自动转换):编译器自动转换
	int a = 11;
	double b = a; //将a的值,11,转换为11.0000,再给b赋值 
	printf("b = %lf\n", b);
	printf("a = %d\n", a);

	// 强制类型转换,用户转换, (类型)变量或常量
	int m = 3;
	int n = 2;
	b = (double)m/n;
	printf("b = %lf\n", b);

	b = (double)3/2;
	printf("b = %lf\n", b);

	// 类型转换原则:数据类型小的往数据类型大的转
	int c = 100;
	char d = (char)c; //没有问题
	printf("d = %d\n", d);

	// 大的往小的转,数据可能会丢失
	c = 129;
	d = (char)c;
	printf("d = %d\n", d);

	// 小的往大的转
	long long big = (int)c;
	printf("big = %lld\n", big);

	return 0;
}

运行结果:

b = 11.000000
a = 11
b = 1.500000
b = 1.500000
d = 100
d = -127
big = 129
#include <stdio.h>

int main() {
    //  1、二进制  逢二进一    除二取余法 
    //     0  0
    //     1  1
    //     2  10
    //     3  11
    //     4   100
    //     5   101
    //     6   110
    //     7   111
    //     8   1000

    //     转换  8  《 = 》 2
    //     01   4    5
    //     001 100 101

    //     转换 16进制 = 2
    //     A      B    C
    //     1010  1011 1100

    return 0;
}

4. 控制语句

4.1 程序执行的三大流程

  • 顺序 : 从上向下, 顺序执行代码
  • 分支 : 根据条件判断, 决定执行代码的分支
  • 循环 : 让特定代码重复的执行

4.2 分支语句

条件语句用来根据不同的条件来执行不同的语句,C语言中常用的条件语句包括if语句和switch语句

4.2.1 if 语句

在这里插入图片描述

#include <stdio.h>

int main() {
 // 根据条件去执行某些代码
 int a = 998;
 if (a > 98.8) {
    printf("回家睡觉");
 }

 return 0;
}

4.2.2if…else 语句

基本语法

在这里插入图片描述

#include <stdio.h>

int main() {
 /* 
● 定义一个整数变量记录年龄
● 判断是否满 18 岁 (>=)
● 如果满 18 岁,允许进网吧嗨皮
● 否则,提示回家写作业
 */

 int age;
 printf("请输入你的年龄:");
 if (scanf("%d", &age)) {
    if (age >= 18) {
        printf("玩游戏");
    } else {
        printf("回家写作业");
    }
 }

 return 0;
}

4.2.3 三目运算符

运算符 术语 示例 结果
?: 三目运算符 a>b?a:b; 如果a>b,整体为结果a,否则整体结果为b
#include <stdio.h>

int main() {
 // ?:	三目运算符	a>b?a:b;	如果a>b,整体为结果a,否则整体结果为b

int a = 30;
int b = 40;
int c= a > b ? a : b;
printf("最大值=%d\n",c);
 return 0;
}

4.2.4if…else if…else语句

在这里插入图片描述

if (条件1) {
    条件1成立时,要做的事
    ……
} else if(条件2) {
	条件2成立时,要做的事 
    ……
} else {
	条件不成立时,要做的事 
    ……
}
#include <stdio.h>

int main() {
  /* 
  ● 天猫超市双 11 推出以下优惠促销活动:
  ○ 购物满 50 元,打 9 折;
  ○ 购物满 100 元,打 8 折;
  ○ 购物满 200 元,打 7 折;
  ○ 购物满 300 元,打 6 折;
  ● 编程计算当购物满 s 元时,实际付费多少
  */

    // 1.定义变量记录购买金额 定义变量记录实际费用
    float money, real_money;
    // 2.输入购买金额
    printf("请输入购买金额:");
    scanf("%f", &money);
    // 3.根据购买金额判断折扣
    if (money >= 50 && money < 100) {
        // 购物满 50 元,打 9 折;
        real_money = money * 0.9;
    } else if (money >= 100 && money < 200) {
        // 购物满 100 元,打 8 折;
        real_money = money * 0.8;
    } else if (money >= 200 && money < 300) {
        // 购物满 200 元,打 7 折;
        real_money = money * 0.7;
    } else if (money >= 300) {
        // 购物满 300 元,打 6 折;
        real_money = money * 0.6;
    } else {
        // 不满50 原价
        real_money = money;
    }
    
    printf("购买金额: %f  实际价格: %f\n", money, real_money);

 return 0;
}

4.2.5 switch语句

  • 测试一个表达式是否等于一些可能的值,并根据表达式的值执行相应的代码块,可以使用switch语言实现

  • switch可以支持数据类型:

    • int
    • 枚举类型
    • char类型
  • switch和if区别:

    • 需要根据布尔条件来执行不同的代码块,则应使用if语句
    • 需要根据表达式的值来执行不同的代码块,则应使用switch语句

语法格式:

switch (expression) {
    case value1:
        // 代码块1
        break;
    case value2:
        // 代码块2
        break;
    default:
    	// 代码块3
}
    

案例:

#include <stdio.h>

int main() {
 /* 
  ○ 输入1:输出Monday
  ○ 输入2:输出Tuesday
  ○ 输入3:输出Wednesday
  ○ 输入4:输出Thursday
  ○ 输入5:输出Friday
  ○ 输入6:输出Saturday
  ○ 输入7:输出Sunday
  ○ 输入其它:输出error
  */

  int day;
  printf("请输入星期:");
  scanf("%d", &day);
  switch (day) {
    case 1:
    printf("Monday\n");
    break;
    case 2:
    printf("Tuesday\n");
    break;
    case 3:
    printf("Wednesday\n");
    break;
    case 4:
    printf("Thursday\n");
    break;
    case 5:
    printf("Friday\n");
    break;
    case 6:
    printf("Saturday\n");
    break;
    case 7:
    printf("Sunday\n");
    break;
    default:
    printf("error\n");
  }

 return 0;
}

4.2.6 分支综合案例

1.定义变量保存年份、月份、天数
2.输入年份和月份
3.根据月份输出天数
1、3、5、7、8、10、12月 31天
4、6、9、11月 30天
2月 非闰年 28天 闰年 29天
闰年判断:能被4整除,但不能被100整除的;或者能被400整除的年份

代码实现:

#include <stdio.h>

int main() {
 /* 
 ● 输入:年份(整数)和月份(整数)
 ● 输出:该月份的天数(整数)
  */

  int year, month, day;
  printf("请输入年份:");
  scanf("%d", &year);
  printf("请输入月份:");
  scanf("%d", &month);
  switch (month) {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:
      day = 31;
      break;
    case 4:
    case 6:
    case 9:
    case 11:
      day = 30;
      break;
    case 2:
     if (year % 4 == 0 && year % 100 != 0 || year %400 == 0) {
        day = 29;
      } else {
        day = 28;
      }
      break;
    default:
      printf("输入的月份有误!");
     }
    printf("%d 年 %d 月 有 %d 天\n", year, month, day);

 return 0;
}

4.3 循环语句

循环就是重复执行代码

流程图:

在这里插入图片描述

循环的实现方式:

while

do…while

for

4.3.1 while语句

图片1.png

示例代码:

#include <stdio.h>

int main() {
 // 终止条件  循环变量
 int i = 0;
 while (i < 5 ) {
    printf("玛卡巴卡\n");
    i++;
 }

 return 0;
}

4.3.2 do…while语句

图片1.png

示例代码:

#include <stdio.h>

int main() {
 // 至少执行一次
 int i = 0;
 do {
    printf("大大怪\n");
    i++;
 } while (i < 10);

 return 0;
}
  • do-while 循环语句是在执行循环体之后才检查 条件 表达式的值
  • 所以 do-while 语句的循环体至少执行一次,do…while也被称为直到型循环

4.3.3 for语句

  • for ( init; condition; increment ) {
    	循环体
    }
    
  • init会首先被执行,且只会执行一次

  • 接下来,会判断 condition,如果条件condition为真

  • 在执行完 for 循环主体后,控制流会跳回上面的 increment 语句

  • 条件再次被判断

示例代码:

#include <stdio.h>

int main() {
 int a = 4;
 // 初始化变量 条件 循环变量  增加/减少
 for (int i = 0; i < a; i++) {
    printf("小小怪\n");
 }

 return 0;
}

4.3.4 循环案例

#include <stdio.h>

int main() {
 // 求 1- 100 的和
 int sum = 0;
 for(int i= 1; i<= 100; i++) {
    sum += i;
 }
 printf("%d\n", sum);



 int a = 0;
 int b = 0;
 while(a <= 100) {
    b += a;
    a++; 
 }
 printf("%d\n", b);



 int c = 0;
 int n = 0;
 do {
    n += c;
    c++;
 } while(c <= 100);
 printf("%d\n", n);

 return 0;
}

4.4 跳转关键字

  • 循环和switch专属的跳转:break
  • 循环专属的跳转:continue
  • 无条件跳转:goto
  • // break 跳出整个循环
  • // continue 跳出本次循环
#include <stdio.h>

int main() {
 // break  跳出整个循环
 // continue 跳出本次循环

//   int i= 0;
//   while(i < 6) {
   
//     if(i == 3) {
//         printf("找到了\n");
//         break;
//     }
//      i++;
//     printf("i = %d\n", i);
//   }
  int i= 0;
  while(i < 6) {
   
    if(i == 3) {
        printf("找到了\n");
        continue;
    }
     i++;
    printf("i = %d\n", i);
  }
 return 0;
}

#include <stdio.h>

// ● goto用于无条件跳转
//  ○ 在一种情况下可以使用goto语句:从一组嵌套的循环中跳出
// ● goto语句可以导致代码不易理解和维护,并且容易引入不必要的错误。因此,除非必要,最好不要使用goto语句

int main() {
 int i = 0;
 while(i < 3) {
    if (i == 3) {
        goto End;   //条转到End标签
    }
    printf("i = %d\n", i);
    i++;
 }
 End: 
 printf("End\n");

 return 0;
}

5. 函数

5.1 概述

  • 函数是一种可重用的代码块,用于执行特定任务或完成特定功能

  • 函数作用:对具备相同逻辑的代码进行封装,提高代码的编写效率,实现对代码的重用

  • 函数分类

    • 系统函数,即库函数:这是由编译系统提供的,用户不必自己定义这些函数,可以直接使用它们,如我们常用的打印函数printf()。
    • 自定义函数:用以解决用户的专门需要。

5.2 函数的使用

5.2.1 无参无返回值

#include <stdio.h>

// 函数没有参数 也没有返回值
// 定义函数
add() {
    printf("阿巴阿巴\n");
}
int main() {
 // 调用函数
    add();
 return 0;
}

5.2.2有参无返回值

#include <stdio.h>
//   编写一个函数,实现2个数相加,2个数通过参数传递
// 定义函数
void add(int a,int b){
    // 形参相加,打印
    int res = a + b;
    printf("相加结果为:%d\n",res);
}

int main() {
 // 调用函数
 add(10,20);

 return 0;
}

5.2.3 有参有返回值

#include <stdio.h>

// 函数有参数有返回值
// 返回使用return返回
// 定义函数返回值类型 int 

int sum(int a, int b) {
    return a +b;
}
int main() {
 
// 调用函数
int num = sum(10, 20);
printf("num = %d\n", num);
 return 0;
}

5.2.4 返回值注意点

  • return的作用是结束函数

    • 函数内,return后面的代码不会执行

示例代码:

#include <stdio.h>

// 函数定义
void func() {
    printf("11111111111111111\n");

    return; // 结束函数,函数内后面代码不会执行

    printf("222222222222222222\n");
}


int main() {
    // 函数调用
    func();

    return 0;
}

5.2.5 函数的声明

  • 如果函数定义代码没有放在函数调用的前面,这时候需要先做函数的声明
  • 所谓函数声明,相当于告诉编译器,函数是有定义的,再别的地方定义,以便使编译能正常进行
  • ❤️注意:一个函数只能被定义一次,但可以声明多次

示例代码:

#include <stdio.h>
// 函数的声明 分号不能省略
// 函数声明的前面可以加extern关键字,也可以不加

int add(int a, int b);
// 另一种方式,形参名可以不写
// int add(int, int);
int main() {
    // 函数的调用
    int temp = add(10, 20);
    printf("temp = %d\n", temp);
 

 return 0;
}

// 函数的定义
int add(int a, int b) {
    // 实现两个形参相加,并返回检索结果
    int c = a + b;
    return c;
}

5.3 局部变量和全局变量

5.3.1 局部变量

  • 定义在代码块{}里面的变量称为局部变量(Local Variable)

  • 局部变量的作用域(作用范围)仅限于代码块{}, 离开该代码块{}是无效的

    • 离开代码块{}后,局部变量自动释放
image-20240415181713473

5.3.2 全局变量

在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域默认是整个程序,也就是所有的源文件

5.4 多文件编程

5.4.1 多文件编程

  • 把函数声明放在头文件xxx.h中,在主函数中包含相应头文件
  • 在头文件对应的xxx.c中实现xxx.h声明的函数

在这里插入图片描述

5.4.2 防止头文件重复包含

  • 当一个项目比较大时,往往都是分文件,这时候有可能不小心把同一个头文件 include 多次,或者头文件嵌套包含。
  • 为了避免同一个文件被include多次,C/C++中有两种方式。
  • 方法一:
#ifndef __SOMEFILE_H__ 
#define __SOMEFILE_H__ 

// 声明语句 

#endif

方法二(嵌入式不能用):

#pragma once // 声明语句

头文件包含的区别:

  • <> 表示系统直接按系统指定的目录检索
  • “” 表示系统先在 “” 指定的路径(没写路径代表当前路径)查找头文件,如果找不到,再按系统指定的目录检索

多文件编程运行结果出现中文乱码:在命令行输入:chcp 65001 再编译执行。

✨✨✨✨❤️6. 指针(重难点)

6.1 指针基本语法

6.1.1 指针变量的定义和使用(重点)

  • 指针也是一种数据类型,指针变量也是一种变量

  • 指针变量指向谁,就把谁的地址赋值给指针变量

  • 指针是C语言的精华,没有掌握指针,就是没有掌握C语言。

    指针让 C 语言更像是结构化的底级语言,所有在内存中的数据结构均可用指针来访问,指针让 C 语言带来了

    更简便的操作,更优效率,更快的速度。是天使也是魔鬼。

在这里插入图片描述

示例代码:

#include <stdio.h>

int main() {
 // 指针就是一种数据类型 可以用来存储内存的地址
//  指针 用来操作内存的
// 查看变量的地址
int a = 30;
printf("a = %d\n", a);
// & 按位与
// 取地址符
printf("a address = %p\n", &a);

// 指针地址
// 定义指针
// 指针就是数据类型
// int * 代表的就是指针, ptr代表的是指针变量名  &a 取a的地址给ptr
int* ptr = &a;
// %p 取地址, ptr 就是a的地址  *ptr 获取地址内容的值(取a的值)
printf("a == %p\n, ptr == %p, a == %d, ptr == %d\n" , &a, ptr, a, *ptr);

// 指针的实质就是内存地址
 return 0;
}

类型 变量;
类型 * 指针变量 = &变量;

    • & 叫取地址,返回操作数的内存地址
    • * 叫解引用,指操作指针所指向的变量的值
    • 在定义变量时,* 号表示所声明的变量为指针类型
      • 指针变量要保存某个变量的地址,指针变量的类型比这个变量的类型多一个*
    • 在指针使用时,* 号表示操作指针所指向的内存空间
#include <stdio.h>

int main() {

    // 定义一个int类型的变量,同时赋值为10
    int a = 10;
    // 打印变量的地址
    printf("&a = %p\n", &a);
    // 定义一个指针变量,int *保存int的地址
    // int *代表是一种数据类型,int *指针类型,p才是变量名
    int* p;
    // 指针指向谁,就把谁的地址赋值给这个指针变量
    p = &a;
    // 打印p, *p, p指向了a的地址,*p就是a的值
    printf("p = %p, *p = %d\n", p, *p);

    return 0;
}

在这里插入图片描述

6.1.2 通过指针间接修改变量的值

  • 指针变量指向谁,就把谁的地址赋值给指针变量
  • 通过 *指针变量 间接修改变量的值

在这里插入图片描述

6.1.3const修饰的指针变量

语法格式

int a = 1;
const int *p1 = &a;	// 等价于 int const *p1 = &a;
int * const p2 = &a;
const int * const p3 = &a;

从左往右看,跳过类型,看修饰哪个字符

  • 如果是*, 说明指针指向的内存不能改变
  • 如果是指针变量,说明指针的指向不能改变,指针的值不能修改

在这里插入图片描述

6.1.4 指针大小

  • 使用sizeof()测量指针的大小,得到的总是:4或8

  • sizeof()测的是指针变量指向存储地址的大小

    • 在32位平台,所有的指针(地址)都是32位(4字节)

    • 在64位平台,所有的指针(地址)都是64位(8字节)
      在这里插入图片描述

6.1.5 指针步长

  • 指针步长指的是通过指针进行递增或递减操作时,指针所指向的内存地址相对于当前地址的偏移量。

  • 指针的步长取决于所指向的数据类型。

    • 指针加n等于指针地址加上 n 个 sizeof(type) 的长度
    • 指针减n等于指针地址减去 n 个 sizeof(type) 的长度

在这里插入图片描述

6.1.6 野指针和空指针

  • 指针变量也是变量,是变量就可以任意赋值
  • 任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针
    • 此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)
  • 野指针不会直接引发错误,操作野指针指向的内存区域才会出问题
  • 为了标志某个指针变量没有任何指向,可赋值为NULL
    • NULL是一个值为0的宏常量

在这里插入图片描述

6.1.7 多级指针

C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。

在这里插入图片描述

6.2 指针和函数

6.2.1 函数参数传值

传值是指将参数的值拷贝一份传递给函数,函数内部对该参数的修改不会影响到原来的变量

在这里插入图片描述

✨✨✨❤️6.2.2 函数参数传址(重点)

传址是指将参数的地址传递给函数,函数内部可以通过该地址来访问原变量,并对其进行修改。

在这里插入图片描述

总结:

在这里插入图片描述

无论是前面学习的变量还是后面我们要学习的数组,都是逻辑上得表现,最终都要保存到内存中,而内存是线性的,这是物理基础。

内存是以字节为单位进行编址的,内存中的每个字节都对应一个地址,通过地址才能找到每个字节。

6.3 函数指针

6.3.1 函数名

一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址

6.3.2 函数指针

函数指针:它是指针,指向函数的指针

语法格式:

返回值 (*函数指针变量)(形参列表);

函数指针变量的定义,其中返回值、形参列表需要和指向的函数匹配


#include <stdio.h>

int func(int a, int b) {
    return a + b;
}

int main() {
    // 函数在编译的时候  会有一个入口地址
    // 这个地址就是函数指针地址
    // 一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址
    // printf("func =  %#x\n", &func);
    // printf("func =  %#x\n", *func);
    // printf("func =  %#x\n", func);
    // int res = func(1, 3);
    // 函数调用 最常见的形式  推荐使用
    printf("res = %d\n", func(1, 3));
    // 地址的形式
    printf("res = %d\n", (&func)(1, 5));
    // 指针的形式
    printf("res = %d\n", (*func)(5, 5));
    return 0;
}

✨✨✨❤️6.3.3 回调函数(重点)

  • 函数指针变量做函数参数,这个函数指针变量指向的函数就是回调函数

  • 回调函数可以增加函数的通用性

    • 在不改变原函数的前提下,增加新功能

在这里插入图片描述

/**
 *
 *#include <stdio.h>

int func(int a, int b) {
    return a + b;
}

int main() {
    // 函数在编译的时候  会有一个入口地址
    // 这个地址就是函数指针地址
    // 一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址
    // printf("func =  %#x\n", &func);
    // printf("func =  %#x\n", *func);
    // printf("func =  %#x\n", func);
    // int res = func(1, 3);
    // 函数调用 最常见的形式  推荐使用
    printf("res = %d\n", func(1, 3));
    // 地址的形式
    printf("res = %d\n", (&func)(1, 5));
    // 指针的形式
    printf("res = %d\n", (*func)(5, 5));
    return 0;
}

*/


#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int minus(int a, int b) {
    return a - b;
}

// 定义calac  方法  接受的参数 int  int   接受计算类型的函数名称 

int calac(int a, int b, int(*func)(int, int)) {
    return  func(a, b);
}

int main() {


    // int res = add(10, 20);
    // 可以接受 参数   接受我们的计算类型 (加减……)

    // int res = calac(5, 3, add);
    int res = calac(5, 3, minus);
    printf("sum = %d\n", res);

    return 0;
}

6.4 案例(计算圆的周长和面积)

// 计算圆的周长和面积


// 使用回调函数的形式 计算

// 圆的周长  pi * 2R
// 面积 pi *r *r
#include <stdio.h>

#define pi 3.14

double len(double r) {
    return 2 * r * pi;
}
double size(double r) {
    return pi * r * r;
}

double calac(double r, double(*p)(double)) {
    return p(r);
}


int main() {
    double r = 5;
    double length = calac(r, len);
    double area = calac(r, size);

    printf("len = %lf\n", length);
    printf("size = %lf\n", area);

    return 0;
}


#parse("C File Header.h")
#if (${HEADER_FILENAME})
#[[#include]]# "${HEADER_FILENAME}"
#end

7. 数组

7.1数组的基本使用

7.1.1什么是数组

数组是 C 语言中的一种数据结构,用于存储一组具有相同数据类型的数据

数组中的每个元素可以通过一个索引(下标)来访问,索引从 0 开始,最大值为数组长度减 1。

7.1.2.数组的使用

基本语法:

类型 数组名[元素个数];
int arr[5];

注意:

    • 数组名不能与其它变量名相同,同一作用域内是唯一的
    • 其下标从0开始计算,因此5个元素分别为arr[0],arr[1],arr[2],arr[3],arr[4]

示例代码:

#include <stdio.h>

int main() {
    // 定义了一个数组,名字叫a,有10个成员,每个成员都是int类型
    int a[10]; 
    // a[0]…… a[9],没有a[10]
    // 没有a这个变量,a是数组的名字,但不是变量名,它是常量
    a[0] = 0;
    // ……
    a[9] = 9;
    // 数据越界,超出范围,错误
    // a[10] = 10;  // err

    for (int i = 0; i < 10; i++) {
        a[i] = i; // 给数组赋值
    }

    // 遍历数组,并输出每个成员的值
    for (int i = 0; i < 10; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");

    return 0;
}

7.1.3数组的初始化

  • 在定义数组的同时进行赋值,称为初始化
  • 全局数组若不初始化,编译器将其初始化为零
  • 局部数组若不初始化,内容为随机值
	int a1[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // 定义一个数组,同时初始化所有成员变量
	int a2[10] = { 1, 2, 3 }; // 初始化前三个成员,后面所有元素都设置为0
	int a3[10] = { 0 }; // 所有的成员都设置为0

	 // []中不定义元素个数,定义时必须初始化
	int a4[] = { 1, 2, 3, 4, 5 }; // 定义了一个数组,有5个成员

7.1.4数组名

  • 数组名是一个地址的常量,代表数组中首元素的地址

示例代码:

#include <stdio.h>

int main() {
    // 定义一个数组,同时初始化所有成员变量
    int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 

    // 数组名是一个地址的常量,代表数组中首元素的地址
    printf("a = %p\n", a);
    printf("&a[0] = %p\n", &a[0]);

    int n = sizeof(a);     // 数组占用内存的大小,10个int类型,10 * 4  = 40
    int n0 = sizeof(a[0]); // 数组第0个元素占用内存大小,第0个元素为int,4
    int num = n / n0;      // 元素个数
    printf("n = %d, n0 = %d, num = %d\n", n, n0, num);

    return 0;
}

7.2数组案例

7.2.1一维数组的最大值

#include <stdio.h>

int main() {
    // 定义一个数组,同时初始化所有成员变量
    int a[] = {1, -2, 3, -4, 5, -6, 7, -8, -9, 10};

    // 假设第0个元素就是最大值
    int temp = a[0];
    for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
        // 如果有元素比临时的最大值大,就交换值
        if (a[i] > temp) {
            temp = a[i];
        }
    }
    printf("数组中最大值为:%d\n", temp);

    return 0;
}

7.3数组和指针

✨✨✨❤️7.3.1通过指针操作数组元素(重点)

  • 数组名字是数组的首元素地址,但它是一个常量
  • * 和 [] 效果一样,都是操作指针所指向的内存

在这里插入图片描述

1.数组名是一个常量,不允许重新赋值。
2.指针变量是一个变量,可以重新赋值。
3.p+i 和 array+i 均表示数组元素 array[i]的地址,均指向 array[i]
4.(p+i)和(array+i)均表示 p+i 和 array+i 所指对象的内容 array[i]。
5.p++:等价于(p++)。其作用:先得到*p,再使 p=p+1。

#include <stdio.h>

int main() {
    int array[10] = { 1,2,3,4,65,6,12,13,14,15 };



    int* p = &array;
    printf("array=== %p\n", &array);
    printf("p    === %p\n", p);
    int len = sizeof(array) / sizeof(array[0]);

    for (int i = 0; i < len; i++)
    {

        // array+i 和p+i  指向的都是地址  array[i]的地址
        // *(array+i) 和*(p+i) 指向的内容  ====array[i]

        // printf("arr[%d]  =  %d\n", i, array[i]);
        // printf("arr[%d]  =  %d\n", i, *(array + i));

        // printf("arr[%d]  =  %d\n", i, *(p + i));

        // *(p++)  == *p++  先获取*p  p=p+1
        // printf("arr[%d]  =  %d\n", i, *(p++));
        printf("arr[%d]  =  %d\n", i, *p++);
        /* code */
    }


    return 0;
}

7.3.2 指针数组

指针数组,它是数组,数组的每个元素都是指针类型

image-20240417200319393

7.3.3 数组名做函数参数

  • 数组名做函数参数,函数的形参本质上就是指针

在这里插入图片描述

7.4 字符数组与字符串

✨✨✨❤️7.4.1 字符数组与字符串区别(重点)

C语言中没有字符串这种数据类型,可以通过char的数组来替代

数字0(和字符 ‘\0’ 等价)结尾的char数组就是一个字符串,字符串是一种特殊的char的数组

在这里插入图片描述

✨✨如果char数组没有以数字0结尾,那么就不是一个字符串,只是普通字符数组

在这里插入图片描述

7.4.2 字符串的输入输出

  • 由于字符串采用了’\0’标志,字符串的输入输出将变得简单方便
#include <stdio.h>

int main()
{
    char str[100];

    printf("input string1: ");
    // scanf("%s",str) 默认以空格分隔
    // 可以输入空格
    gets(str);
    printf("output: %s\n", str);

    return 0;
}

✨✨✨❤️7.4.3 字符指针(重点)

  • 字符指针可直接赋值为字符串,保存的实际上是字符串的首地址

  • 这时候,字符串指针所指向的内存不能修改,指针变量本身可以修改

在这里插入图片描述

7.4.4 字符串常用字符库

7.4.4.1 strlen

示例代码:

#include <string.h>
size_t strlen(const char *s);
功能:计算指定指定字符串s的长度,不包含字符串结束符‘\0’
参数:
	s:字符串首地址
返回值:字符串s的长度,size_tunsigned int类型,不同平台会不一样
#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "abcdefg";
    int n = strlen(str);
    printf("n = %d\n", n); // n = 7

    return 0;
}
7.4.4.2 strcpy

示例代码:

#include <string.h>
char *strcpy(char *dest, const char *src);
功能:把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去
参数:
	dest:目的字符串首地址,如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况
	src:源字符首地址
返回值:
	成功:返回dest字符串的首地址
	失败:NULL
#include <stdio.h>
#include <string.h>

int main() {
    char dest[20] = "123456789";
    char src[] = "hello world";
    strcpy(dest, src);
    printf("%s\n", dest); // hello world

    return 0;
}
7.4.4.3 strcat

示例代码

#include <string.h>
char *strcat(char *dest, const char *src);
功能:将src字符串连接到dest的尾部,‘\0’也会追加过去
参数:
	dest:目的字符串首地址
	src:源字符首地址
返回值:
	成功:返回dest字符串的首地址
	失败:NULL
#include <stdio.h>
#include <string.h>

int main() {
    char str[20] = "123";
    char *src = "hello world";
    strcat(str, src);
    printf("%s\n", str); // str = 123hello world

    return 0;
}
7.4.4.4 strcmp

示例代码:

#include <string.h>
int strcmp(const char *s1, const char *s2);
功能:比较 s1 和 s2 的大小,比较的是字符ASCII码大小。
参数:
	s1:字符串1首地址
	s2:字符串2首地址
返回值:
	相等:0
	大于:>0
	小于:<0
#include <stdio.h>
#include <string.h>

int main() {
    char *str1 = "hello world";
    char *str2 = "hello mike";

    if (strcmp(str1, str2) == 0) {
        printf("str1==str2\n");
    } else if (strcmp(str1, str2) > 0) {
        printf("str1>str2\n");		// str1>str2
    } else {
        printf("str1<str2\n");  
    }

    return 0;
}

7.5 字符串案例

自定义一个函数my_strlen(),实现的功能和strlen一样

示例代码:

#include <stdio.h>

// 函数定义
int my_strlen(char * temp) {
    // 定义一个累加个数的变量,初始值为0
    int i = 0;
    // 循环遍历每一个字符,如果是'\0'跳出循环
    while (temp[i] != '\0') {
        // 下标累加
        i++;
    }

    return i;
}

int main() {
    char *p = "hello";
    // 函数调用
    int n = my_strlen(p);
    printf("n = %d\n", n);

    return 0;
}

8. 复合类型(自定义类型)

8.1 结构体

✨✨✨❤️8.1.1 结构体的使用(重点)

示例代码:

#include <stdio.h>

struct stu {
    char name[20];
    int age;
    char sex[6];
};
// struct 结构体名 变量名;
struct stu s = { "mike",18,'nan' };

// 定义多个结构体数据
struct stu s1[] = {
    {"mike",18,'nan'},
    { "mike",18,'nan'},
    { "mike",18,'nan'},
    { "mike",18,'nan'}
};


// struct stu2 {
// 	char name[50];
// 	int age;
// }s2 = { "yoyo", 19 };


int main() {




    return 0;
}
#include <stdio.h>
#include <string.h>

struct ST {
    int age;
    int a;
    int b;
};
struct ST s = { 1,2,3 };

// struct ST s[] = {
//     {1,2,3},
//     {1,2,3},
//     {1,2,3}
// };
int main() {
    // 结果变量的     我们可以通过.操作结构体成员
    // char* p = &s;

    // printf("name = %s age = %d sex = %s\n", s.name, s.age, s.sex);

    // strcpy(s.name, "李四");
    // s.name = "李四";
    // printf("name = %s age = %d sex = %s\n", s.name, s.age, s.sex);

    // 结构体的大小
    //  结构体的大小 是由内部数据决定的   如果是空的结构  是由内部的数据类型决定的

    printf("大小=%d\n", sizeof(s));
    return 0;
}

8.1.2结构体做函数参数

8.1.2.1结构体值传参

传值是指将参数的值拷贝一份传递给函数,函数内部对该参数的修改不会影响到原来的变量

示例代码:

#include <stdio.h>

#include <string.h>
struct stu {
    char name[50];
    int age;
};

// 值传递  值传递 只在本函数内容 生效
void show_struct(struct stu s) {

    strcpy(s.name, "李四");
    printf("%s %d\n", s.name, s.age);
}

int main() {


    struct stu s = { "lisi",30 };

    show_struct(s);

    printf("%s %d\n", s.name, s.age);

    return 0;
}
8.1.2.2结构体地址传递

传址是指将参数的地址传递给函数,函数内部可以通过该地址来访问原变量,并对其进行修改。

示例代码:

#include <stdio.h>

#include <string.h>
struct stu {
    char name[50];
    int age;
};

// 地址传递  地址传递 
void show_struct(struct stu* s) {

    // 如果此时结构体是指针类型的  就不能使用.操作符  需要->操作符
    strcpy(s->name, "李四");
    printf("%s %d\n", s->name, s->age);
}

int main() {

    struct stu s = { "lisi",30 };

    show_struct(&s);

    printf("%s %d\n", s.name, s.age);

    return 0;
}

8.2共用体

8.2.1共用体的语法

  • 共用体union是一个能在同一个存储空间存储不同类型数据的类型
  • 共用体所占的内存长度等于其最长成员的长度,也有叫做共用体
  • 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用
  • 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖
  • 共用体变量的地址和它的各成员的地址都是同一地址

8.2.2共用体和结构体区别

  • 存储方式:

    • 结构体:结构体中的每个成员都占据独立的内存空间,成员之间按照定义的顺序依次存储

    • 共用体:共用体中的所有成员共享同一块内存空间,不同成员可以存储在同一个地址上

  • 内存占用:

    • 结构体:结构体的内存占用是成员变量占用空间之和,每个成员变量都有自己的内存地址
    • 共用体:共用体的内存占用是最大成员变量所占用的空间大小,不同成员变量共享同一块内存地址

示例代码:

#include <stdio.h>
union UN
{
    char ch;
    short sh;
    int i;
};


// ● 共用体所占的内存长度等于其最长成员的长度,也有叫做共用体
// ● 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用
// ● 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖
// ● 共用体变量的地址和它的各成员的地址都是同一地址

int main() {

    union UN u;

    // 查看共用体的长度
    printf("u == %d\n", sizeof(u));
    // 数据的地址   成员和共用体的地址 是一致的 
    printf("ch = %p\n sh = %p\n  i = %p\n  s=%p\n", &u.ch, &u.sh, &u.i, &u);

    u.i = 0x12345678;
    // int  4 字节  char  1字节  short  2字节 
    printf("== %0x\n  ===%0x\n", u.ch, u.sh);

    return 0;
}

8.2.3大小端判断:

		所谓的大端模式,是指数据的低位(就是权值较小的后面那几位)保存在内存的高地址中,而数据的高位,保存在内存的低地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;
		所谓的小端模式,是指数据的低位保存在内存的低地址中,而数 据的高位保存在内存的高地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

1)大端模式:

低地址 -----------------> 高地址

0x12  |  0x34  |  0x56  |  0x78

2)小端模式:

低地址 ------------------> 高地址

0x78  |  0x56  |  0x34  |  0x12

示例代码:

#include <stdio.h>
#include <string.h>
union un
{
    char ch;
    int s;
    /* data */
}un;


int main() {

    un.s = 0x12345678;
    printf("%p\n", un.ch);

    if (un.ch == 0x78) {
        printf("小端对齐\n");
    }
    else {
        printf("大端对齐\n");
    }
    return 0;
}

8.3枚举

  • 枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内

  • 语法格式:

enum  枚举名 { 枚举值表 };

在枚举值表中应列出所有可用值,也称为枚举元素

  • 枚举值是常量,不能在程序中用赋值语句再对它赋值
  • 枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2 …

示例代码:

#include <stdio.h>

enum Week {
    mon = 9, tue, wed, thu, fri, sat
};


enum bool{
    flase, true
};
// 枚举是值的罗列  0.1.2.3.4  所有的值都是在前面的基础上进行累加的  9  10 11   1 2 3 
int main() {

    printf("==%d\n", tue);

    // enum bool flag;
    // flag = true;

    if (true) {
        printf("flag为真\n");
    }

    return 0;
}

8.4 typedef

typedef为C语言的关键字,作用是为一种数据类型(基本类型或自定义数据类型)定义一个新名字,不能创建新类型。

示例代码:

#include <stdio.h>

#include <inttypes.h>

#define NUM  999
// gcc -E hello.c -o hello.i //处理文件包含,宏和注释 
// gcc -S hello.i -o hello.s //编译为汇编文件 
// gcc -c hello.s -o hello.o //经汇编后为二进制的机器指令
// gcc hello.o -o hello       //链接所用的到库

typedef int INNT;
typedef char int_8;
typedef short int_16;

// typedef  不是创作新的类型  给类型 进行重新命名
int main() {



    INNT a = 8807;



    printf("---%d\n", a);

    return 0;
}

#include <stdio.h>

// 类型起别名
typedef int INT;
typedef char BYTE;
typedef BYTE T_BYTE;
typedef unsigned char UBYTE;

// struct type 起别名 
// TYPE为普通结构体类型,PTYPE为结构体指针类型
typedef struct type {
    UBYTE a;
    INT b;
    T_BYTE c;
} TYPE, * PTYPE;

int main() {
    TYPE t;
    t.a = 254;
    t.b = 10;
    t.c = 'c';

    PTYPE p = &t;
    printf("%u, %d, %c\n", p->a, p->b, p->c);

    return 0;
}

9.内存管理

9.1C代码编译过程

  • 预处理

    • 宏定义展开、头文件展开、条件编译,这里并不会检查语法
  • 编译

    • 检查语法,将预处理后文件编译生成汇编文件
  • 汇编

    • 将汇编文件生成目标文件(二进制文件)
  • 链接

    • 将目标文件链接为可执行程序
gcc -E hello.c -o hello.i //处理文件包含,宏和注释 
gcc -S hello.i -o hello.s //编译为汇编文件 
gcc -c hello.s -o hello.o //经汇编后为二进制的机器指令
gcc hello.o -o hello      //链接所用的到库

1 预处理:预处理相当于根据预处理命令组装成新的 C 程序,不过常以 i 为扩展 名。 
2 编 译:将得到的 i 文件翻译成汇编代码 .s 文件。 
3 汇 编:将汇编文件翻译成机器指令,并打包成可重定位目标程序的 O 文件。 该文件是二进制文件,字节编码是机器指令。 
4 链 接:将引用的其他 O 文件并入到我们程序所在的 o 文件中,处理得到最终 的可执行文件

gcc -E zy.c -o z.i
gcc -S z.i -o z.s
gcc -C z.s -o z.o
gcc z.o -o z

9.2进程的内存分布

  • 对于一个C语言程序而言,内存空间主要由五个部分组成 代码区(text)、数据区(data)、未初始化数据区(bss),堆(heap) 和 栈(stack) 组成

    • 有些人直接把data和bss合起来叫做静态区或全局区

在这里插入图片描述

  • 代码区(text segment)

    • 加载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以在运行期间修改的。
  • 未初始化数据区(BSS)

    • 加载的是可执行文件BSS段,位置可以分开亦可以紧靠数据段,存储于数据段的数据(全局未初始化,静态未初始化数据)的生存周期为整个程序运行过程。
  • 全局初始化数据区/静态数据区(data segment)

    • 加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常量(只读))的数据的生存周期为整个程序运行过程。
  • 栈区(stack)

    • 栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间
  • 堆区(heap)

    • 堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

9.3堆区内存的使用

9.3.1malloc函数说明:
#include <stdlib.h>
void *malloc(size_t size);
功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。
	分配的内存空间内容不确定。
参数:
	size:需要分配内存大小(单位:字节)
返回值:
    成功:分配空间的起始地址
    失败:NULL
9.3.2free函数说明:
#include <stdlib.h>
void free(void *ptr);
功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址。
	对同一内存空间多次释放会出错。
参数:
	ptr:需要释放空间的首地址,被释放区应是由malloc函数所分配的区域。
返回值:无

示例代码:

#include <stdlib.h>
#include <stdio.h>

int main() {
    int i, *arr, n;
    printf("请输入要申请数组的个数: ");
    scanf("%d", &n);

    // 堆区申请 n * sizeof(int) 空间,等价int arr[n]
    arr = (int *)malloc(n * sizeof(int));
    if (arr == NULL) { // 如果申请失败,提前中断函数
        printf("申请空间失败!\n");
        return -1;
    }

    for (i = 0; i < n; i++){
        // 给数组赋值
        arr[i] = i;
    }

    for (i = 0; i < n; i++) {
        // 输出数组每个元素的值
        printf("%d, ", *(arr+i));
    }
    
    // 释放堆区空间
    free(arr);

    return 0;
}

9.4内存分布代码分析

9.4.1返回栈区地址

address of stack memory associated with local variable 'a' returned [-Wreturn-stack-address]

这个警告意味着你的代码中有一个函数返回了指向本地变量的地址,而本地变量通常是分配在栈上的。这样做可能会导致未定义的行为,因为在函数调用结束后,本地变量的内存空间将被释放,指向它们的指针就会变成无效。

要解决这个警告,你需要确保函数返回的指针不指向本地变量,或者在函数内部动态分配内存,并返回分配的内存地址。如果你需要返回本地变量的值,可以通过函数参数传递给调用者,而不是返回指向本地变量的指针。
#include <stdio.h>



int* func() {
    int a = 10;
    return &a; // 函数调用完毕,因为a是局部变量,a释放
}

int main() {
    int* p = NULL;
    p = func();
    *p = 100; // 操作野指针指向的内存,err

    printf("11111111111111111\n"); // 这句话可能执行不到,因为上一句话报错

    return 0;
}

// [{
// 	"resource": "/Users/doudou/Desktop/workspace/C语言基础/day06/代码/07-返回栈区的地址.c",
// 	"owner": "cpptools",
// 	"severity": 4,
// 	"message": "address of stack memory associated with local variable 'a' returned [-Wreturn-stack-address]",
// 	"source": "gcc",
// 	"startLineNumber": 5,
// 	"startColumn": 13,
// 	"endLineNumber": 5,
// 	"endColumn": 13
// }]

9.4.2 返回data区地址

  • 在函数内部使用static修饰的变量称为静态局部变量
  • 它在程序运行期间只被初始化一次,并且在函数调用结束后也不会被销毁
#include <stdio.h>

// static 修饰的局部变量 只会初始化一次  函数调用完成之后 不会销毁

int* func() {
    static int a = 10;
    return &a;
}

int main() {

    // 只定义 未初始化的数据  值 不确定  
    // 只能赋值  
    int age = 666;
    int* p = NULL;
    p = func();
    *p = 100;
    printf("%d\n", age);
    printf("11111111111111111\n");

    return 0;
}

// [{
// 	"resource": "/Users/doudou/Desktop/workspace/C语言基础/day06/代码/07-返回栈区的地址.c",
// 	"owner": "cpptools",
// 	"severity": 4,
// 	"message": "address of stack memory associated with local variable 'a' returned [-Wreturn-stack-address]",
// 	"source": "gcc",
// 	"startLineNumber": 5,
// 	"startColumn": 13,
// 	"endLineNumber": 5,
// 	"endColumn": 13
// }]
  • 普通局部变量和静态局部变量区别

    • 存储位置:
      • 普通局部变量存储在栈上
      • 静态局部变量存储在静态存储区
    • 生命周期:
      • 当函数执行完毕时,普通局部变量会被销毁
      • 静态局部变量的生命周期则是整个程序运行期间,即使函数调用结束,静态局部变量的值也会被保留
    • 初始值:
      • 普通局部变量在每次函数调用时都会被初始化,它们的初始值是不确定的,除非显式地进行初始化
      • 静态局部变量在第一次函数调用时会被初始化,然后保持其值不变,直到程序结束

示例代码:

#include <stdio.h>

void normal_func() {
    int i = 0;
    i++;
    printf("局部变量 i = %d\n", i);
}

void static_func() {
    static int j = 0;
    j++;
    printf("static局部变量 j = %d\n", j);
}

int main() {
    // 调用3次normal_func()
    normal_func();
    normal_func();
    normal_func();

    // 调用3次static_func()
    static_func();
    static_func();
    static_func();

    return 0;
}

结果:

局部变量 i = 1
局部变量 i = 1
局部变量 i = 1
static局部变量 j = 1
static局部变量 j = 2
static局部变量 j = 3

9.4.2返回堆区地址

示例代码:

#include <stdio.h>
#include <stdlib.h>

int* func() {
    int* tmp = NULL;
    // 堆区申请空间
    tmp = (int*)malloc(sizeof(int));
    *tmp = 100;
    return tmp; // 返回堆区地址,函数调用完毕,不释放
}

int main() {
    int* p = NULL;
    p = func();
    printf("*p = %d\n", *p); // ok

    // 堆区空间,使用完毕,手动释放
    if (p != NULL) {
        free(p);
        p = NULL;
    }
    printf("*p = %d\n", *p);

    return 0;
}

10.案例-学生信息管理系统

代码实现:

//
// Created by admin on 2024/4/17.
//


#include <stdio.h>
#include <string.h>

struct stu {
    char name[30];
    int  age;
    char sex[5];
};

struct stu s[100] = {
        {"大大怪下士",22,"女"},
        {"小小怪将军",23,"男"},
        {"玛卡巴卡",18,"男"}
};
// 帮助菜单显示函数定义
void help_menu() {
    printf("\n");
    printf("     欢迎使用本学生信息管理系统\n");
    printf("* ================================ *\n");
    printf("* 1. 添加                          *\n");
    printf("* 2. 显示                          *\n");
    printf("* 3. 查询                          *\n");
    printf("* 4. 修改                          *\n");
    printf("* 5. 删除                          *\n");
    printf("* 6. 退出                          *\n");
    printf("* ================================ *\n");
}

int n = 3;

// 添加
void add() {
    printf("请输入用户名:");
    scanf("%s",s[n].name);
    printf("请输入年龄:");
    scanf("%d",&s[n].age);
    printf("请输入性别:");
    scanf("%s",s[n].sex);
    printf("添加成功  姓名:%s\n, 年龄:%d\n, 性别:%s\n",s[n].name,s[n].age,s[n].sex);
    n++;
}
// 显示
void show() {
    for (int i = 0; i < n; ++i) {
        printf("姓名:%s, 年龄:%d, 性别:%s\n",s[i].name,s[i].age,s[i].sex);
    }
}
// 抽离代码
int find_index(char* p) {
    char sname[30];
    scanf("%s",sname);
    for (int i = 0; i < n; ++i) {
        if(strcmp(sname,s[i].name) == 0) {
            return i;
        }
    }
    return -1;
}
// 查询
void find() {
    printf("请输入需要查询的用户名:");
    int index = find_index(s[0].name);
    if (index == -1) {
        printf("未找到该用户");
    } else {
        printf("姓名:%s, 年龄:%d, 性别:%s\n",s[index].name,s[index].age,s[index].sex);
    }

}
// 修改
void edit() {
    printf("请输入要修改的用户名:");
    int index = find_index(s[0].name);
    if(index == -1) {
        printf("未找到该用户");
    } else {
        printf("修改前的用户信息:姓名:%s, 年龄:%d, 性别:%s\n",s[index].name,s[index].age,s[index].sex);
        printf("请输入修改后的用户名:");
        scanf("%s",s[index].name);
        printf("请输入修改后的年龄:");
        scanf("%d",&s[index].age);
        printf("请输入修改后的性别:");
        scanf("%s",s[index].sex);
        printf("修改后的用户信息:姓名:%s, 年龄:%d, 性别:%s",s[index].name,s[index].age,s[index].sex);
    }

}
// 删除
void dele() {
    printf("请输入要删除的用户名:");
    int index = find_index(s[0].name);
    if(index != -1) {
        if(index != -1) {
            s[index] = s[n-1];
        }
       n--;
        printf("删除成功",s[0].name);
    } else {
        printf("删除失败,未找到该用户",s[0].name);
    }
}




int main() {
    while (1) {
        help_menu();
        printf("请输入对应的数字:");
        int num;
        scanf("%d",&num);
        switch (num) {
            case 1:
                add();
                break;
            case 2:
                show();
                break;
            case 3:
                find();
                break;
            case 4:
                edit();
                break;
            case 5:
                dele();
                break;
        }
        if(num == 6) {
            printf("程序退出");
            break;
        }
        if(num !=1 && num !=2 && num !=3  && num != 4 && num != 5 && num != 6) {
            printf("指令输入错误请重新输入!\n");
        }
    }
    return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到