C++(week2):C语言中高级

文章目录

    • (八) 指针
      • 0.概念
      • 1.指针基础
        • (1)指针的声明
        • (2)指针的两个基本操作
          • ①取地址运算符 &
          • ②解引用运算符 *
        • (3)野指针
          • ①野指针
          • ②空指针
          • ③指针变量的赋值 vs 指针变量指向对象的赋值
        • (4)指针的应用
          • ①指针作为参数进行传递
          • ②指针作为返回值
          • ③拓展:栈帧
        • (5)常量指针、指针常量
        • (6)传入参数、传出参数
      • 2.指针与数组
        • (1)指针的算数运算:加法、减法、比较
        • (2)指针和数组的关系
          • ①用指针处理数组:指针代替索引
          • ②数组可以退化为指向数组第一个元素的指针
          • ③指针也支持取下标运算
        • (3)*与++的组合
        • (4)指针支持的操作
      • 3.指针的高级应用
        • (1)动态内存分配
          • 内存分配函数:malloc、calloc、realloc
          • ②空指针 NULL、通用指针
          • ③动态分配数组:vector
        • (2)释放内存空间:free
        • (3)动态分配结构体
        • (4)二级指针:指向指针的指针
        • (5)函数指针:指向函数的指针
    • (九) 字符串
      • 0.总纲
        • (1)C字符串的遍历
          • ①数组下标
          • 指针
          • ③注意
          • ④遍历字符串的三个效率级别
      • 1.字符串常量 (字符串字面值)
        • (1)概念
        • (2)字符串字面值的三种书写方式
        • (3)内存模型
        • (4)字符串字面值支持的操作
      • 2.字符串变量
        • (1)声明字符串变量并赋初始值
        • (2)字符数组 vs 字符指针
      • 3.读 / 写字符串
        • (1)读:使用scanf和gets读字符串
          • ①scanf+ %s:读取一个单词
          • ②gets():从stdin中读取一整行数据,存入字符数组。并将'\n'替换为'\0'
          • ③fgets()
        • (2)写:使用printf和puts写字符串
          • ①printf + %s
          • ②puts()
      • 4.C字符串的操作:C语言字符串库
        • (1)strlen
          • 惯用法:遍历字符串 / 搜索字符串末尾
        • (2)strcpy
          • ①strcpy(s1, s2)
          • ②strncpy(s1, s2, count)
          • 惯用法:复制字符串 (空字符也复制)
        • (3)strcat
          • ①strcat(s1, s2)
          • ②strncat(s1, s2, count)
        • (4)strcmp
      • 5.字符串数组
        • (1)二维字符数组
        • (2)字符指针数组
      • 6.命令行参数
      • 7.练习
        • (1)逆序输出字符串
        • (2)回文字符串
    • (十) 结构体、枚举
      • 1.结构体 struct
        • (1)结构体变量的声明和初始化
        • (2)结构体的内存模型
        • (3)结构体的操作:获取成员、赋值
        • (4)右箭头运算符`->` 的由来
        • (5)给结构体起别名
        • (6)匿名结构体
        • (7)练习
      • 2.枚举 enum

(八) 指针

0.概念

①计算机最小寻址单位:字节
②变量的地址:变量首字节的地址
③指针:就是地址
④指针变量:存储地址值的变量
⑤野指针:不知道指向哪个对象的指针
⑥空指针:不指向任何对象的指针
⑦常量指针和指针常量
⑧传入参数和传出参数
⑨通用指针类型 void*


1.指针基础

(1)指针的声明

1.int *pint* p
*说明了p是指针

2.变量名:p,类型:int*

3.注意事项:声明指针变量时,需要指定它指向对象的类型
int是指向对象的类型:①说明对象所占内存大小 ②如何解释那片内存空间 (说明了对象的类型)


(2)指针的两个基本操作
①取地址运算符 &

在这里插入图片描述


②解引用运算符 *

在这里插入图片描述

0.示例
i:直接访问,逻辑上访问内存一次
*p:间接访问,逻辑上访问内存两次

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void) {
	int i = 1;
	int* p = &i;

	printf("*p = %d\n", *p);

	*p = 2; //*p 是 i 的别名,有读写权限
	printf("i = %d\n", i);

	return 0;
}

(3)野指针
①野指针

(1)野指针不知道指向哪个对象(哪块数据)

(2)对野指针进行解引用,是未定义行为

(3)野指针的两种表现形式

int* p;         //1.不初始化
int* q =0xABCD; //2.用一个整数赋值

(4)正确地给指针变量赋值的两种方式

int* p = &i; //1.
int* q = p;  //2.
p = NULL;    //2.

在这里插入图片描述


②空指针

(1)空指针(NULL):不指向任何对象的指针,不指向任何有效的内存地址
(2)不能对空指针进行解引用


③指针变量的赋值 vs 指针变量指向对象的赋值

①p = q
②*p = *q


(4)指针的应用
①指针作为参数进行传递

好处:在被调函数中可以修改主调函数中变量的值,解引用

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

void swap(int* p, int* q) {
	int temp = *p;
	*p = *q;
	*q = temp;
}

int main(void) {
	int a = 3, b = 4;
	printf("a = %d, b = %d\n", a, b);
	swap(&a, &b);
	printf("a = %d, b = %d\n", a, b);
	return 0;
}

在这里插入图片描述


②指针作为返回值

教训:不要返回指向当前栈帧区域的指针 (因为返回以后,该栈帧就出栈了,变量被销毁)

在这里插入图片描述


③拓展:栈帧

栈帧 esp、ebp

栈帧里存储的是:函数调用相关的信息:形参、局部变量、返回地址


(5)常量指针、指针常量

const本质:限制变量的写权限

这里const是限制指针变量p的写权限


1.不加const,正常情况
在这里插入图片描述


2.const int* p (pointer to const)
对内存1有写权限,但对内存2没有写权限。
即可以修改指针的指向,但不能通过 *p 对所指变量的值进行修改。(但变量i自身可修改)在这里插入图片描述


3.int* const p (const pointer)
对内存1没有写权限,但对内存2有写权限。
即不能修改指针的指向,但可以通过 *p 修改所指对象的值
在这里插入图片描述


4.const int* const p (const pointer to const)
对内存1和内存2都没有写权限。
即不能修改指针的指向、不能通过 *p 改写变量的值
在这里插入图片描述


(6)传入参数、传出参数

1.传入参数:const int* p
在函数里面,不能够通过指针变量修改指针指向的对象
在这里插入图片描述


2.传出参数:int* p
在被调函数中可通过指针变量修改主调函数中指向的对象的值,可替代返回值来用 (C语言返回值只能返回一个值,但指针修改可修改多个值,即通过传出参数可代替多个返回值)
在这里插入图片描述
在这里插入图片描述


传入参数和传出参数,指的都是指针变量


2.指针与数组

(1)指针的算数运算:加法、减法、比较

1.指针的加法
①指针 + 整数:指针向右偏移几个单位

p = p+3; //指针向右偏移3个单位

2.指针的减法
①指针减去整数n,代表指针向左偏移n个单位

p  = p-3; //指针向左偏移3个单位

②两个指针相减,结果为一个整数,相隔几个单位

int n = p-q;

3.指针的比较运算
在这里插入图片描述


(2)指针和数组的关系

概念:
数组是一片连续的内存空间,并被划分为一个个大小相等的小空间
指针与另一个对象进行关联


①用指针处理数组:指针代替索引

指针处理数组(早期C语言)

现代的编译器会把for顺序处理在编译层面翻译成第一种写法,避免了乘法运算
在这里插入图片描述


②数组可以退化为指向数组第一个元素的指针

在必要的时候,数组可以退化为指向数组第一个元素的指针
退化:&arr[0] 就可以写为 arr

数组会退化为指针的情景:
①数组作为参数传递: fun(arr)
②数组给指针变量赋值时:int* p = arr (数组在赋值表达式的右边,即数组进行赋值运算时作为右值)
③数组参与算术运算: arr+3


在这里插入图片描述


③指针也支持取下标运算

p[i] 等价于 *(p+i)

p[i] 等价于 *(p+i),等价于 *(i+p) 等价于i[p]
故,防御性编程:i[arr]

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

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

	//1.指针也支持取下标运算: p[i]
	int* p = arr;
	for (int i = 0; i < 5; ++i) {
		printf("%d ", p[i]);
	}
	printf("\n");
	
	//2.防御性编程: i[arr]
	for(int i = 0; i < 5; ++i){
		//printf("%d ", arr[i]); //arr[i] 等价于 *(arr+i)
		printf("%d ", i[arr]);   //i[arr] 等价于 *(i+arr)
	}
	printf("\n");

	return 0;
}

(3)*与++的组合
表达式表达式的值副作用
*p++*(p++)*pp自增
(*p)++*p*p自增
*++p*(++p)*(p+1)p自增
++*p++(*p)(*p)+1*p自增

*p++*(p++) :表达式值为 *p,副作用是 p自增 (最常见)
(*p)++ : 表达式值为 *p,副作用是 *p自增
*++p*(++p):表达式值为 *(p+1),副作用是p自增
++*p++(*p):表达式值为 (*p)+1,副作用是 *p自增


在这里插入图片描述


(4)指针支持的操作

①解引用 *:通过指针变量,获取它指向的对象
②算数运算:加整数、减整数、减指针、自增、自减、比较运算(==、!=、>、>=、<、<=)
③取下标 []


3.指针的高级应用

(1)动态内存分配

1.为什么要在堆上分配空间、为什么需要动态内存分配?(why)
①栈帧的大小是在编译期间确定的,栈空间不能存放动态大小的数据,如vector
栈空间比较小,主线程8MB,其他线程2MB。所以栈上不能存很大的数据。
③每个线程都有自己的栈,多线程共享的数据,最好不要存在栈上,应该存在堆上


内存分配函数:malloc、calloc、realloc

1.如何申请堆空间? (How)
答:使用内存分配函数

2.头文件:#include <stdlib.h>

在这里插入图片描述

(1)malloc
malloc:memory allocate,内存分配函数

分配连续内存的大小

int* p = malloc(sizeof(int) * 100);

(2)calloc
clear + allocate:申请空间,并清零(全部赋值为0)。(空指针NULL值也是0)

calloc为 num_elements 个大小为 element_size 的元素分配内存空间,并将所有的位初始化为 0。如果函数成功,它将返回一个指向已分配内存的指针。如果失败,它将返回 NULL。

int* p = calloc(个数, sizeof(类型));

(3)realloc
调整先前分配的内存块大小。如果重新分配内存大小成功,返回新内存块的指针,否则返回空指针。(旧内存块不会被释放)
缩容是直接截断。尽可能地原地扩容,扩充的内存是未初始化的

void* reallloc(void* ptr, size_t size);

在这里插入图片描述


②空指针 NULL、通用指针

1.NULL

2.void * p 通用指针
作用:C语言中,通用指针 void * 可以与其他任意类型的指针 相互转化。也即 void * 指向对象的类型还不确定,不能直接操作(解引用、自增、加法等)通用指针。


③动态分配数组:vector

1.动态数组的实现

typedef int E;

typedef struct{
	E* elements;    //指向堆空间的数组
	int capacity;   //容量
	int size;       //实际个数
} Vector;

在这里插入图片描述


2.跨文件编写程序

依赖接口,不要依赖具体的实现 (因为实现是变化的,接口一般是固定的、稳定的)
接口:*.h、Interface、抽象类
接口中存放:类型定义和API的声明


3.头文件
(1)头文件中存放:类型的定义、API的声明
API的声明,可以给用户使用的。
但实现时不希望用户直接使用的函数(实现),就不要放到头文件中

(2)两种头文件
" " 自己写的头文件:搜索路径:当前目录 (若找不到) -> 系统头文件包含目录
<> 搜索路径:系统头文件包含目录


(3)依赖关系图
在这里插入图片描述


(2)释放内存空间:free

1.垃圾(garbage):不可再被访问的内存块
内存泄漏(memory leak):程序中存在垃圾


2.垃圾回收器:
(1)①有垃圾回收器的语言:Java、Python、Go
②手动管理垃圾的语言:C、C++、rust

(2)①垃圾回收器的特点:减轻程序员的负担、但引入了不确定因素。清除垃圾时会stop the world,不适合写实时系统。[实时系统:在某个确定的时间内完成某个任务]
②没有垃圾回收器:
C:free
C++:delete、析构函数、智能指针、RAII
Rust:所有权机制


3.free的问题

void free(void *ptr);

悬空指针(野指针的一种)
对堆上内存释放一次后,p就变成了悬空指针

(1)double free
free两次

(2)use after free
free后再使用

(3)忘记free
造成内存泄露

结论:当堆上的数据不再使用时,应该有且只释放一次


在这里插入图片描述


(3)动态分配结构体

见链表


(4)二级指针:指向指针的指针

Q:传一级指针还是传二级指针?
A:想修改哪个变量,就传那个变量的地址(指针)
①想修改指针指向的对象,传一级指针
想修改指针的指向 (修改指针变量的值),传二级指针


(5)函数指针:指向函数的指针

1.声明函数指针变量:

int (*p) (int, int); //参数类型是(int,int), 返回值类型是int

区分:声明函数

int* p2(int, int);

2.初始化 / 赋值:foo、&foo

int (*p1)(int, int) = foo;
int (*p2)(int, int) = &foo;

3.通过函数指针调用函数

p(a,b);
(*p)(a,b);

4.作用 / 应用场景
(1)函数式编程 (传递函数,返回函数)
C语言通过函数指针,支持函数式编程
好处:分解任务,解耦合
(2)编写非常通用的函数 (功能非常强大的函数),如qsort()

举例:qsort函数
在这里插入图片描述

函数指针调用的函数,称为钩子函数,如cmp( )
函数指针实现了:分解任务,解耦合。(将排序和比较分开了。若没有函数指针调用函数,则比较的逻辑要写死在qsort中,类型要固定,无法实现通用的功能)



(九) 字符串

0.总纲

①C语言没有字符串类型!
②C语言中的字符串,依赖字符数组存在 (字符数组最后一个存\0,才是C字符串)
③C语言中的字符串,是一种逻辑类型


(1)C字符串的遍历

在C语言中,字符串是一系列以空字符(‘\0’)结尾的字符数组。可以通过迭代每个字符来遍历字符串,有以下两种常用的方式:

①数组下标

1.使用数组下标:

#include <stdio.h>

int main() {
    char str[] = "Hello, World!";
    for (int i = 0; str[i] != '\0'; ++i) {
        printf("%c\n", str[i]);
    }
    return 0;
}

指针

2.使用指针:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void) {
	char str[] = "Hello";
	char* p = str;
	while (*p != '\0') {
		printf("%c", *p);
		p++;
	}
	return 0;
}

完整代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void) {
	char str[] = "Hello,";
	char* p = str;
	while (*p != '\0') {
		printf("%c", *p++); 
	};

	char str2[] = "World!";
	for (int i = 0; str2[i] != '\0'; i++) {
		printf("%c", str2[i]);
	}

	return 0;
}

③注意

①C语言中的字符串,以空字符\0结尾!
②C语言中求字符串的长度,需要从头遍历,是O(n)的时间复杂度。

length是O(1)的时间复杂度
strlen是O(n)的时间复杂度


④遍历字符串的三个效率级别

1.最糟糕的写法:

for (int i = 0; i < strlen(str); i++) { ... }

因为strlen()本身就是O(n)的复杂度,又嵌套在for里,使得这个遍历字符串的时间复杂度达到了O(n²)


2.稍微好一些的写法:

int len = strlen(str);
for (int i = 0; i < len; i++) { ... }

3.比较好的写法:数组下标 + ‘\0’

for (int i = 0; str[i] != '\0'; i++) { ... }

4.最好的写法:指针操作字符串

char* p = str;
while (p){
	...
	p++;
}



1.字符串常量 (字符串字面值)

(1)概念

字符串常量(字符串字面值),表示双引号括起来的字符序列


(2)字符串字面值的三种书写方式
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(void) {
	//字符串字面值的三种书写方式
	//1.最普通的书写方式
	printf("I love xixi  --  From peanut\n");
	
	//2.换行 (但是换行后不忽略空白字符)
	printf("I love xixi \
 --  From peanut\n");

	//3.字符串拼接
	printf("I love xixi"
		   "  --  From peinut\n");

	return 0;
}

当两个或更多个字符串字面值相邻时 (仅用空白字符分割),编译器会把
它们合并成一个


举例:模拟 输出图形
在这里插入图片描述

//打印菱形
printf("   *\n"
	   "  * *\n"
	   " *   *\n"
	   "*     *\n"
	   " *   *\n"
	   "  * *\n"
	   "   *\n"   );

传统的两层for循环,可读性差,性能低。


(3)内存模型

1.字符串字面值,存放在代码段,不可被修改。

代码段存放:指令、字符串字面值

2.C语言中字符串以\0结尾

void 空类型 (没有值)
\0 空字符 (C字符串结束标志)
NULL 空指针 (不指向任何对象)
"" 空字符串


(4)字符串字面值支持的操作

常量数组支持的操作,字符串字面值都支持。(可以把字符串字面值看作是常量数组)

"字符串内容"可作为数组名,支持取下标运算

char* p = "ABC" + 1; //"ABC"是数组名,进行算术运算时退化为首元素的指针,+1就是向右偏移1个单位
printf("%c\n",*p);   //输出B
//十六进制转换
char digit_to_hex(int dight){
	return "0123456789ABCDEF"[dight];
}

2.字符串变量

(1)声明字符串变量并赋初始值

两种方式,第二种是第一种的语法糖

//声明字符串变量,并赋初始值
char str0[] = { 'H','e','l','l','o' };      //字符数组
char str1[] = { 'H','e','l','l','o','\0'};  //字符串: 数组的初始化式 { }
char str2[] = "Hello";						//字符串: 语法糖,"Hello"是数组初始化式的简写形式

建议:
①如果初始化字符数组,用数组的初始化式,{'H','e','l','l','o','\0'}
②如果初始化字符串,用双引号语法糖,"Hello"

char类型,0值就是空字符\0



例:

char s[10] = {'H','e','l','l','o','\0'}

①字符数组长度:10
②字符串长度:5
③字符串占用的空间:6



(2)字符数组 vs 字符指针
char str[] = "hello"; //"hello":数组的初始化式
char* p = "hello";    //"hello":字符串字面值

在这里插入图片描述


3.读 / 写字符串

(1)读:使用scanf和gets读字符串
①scanf+ %s:读取一个单词
scanf("%s",str);  //数组名退化为指针,就是地址。不需要加取地址运算符
char str[MAX_SIZE];

scanf("%s", str);  //scanf + %s,会忽略前置空白字符,遇到空白字符停止
printf("%s\n", str);

(1)%s的匹配规则:忽略前置空白字符,读取字符填入字符数组,遇到空白字符结束。

(2)缺点:
①不能够存储空白字符
②不会检查数组越界 (读多了,超过了数组长度,数据覆盖了数组后面的内存空间)


②gets():从stdin中读取一整行数据,存入字符数组。并将’\n’替换为’\0’

1.匹配规则
gets()不会忽略前置空白字符,一次读取一行,遇到空白字符不结束,直到遇到换行符\n才结束,并将’\n’替换为’\0’

2.缺点:不会检查数组越界。(若要读取的字符串长度超过了字符数组的长度,也会照样写内存,覆盖数组后面的内存的数据,造成数组越界)


③fgets()
fgets(str,sizeof(str),stdin);  //str,数组长度,从哪里读入

注意事项:
①fgets()会检查数组越界 (对比第二个数值)
②会保存换行符’\n’,并在后面添加’\0’。以\n\0结尾


(2)写:使用printf和puts写字符串
①printf + %s

输出一个字符串。(从头输出到空字符结束,空字符\0标志着字符串的结束)

%.ps,精度p:最多输出p个字符

char str[] = "Hello world";
printf("%s\n", str);
printf("%.5s\n", str);  //%.ps

②puts()

①puts():输出一个字符串
②puts()效率高于printf,不用处理格式化输出

printf("%s\n",str);
puts(str);   //两种写法等价,puts()会自动添加换行符。puts()效率更高

4.C字符串的操作:C语言字符串库

头文件 <string.h>


(1)strlen

strlen():求字符串的长度


惯用法:遍历字符串 / 搜索字符串末尾
while(*p != '\0'){
	p++;
}

在这里插入图片描述

②sizeof() 和 strlen() 的区别:

char str1[10] = "abc";
char str2[  ] = "abc";

printf("sizeof(str1) = %d\n", sizeof(str1));  //10
printf("sizeof(str2) = %d\n", sizeof(str2));  //4
printf("strlen(str1) = %d\n", strlen(str1));  //3
printf("strlen(str2) = %d\n", strlen(str2));  //3

(2)strcpy
①strcpy(s1, s2)
strcpy(str,"Hello");

②strncpy(s1, s2, count)
strncpy(str,"Hello world",MAXLINE-1);
str[MAXLINE-1] = '\0';

惯用法:复制字符串 (空字符也复制)
while(*s1++ = *s2++)
	;

完整版:

char* p = s1;
while(*s1++ = *s2++)
	;
return p;

(3)strcat
①strcat(s1, s2)

concatenate v.连接

1.strcat也可能数组越界


②strncat(s1, s2, count)
strcat(s1, "world\n"), MAXLINE - strlen(s1) -1);  //1 for '\0'
s1[MAXLINE - 1] = '\0';  //记不住它到底会不会自动添加空字符,就一律手动添加一次

(4)strcmp

strcmp返回值是s1 - s2

在这里插入图片描述



5.字符串数组

字符串数组,即字符数组的数组,即二维字符数组

(1)二维字符数组

在这里插入图片描述

" "里的是一维字符数组的初始化式

弊端:
①空间浪费:字符串之间长度差异大,短的后面就要存很多’\0’,造成空间浪费
②不灵活:如交换字符串、字符串进行排序

在这里插入图片描述


(2)字符指针数组

字符指针数组,来存储字符串数组

在这里插入图片描述

" "里的是字符串字面值

在这里插入图片描述

缺点:多了一个存储字符数组

优点:
①节省了空间
②灵活

#include <stdio.h>

int main(void) {
	//planets是数组,存储的元素是 char*, 字符指针 ,即指向字符的指针
	char* planets[] = { "Mercury", "Venus", "Earth", "Mars",
					    "Jupiter", "Saturn", "Uranus", "Neptune" };

	char* p = "Edward";  
	planets[2] = p;   
	int len = strlen(planets);
	for (int i = 0; i < len; i++) {
		printf("%s\n", planets[i]);
	}

	return 0;
}

6.命令行参数

1.命令行参数是什么?
操作系统调用可执行程序时,可以给它(main函数)传递的参数

2.要接收命令行参数,要修改main函数的参数:

int main(int argc, char* argv[]) {
	//argc:argument count,命令行参数的个数
	//argv:argument vector,命令行参数,字符串

	//argv[0]是第一个命令行参数,一般为可执行程序的路径+程序名
	printf("argc = %d\n", argc);
	printf("argv[0] = %s\n\n", argv[0]); 

	for (int i = 0; i < argc; i++) {
		printf("argv[%d] = %s\n", i, argv[i]);
	}

	return 0;
}

3.命令行参数的转换
sscanf是string scanf

int main(int argc, char* argv[]) {
	int n;
	float f;

	sscanf(argv[1], "%d", &n);
	sscanf(argv[2], "%f", &f);

	for (int i = 0; i < argc; i++) {
		printf("argv[%d] = %s\n", i, argv[i]);
	}

	return 0;
}

在这里插入图片描述

4.如何在VS中设置命令行参数
项目[右键]→属性→调试→命令参数→[以空格间隔]→应用→确定

5.命令行参数和stdin的区别?
命令行参数在程序执行之前,stdin在程序执行中

6.命令行参数有什么作用?
①编写通用的程序 cp a.txt b.txt
②改变程序的行为 ls -l (传递不同的参数,程序展示不同的行为)


7.练习

(1)逆序输出字符串

CDay07第一题


(2)回文字符串

CDay07第二题



(十) 结构体、枚举

1.C语言最重要的三个组成部分:函数、指针、结构体

2.C语言中的聚合变量
数组 (同类的元素)
结构体 (不同类的成员)

3.对象:
①属性:静态数据
②方法:行为

4.C语言结构体中只有属性,没有方法。
但是C可以通过指针,实现类似方法的功能

在这里插入图片描述


1.结构体 struct

(1)结构体变量的声明和初始化

1.定义结构体
结构体类型,是自定义类型

struct student{
	int id;
	char name[25];
	int age;
	char gender;
	int chinese;
	int math;
	int english;
};

2.声明并初始化变量

struct student s1 = {1, "xixi", 'F', 100}; //按位置赋值
struct student s2 = {2, "peanut", 'M'};   //未初始化的成员,默认为0值

(2)结构体的内存模型

(1)一片连续的内存空间
(2)按声明的顺序依次存放每一个成员
(3)成员之间 (在结构体变量的中间或后面),可能会进行填充,为了内存对齐

在这里插入图片描述

四个字节四个字节的传输时,如果不对齐,则int会被分割开,要读两次。
对齐的目的是为了减少读的次数更快地访问数据


(3)结构体的操作:获取成员、赋值

(1)获取成员 .
(2)赋值

s2 = s1;

赋值的本质 是 内存空间的复制。
在结构体很大时,复制结构体的开销很大。
传递参数和返回值时都会复制结构体,考虑只传递结构体的指针。

在这里插入图片描述

数组不支持赋值运算,而结构体支持赋值运算


(4)右箭头运算符-> 的由来

传递或返回一个结构体时,会导致结构体的复制。当结构体很大时,会增加很多开销。
所以C程序员往往会传递结构体的指针。
现在s是指针,*s才是结构体。则 s. 就要写成 (*s). ,先解引用 (结构体指针解引用为结构体),再获取成员
为了简便书写,将 (*s). 写作 s-> (语法糖)

格式:  指向结构体的指针 -> 结构体的成员

完整代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

struct student{
	int id;     //4
	char name[25];  //25
	char gender;    //1
	int chinese;    //4
	int math;		//4
	int english;	//4
};

void print_stu_info(const struct student* s) {
	/*printf("%d %s %c %d %d %d\n",(*s).id, (*s).name, (*s).gender,
								 (*s).chinese,(*s).math,(*s).english);*/
	//语法糖: 用 s-> 代替 (*s).
	printf("%d %s %c %d %d %d\n", s->id, s->name, s->gender,
								  s->chinese, s->math, s->english);
}

int main(void){
	//声明并初始化变量
	struct student s1 = { 1, "xixi",  'F', 100, 100, 100 };
	struct student s2 = { 2,"peanut", 'M' };
	
	print_stu_info(&s1); //s1是结构体, &s1是结构体的指针
	print_stu_info(&s2);

	return 0;
}

(5)给结构体起别名
typedef 类型 别名;
typedef struct student{
	int id;     
	char name[25];  
	char gender;   
	int chinese;   
	int math;	
	int english;
} Student;

//请不要给指针类型起别名! 如下文的 *pStudent

typedef struct student{
	int id;     
	char name[25];  
	char gender;   
	int chinese;   
	int math;	
	int english;
} Student, *pStudent;

(6)匿名结构体

匿名结构体:
没有标签,需要搭配 typedef 来形成新名字

typedef struct {  //匿名结构体
	int id;     
	char name[25];  
	char gender;   
	int chinese;    
	int math;	
	int english;	
} Student;

(7)练习

结构体指针 Student* p 的作用:避免复制整个结构体
结构体指针数组 Student* pstudents[5] 的作用:通过交换指针数组中指针的顺序,堆学生按总成绩进行排序,避免了对结构体本身进行排序(移动),减少了开销

在这里插入图片描述

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

typedef struct student {
	int number;
	char name[25];
	int chinese;
	int math;
	int english;
} Student;

void print_stu_info(const Student* p) {
	printf("%d %s %d %d %d\n", p->number, p->name, p->chinese, p->math, p->english);
}

void highest(Student stu[], int n) {
	int max_chinese = stu[0].chinese, max_math = stu[0].math, max_english = stu[0].english;
	int index_chinese = 0, index_math = 0, index_english = 0;
	
	for (int i = 1; i < 5; ++i) {
		if (stu[i].chinese > max_chinese) {
			max_chinese = stu[i].chinese;
			index_chinese = i;
		}
		if (stu[i].math > max_math) {
			max_math = stu[i].math;
			index_math = i;
		}
		if (stu[i].english > max_english) {
			max_english = stu[i].english;
			index_english = i;
		}
	}

	printf("语文最高分的同学的信息:");
	print_stu_info(&stu[index_chinese]);
	printf("数学最高分的同学的信息:");
	print_stu_info(&stu[index_math]);
	printf("英语最高分的同学的信息:");
	print_stu_info(&stu[index_english]);
}

void average(Student stu[], int n) {
	float sum_chinese = 0, sum_math = 0, sum_english = 0;
	for (int i = 0; i < n; ++i) {
		sum_chinese += stu[i].chinese;
		sum_math += stu[i].math;
		sum_english += stu[i].english;
	}
	printf("语文平均分:%.1f\n", sum_chinese / n);
	printf("数学平均分:%.1f\n", sum_math / n);
	printf("英语平均分:%.1f\n", sum_english / n);
}

int sum_score(const Student* p) {
	return p->chinese + p->math + p->english;
}

void Bubble_des(Student* A[], int n) {
	for (int i = 0; i < n - 1; ++i) {
		for (int j = 0; j < n - 1 - i; ++j) {
			if (sum_score(A[j]) < sum_score(A[j + 1])){     //对于降序,条件改为小于
				Student* temp = A[j];
				A[j] = A[j + 1];
				A[j + 1] = temp;
			}
		}
	}
}

int main(void) {
	Student students[5];   //结构体数组,大小为5

	for (int i = 0; i < 5; ++i) {
		scanf("%d%s%d%d%d", &students[i].number, students[i].name,&students[i].chinese, 
			                &students[i].math, &students[i].english);
	}
	printf("\n");

	highest(students, 5);
	printf("\n");

	average(students, 5);
	printf("\n");

	//结构体 指针数组
	Student* pstudents[5] = { students, students+1, students+2 ,students+3, students+4 };

	Bubble_des(pstudents, 5);
	for (int i = 0; i < 5; ++i) {
		print_stu_info(pstudents[i]);
	}

	return 0;
}

2.枚举 enum

1.枚举类型,用来表示离散值,如 类型和状态。枚举类型是整数类型。


2.定义枚举类型

// 定义枚举类型
enum Suit{
    // 罗列枚举值
    DIAMONDS,    //0  默认从0开始
    HEARTS  = 4, //4
    SPADES = 10, //10
    CLUBS        //11 递增    
};

3.给枚举类型起别名

typedef enum{
    DIAMONDS,
    HEARTS,
    SPADES,
    CLUBS
} Suit;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/622429.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用java远程提交flink任务到yarn集群

使用java远程提交flink任务到yarn集群 背景 由于业务需要&#xff0c;使用命令行的方式提交flink任务比较麻烦&#xff0c;要么将后端任务部署到大数据集群&#xff0c;要么弄一个提交机&#xff0c;感觉都不是很离线。经过一些调研&#xff0c;发现可以实现远程的任务发布。…

为什么3d重制变换模型会变形?---模大狮模型网

3D建模和渲染过程中&#xff0c;设计师经常会遇到一个让人头疼的问题&#xff0c;那就是模型在进行重制变换后出现的意外变形。这种变形不仅影响了模型的外观和质量&#xff0c;也给设计工作带来了额外的麻烦。本文将深入探讨3D模型进行重制变换后出现变形的原因&#xff0c;帮…

Hystrix服务熔断

服务熔断 熔断机制是应对雪崩效应的一种微服务链路保护机制。当某个微服务不可用或者响应时间太长时&#xff0c; 会进行服务降级&#xff0c;进而熔断该节点微服务的调用&#xff0c;快速返回“错误”的响应信息。当检测到该节点微 服务调用响应正常后恢复调用链路。 在Spri…

【Java】HOT100+代码随想录 动态规划(上)背包问题

目录 理论基础 一、基础题目 LeetCode509&#xff1a;斐波那契数 LeetCode70&#xff1a;爬楼梯 LeetCode746&#xff1a;使用最小花费爬楼梯 LeetCode62&#xff1a;不同路径 LeetCode63&#xff1a;不同路径ii LeetCode343&#xff1a;整数拆分 LeetCode96&#xff1a;不…

海外动态IP:揭秘其背后的技术与应用

在数字化时代&#xff0c;网络技术的发展日新月异&#xff0c;其中海外动态IP作为网络通信技术的重要一环&#xff0c;逐渐走进公众视野。海外动态IP不仅为跨国企业提供了灵活的网络接入方案&#xff0c;还为个人用户带来了更多样化的网络体验。本文将深入探讨海外动态IP的技术…

【Docker学习】重启容器的docker restart

命令&#xff1a; docker container restart 描述&#xff1a; 重启一个或多个容器 用法&#xff1a; docker container restart [OPTIONS] CONTAINER [CONTAINER...] 别名&#xff1a; docker restart(docker的一些命令可以简写&#xff0c;docker restart就等同于docker cont…

树莓派|连接CSI接口摄像头+opencv

CSI&#xff08;Camera Serial Interface&#xff09;接口摄像头是一种常见的嵌入式系统或移动设备中使用的摄像头接口。它通常用于与处理器或图像传感器进行直接连接&#xff0c;实现高速的图像数据传输。 CSI接口摄像头具有以下特点&#xff1a; 高速传输&#xff1a;CSI接口…

免翻,剪映出品的AI作图和AI视频官网免费体验!

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 近日&#xff0c;据剪映 Dreamina 官方消息&#xff0c;Deramina正式更名为即梦&#xff0c;同时宣布其AI作图和AI视频生成功能已全量上线。 ▲ 官网主页面 AI作图 1、通过文字描述或…

华中科大:感谢大家,我的春招之旅结束了

今天在论坛上看到一个帖子&#xff0c;一位华中科大的同学&#xff0c;因为家中父亲突然病倒&#xff0c;发求助帖&#xff1a; 请问大家&#xff0c;春招走哪个方向能最快找到工作&#xff1f;还是说继续读研呢&#xff0c;但是家里急需钱…… 当时这个帖子直接热榜第一&…

Python练习04

目录 制作一个简易的注册登陆系统 实现过程 声明需要用到的库 构造一个判断用户文件是否存在的函数 构造一个存储用户文件的函数 制作UI 制作系统主体 运行效果 制作一个简易的注册登陆系统 通过所学知识制作一个简易的注册登陆系统&#xff0c;要求可以存储账户及密码&#…

省级生活垃圾无害化处理率面板数据(2004-2022年)

01、数据简介 生活垃圾无害化处理率是指经过处理的生活垃圾中&#xff0c;达到无害化标准的垃圾所占的比例。这一指标是衡量城市垃圾处理水平的重要标准&#xff0c;反映了城市对垃圾进行有效管理和处理的能力。 生活垃圾无害化处理的主要方式包括生活垃圾焚烧、生活垃圾卫生…

2024生日快乐祝福HTNL源码修复版

源码介绍 2024生日快乐祝福HTNL源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c; 源码截图 源码下载 2024生日快乐祝福HTNL源码

数据库编程

PL/SQL程序 1.PL/SOL程序块 整个PL/SQL块分三部分&#xff1a;声明部分、执行部分、异常处理部分&#xff1b; 示例&#xff1a; declare --变量声明 v_sno varchar2(10) : ‘04001’; v_cno varchar2(10) :‘001’; v_grade number : 90; begin --程序入口 insert…

PyQt程序的打包

Qt hello - 专注于Qt的技术分享平台 记录下PyQt程序的打包。 一&#xff0c;安装 pip3 install PyInstaller 二&#xff0c;打包 pyinstaller -w -n app app.py 根据需要选择打包参数&#xff0c;例如&#xff1a;-F表示生成单文件模式&#xff0c;即只有一个可执行文件…

使用Eigen将经纬度、高程、偏北角转成变换矩阵

目录 1、前言 2、示例 3、代码解析 4、垂直于给定点的切平面变换 5、代码解析 1、前言 在地球表面进行刚体变换时候&#xff0c;要将具有经纬度、高程和偏北角的坐标信息转换为变换矩阵表达&#xff0c;首先需要了解坐标系之间的转换关系。 通常&#xff0c;我们会将经纬…

攻防演练-防守单位常见防守策略

为方便您的阅读&#xff0c;可点击下方蓝色字体&#xff0c;进行跳转↓↓↓ 01 防守单位常见防守策略 01 防守单位常见防守策略 为普及网络安全知识&#xff0c;提高网络安全防范意识&#xff0c;和网络安全工作技能。我们将向大家介绍网络安全攻防演练中防守单位的一些关键策…

自回归模型的优缺点及改进方向

在学术界和人工智能产业中&#xff0c;关于自回归模型的演进与应用一直是一个引发深入讨论和多方观点交锋的热门议题。尤其是Yann LeCun&#xff0c;这位享誉全球的AI领域学者、图灵奖的获得者&#xff0c;以及被誉为人工智能领域的三大巨擘之一&#xff0c;他对于自回归模型持…

2.三极管

2.习题 3.知识补充

ssm125四六级报名与成绩查询系统+jsp

四六级报名与成绩查询系统的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对四六级报名信息管理混乱&am…

最短路(图论学习总结部分内容)

文章目录 前言二、最短路多源最短路 F l o y d Floyd Floyd​ 算法例题及变形 e g 1 &#xff1a; S o r t i n g I t A l l O u t eg1&#xff1a;Sorting\ It\ All\ Out eg1&#xff1a;Sorting It All Out ( 蓝书例题&#xff0c;传递闭包 ) (蓝书例题&#xff0c;传递闭包…