#San的C语言学习笔记5

一、指针高级1.野指针和悬空指针野指针&#xff1a;指针指向的空间未分配悬空指针&#xff1a;指针指向的空间已分配&#xff0c;但是被释放了#include<stdio.h>int* me

一、指针高级

1.野指针和悬空指针

野指针:指针指向的空间未分配
悬空指针:指针指向的空间已分配,但是被释放了

#include<stdio.h>
int* method();
int main()
{
	int a = 10;
	int* p1 = &a;
	printf("%p\n", p1);
	printf("%d\n", *p1);
	//野指针
	int* p2 = p1 + 10;
	printf("%p\n", p2);
	printf("%d\n", *p2);
	//悬空指针
	int* p3 = method();
	printf("拖点时间\n");
	printf("%p\n", p3);
	printf("%d\n", *p3);
	return 0;
}
int* method()
{
	int num = 10;
	int* p = &num;
	return p;
}


不要使用野指针和悬空指针

2.void类型的指针

特殊类型:
Void*p不表示任何类型
特点:无法获取数据,无法计算,但是可以接收任意地址
不同类型的指针之间,是不能互相赋值的
void类型的指针打破上面的观念
void没有任何类型,好处可以接受任意类型指针记录的内存地址
缺点:void类型的指针,无法获取变量里面的数据,也不能进行加、减的计算

#include<stdio.h>
void swap(void* p1, void* p2, int len);
int main()
{
	int a = 10;
	short b = 20;
	int* p1 = &a;
	short* p2 = &b;
	printf("%d\n", *p1);
	printf("%d\n", *p2);
	void* p3 = p1;
	void* p4 = p2;
	int c = 100;
	int d = 200;
	swap(&c, &d, 4);
	printf("c=%d,d=%d\n",c,d);
	return 0;
}
void swap(void* p1, void* p2, int len)
{
	char* pc1 = p1;
	char* pc2 = p2;
	char temp = 0;
	for (int i = 0; i < len; i++)
	{
		temp = *pc1;
		*pc1 = *pc2;
		*pc2 = temp;
		pc1++;
		pc2++;
	}
}

3.二级指针和多级指针

指针数据类型:跟指向空间中,数据的类型是保持一致的
作用:二级指针可以操作一级指针记录的地址
二级指针:
格式;数据类型**指针名

  • 作用一:利用二级指针修改一级指针里面记录的内存地址
  • 作用二:利用二级指针获取到变量中记录的数据

4.数组指针

概念:指向数组的指针,叫做数组指针
作用:方便的操作数组中的各种数据
练习: 利用指针遍历数组

5.数值指针的细节

练习:数组指针的细节

  • arr参与计算的时候,会退化为第一个元素的指针特殊情况:
  • sizeof 运算的时候,不会退化,arr还是整体&arr获取地址的时候,不会退化
  • &arr获取地址的时候,不会退化,记录的内存地址第—个元素的首地址,也是数组的首地址,步长:数据类型*数组的长度49
  • arr参与计算的时候,会退化为第一个元素的指针,记录的内存地址第一个元素的首地址,也是数组的首地址,步长:数据类型 int 4

6.利用索引遍历第一种格式的二维数组

二维数组
概念:把多个小数组,放到一个大的数组当中
定义格式一和定义格式二:
利用索引的方式进行遍历
arr[0]:表示二维数组当中的第一个一维数组,{1,2,3,4,5}
arr[1]:表示二维数组当中的第二个一维数组,{11,22,33,44,55}
arr[2]:表示二维数组当中的第三个一维数组,{111,222,333,444,555}

弊端:要求二维数组里面每个一维数组的长度都要一样

7.利用索引遍历第二种格式的二维数组

核心:事先先把所有的一维数组定义完毕,再放入到二维数组当中。
注意:数组的数据类型,跟内部存储的元素类型保持一致int==*==arr[3]
arr1:使用数组名进行计算的时候,退化为指向第一个元素的指针,此时不再表示数组的那个整体了
指针----内存地址 64位win 8个字节
不能使用int len=sizeof(arr[i])/sizeof(int);

#include<stdio.h>
int main()
{
	int arr1[3] = { 1,2,3 };
	int arr2[5] = { 1,2,3,4,5 };
	int arr3[9] = { 1,2,3,4,5,6,7,8,9 };
	//预先计算每一个数组真实的长度
	int len1 = sizeof(arr1) / sizeof(int);
	int len2 = sizeof(arr2) / sizeof(int);
	int len3 = sizeof(arr3) / sizeof(int);
	//再定义一个数组,装所有数组的长度
	int lenArr[3] = { len1,len2,len3 };
	//把三个一位数组放入到二维数组当中
	int* arr[3] = { arr1,arr2,arr3 };
	//利用索引的方式进行遍历
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < lenArr[i]; j++)
		{
			printf("%d", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

8.利用指针遍历第一种格式的二维数组

二维数组里面存的是一维数组,一维数组里面存的才是数据本身。

#include<stdio.h>
int main()
{
	int arr[3][5] =
	{
		{1,2,3,4,5},
		{11,22,33,44,55},
		{111,222,333,444,555}
	};
	//数组指针里面的数据类型:要跟数组
	//内部的元素类型保持一致
	//二维数组里面存储的是一维数组int[5]
	int(*p)[5] = arr;
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j <5; j++)
		{
			printf("%d ", *(*p+j));
		}
		printf("\n");
		//移动二维数组的指针,继续遍历下一个一维数组
		p++;
	}
	return 0;
}

9.利用指针遍历第二种格式的二维数组

10.数组指针和指针数组

数组指针:指向数组的指针
作用:方便的操作数组中的各种数据
举例:int* p = arr; 步长为:int(4字节)
举例:int(*p)[5] = &arr; 步长为:int乘5(20字节)
指针数组:存放指针的数组作用:用来存放指针
举例:int * p[5],这个数组里面存着int类型的指针

11.函数指针

格式:返回值类型(*指针名)(形参列表)
作用:利用函数指针,可以动态的调用函数

#include<stdio.h>
void method1();
int method2(int num1, int num2);
int main()
{
	void (*p1)()=method1;
	int (*p2)(int, int)=method2;
	p1();
	int num=p2(10,20);
	printf("%d\n", num);
	return 0;
}
void method1()
{
	printf("method1\n");
}
int method2(int num1, int num2)
{
	printf("method2\n");
	return num1 + num2;
}

12.函数指针和函数指针数组的练习

定义加、减、乘、除、四个函数
用户键盘录入三个数字
前两个表示参与计算的数字
第三个数字表示调用的函数
1:加法
2:减法
3:乘法
4:除法

#include<stdio.h>
int add(int num1, int num2);
int subtract(int num1, int num2);
int mutiply(int num1, int num2);
int divide(int num1, int num2);
int main()
{
	//函数指针数组
	int (*arr[4])(int,int) = { add,subtract,mutiply,divide };
	printf("请录入两个数字参与计算\n");
	int num1;
	int num2;
	scanf("%d%d",&num1,&num2);
	printf("%d\n", num1);
	printf("%d\n", num2);
	int choose;
	printf("请录入一个数字表示要进行的计算\n");
	scanf("%d", &choose);
	int res=(arr[choose - 1])(num1,num2);
	printf("%d\n", res);

}
int add(int num1, int num2)
{
	return num1 + num2;
}
int subtract(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;
}

细节:只有形参完全相同而且返回值也要一样的函数,才能放到同一个函数指针数组当中

二、字符串

1.字符串的两种定义方式和底层细节

1.1利用字符数组+双引号的方式定义字符串 char str1[4] = “abc”;

  • 细节1:
    在底层,实际存储的时候,C语言还是会帮我们把字符串"abc"转换成字符数组进行保存,并且在末尾还要再加上’\0’
    {‘a’ ,‘b’ ,‘c’ ,‘\0’};
  • 细节2:
    数组的长度,要么不写,如果要写的话,记得要把结束标记的空间给预留出来
  • 细节3:
    字符数组+双引号的方式定义字符串,内容是可以发生改变的

1.2利用指针+双引号的方式定义字符串 char* str2 = “abcd”;

  • 细节1:
    在底层,实际存储的时候,C语言还是会帮我们把字符串"abcd"转换成字符数组进行保存,并且在末尾还要再加上’\0’
    {‘a’ ,‘b’ ,‘c’ ,‘d’ ,‘\0’};
  • 细节2:
    利用指针+双引号的方式定义字符串,会把底层字符数组放在只读常量区
    只读常量区的特点:
    内容不可以修改的
    里面定义的字符串是可以复用的(在创建abcd的时候,会检查只读常量区里面有没有abcd,如果没有才会创建新的,如果已经有了,不会创建新的,而是进行复用)

2.练习:键盘录入字符并遍历

需求:键盘录入一个字符串,使用程序实现在控制台遍历该字符串
底层逻辑:
程序在运行的时候,首先会创建一个长度为100的字符数组str
在进行健盘录入的时候,会把每一个字符存入到上面的str数姐当中,并加上结束标记\0
在这个过程中,需要修改字将数姐的内容,所以第一种方式可以。第二种方式不可以

3.字符串数组

需求:定义一个数组存储5个学生的名字并进行遍历
字符的底层其实就是字符数组
把多个字符数组,再放入到一个大的数组当中

#include<stdio.h>
int main()
{
	//二维数组的方式定义
	char strArr[5][100] =
	{
		"zhangsan",
		"lisi",
		"wangwu",
		"zhaoliu",
		"qiangqi"
	};
	for (int i = 0; i < 5; i++)
	{
		char* str = strArr[i];
		printf("%s\n", str);
	}
	//指针数组的方式定义
	char* strArr2[5] =
	{
		"zhangsan",
		"lisi",
		"wangwu",
		"zhaoliu",
		"qiangqi"
	};
	for (int i = 0; i < 5; i++)
	{
		char* str = strArr2[i];
		printf("%s\n", str);
	}
	return 0;
}

4.字符串的常见函数

4.1常见函数

函数作用
strlen获取字符串的长度
strcat拼接两个字符串
strcpy复制字符串
strcmp比较两个字符串
strlwr将字符串变成小写
strupr将字符串变成大写
#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:把第二个字符串中全部的内容拷贝到第一个字符串的末尾
	//前提1:第一个字符串是可以被修改的
	//前提2:第一个字符串中剩余的空间可以容纳拼接的字符串
	strcat(str2, str3);
	printf("%s\n", str2);//abcqwer
	printf("%s\n", str3);//qwer

	printf("----------------------strcpy(拷贝)------------------------\n");
	//细节:把第二个字符串中全部的内容,拷贝到第一个字符串中,把第一个字符串里面的内容里面原有的内容给覆盖了
	//前提1:第一个字符串是可以被修改的
	//前提2:第一个字符串中的空间可以容纳第二个字符串的完整内容
	strcpy(str2, str3);
	printf("%s\n", str2);//qwer
	printf("%s\n", str3);//qwer

	printf("----------------------strcmp(比较)------------------------\n");
	//细节:在比较的时候,有要求顺序和内容完全一致,才叫做字符串一样
	//完全一样:0
	//只要有一个不一样:非0
	int res = strcmp(str1, str2);
	printf("%d\n", res);

	printf("----------------------strlwr(变小写)------------------------\n");
	//细节:只能转换英文的大小写,不能修改中文的大小写
	strlwr(str2);
	printf("%s\n", str2);

	printf("----------------------strupr(变大写)------------------------\n");
	strupr(str2);
	printf("%s\n", str2);


	return 0;
}

5.练习1:用户登录

需求:已知正确的用户名和密码,请用程序实现模拟用户登录。
总共给三次机会,登录之后,给出相应的提示

#include<stdio.h>
#include<string.h>
int main()
{
	char* rightUsername = "zhangsan";
	char* rightPassword = "qwerqwer";
	for(int i=0;i<3;i++)
	{
		printf("请输入用户名\n");
		char username[100];
		scanf("%s", username);
		printf("请输入密码\n");
		char password[100];
		scanf("%s", password);
		if (!strcmp(username, rightUsername) && (!strcmp(password, rightPassword)))
		{
			printf("登陆成功\n");
			break;
		}
		else
		{
			if (i == 3)
			{
				printf("用户%s被锁定,请联系管理员",username);
			}
			else
			{
				printf("登陆失败,还剩下%d次机会\n", 3 - i);
			}
		}
	}
	
	return 0;
}

6.练习2:统计次数

键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数(不考虑其他字符)

#include<stdio.h>
#include<string.h>
int main()
{
	printf("请输入一个字符串\n");
	char str[100];
	scanf("%s", str);
	int bigCount = 0;
	int smallCount = 0;
	int numberCount = 0;
	for (int i = 0; i < strlen(str); i++)
	{
		char c = str[i];
		if (c >= 'a' && c <= 'z')
		{
			smallCount++;
		}
		else if(c >= 'A' && c <= 'Z')
		{
			bigCount++;
		}
		else if (c >= '0' && c <= '9')
		{
			numberCount++;
		}
	}
	printf("大写字符出现了%d次\n", bigCount);
	printf("小写字符出现了%d次\n", smallCount);
	printf("数字字符出现了%d次\n", numberCount);
	return 0;
}

三、结构体

1.结构体

1.1定义:

结构体可以理解为自定义的数据类型
他是由一批数据组合而成的结构性数据
里面的每一个数据都是结构体的“成员”
结构体作为函数参数,在函数中可以传递结构体

1.2传递的两种情况:

传递结构体中的数据值
传递结构体的地址值

1.3书写的位置:

函数的里面:局部位置,只能在本函数中使用
函数的外面:全局位置,在所有的函数中都可以使用

#include<stdio.h>
#include<string.h>
struct GirlFriend
{
	char name[100];
	int age;
	char gender;
	double height;
};
int main()
{
	struct GirlFriend gf1;
	strcpy(gf1.name, "小诗诗");
	gf1.age = 23;
	gf1.gender = 'F';
	gf1.height= 1.63;
	printf("我女朋友的名字为:%s\n", gf1.name);
	printf("我女朋友的年龄为:%d\n", gf1.age);
	printf("我女朋友的性别为:%c\n", gf1.gender);
	printf("我女朋友的身高为:%lf\n", gf1.height);

	struct GirlFriend gf2;
	strcpy(gf2.name, "小丹丹");
	gf2.age = 24;
	gf2.gender = 'F';
	gf2.height = 1.62;
	printf("我女朋友的名字为:%s\n", gf2.name);
	printf("我女朋友的年龄为:%d\n", gf2.age);
	printf("我女朋友的性别为:%c\n", gf2.gender);
	printf("我女朋友的身高为:%lf\n", gf2.height);
	return 0;
}

2.结构体数组

定义一个结构体表示学生
学生的属性有:姓名、年龄
要求:把三个学生信息放入到数组当中,并遍历数组
crtl+A再ctrl+K和ctrl+F----->格式化

3.起别民


定义一个结构体表示游戏人物
属性有:姓名、攻击力、防御力、血量
要求:把三个游戏人物放入到数组当中,并遍历数组

#include<stdio.h>
#include<string.h>
typedef struct Ultraman
{
	char name[100];
	int attack;
	int defense;
	int blood;
}M;
int main()
{
	M taro = { "泰罗",100,90,500 };
	M rem = { "雷欧",90,80,450 };
	M eddie = { "艾迪",120,70,600 };
	M arr[3] = { taro,rem,eddie };
	for (int i = 0; i < 3; i++)
	{
		M temp = arr[i];
		printf("奥特曼的名字为%s,攻击力是%d,防御力是%d,血量是%d\n", temp.name, temp.attack, temp.defense,temp.blood);
	}
	return 0;
}

4.结构体作为函数的参数进行传递

定义一个结构体表示学生
学生的属性:姓名,年龄
要求:定义一个函数,修改学生中的数据

#include<stdio.h>
#include<string.h>
typedef struct Student
{
	char name[100];
	int age;
}S;
//细节: 因为这个函数用到了结构体,所以函数的申明必须写在结构体的下面,否则代码会报错
void method(S st);
void method2(S* p);
int main()
{
	S stu;
	strcpy(stu.name, "aaa");
	stu.age = 0;
	printf("学生的初始数据为: %s, %d\n", stu.name, stu.age);
	//调用函数修改学生中的数据
	method(stu);
	printf("学生的信息修改为: %s, %d\n", stu.name, stu.age);
}
void method(S st)
{
	printf("接收到 main函数中学生的初始数据为: %s, %d\n", st.name, st.age);
	//修改
	printf("请输入要修改的学生名字\n");
	scanf("%s", st.name);
	printf("请输入要修改的学生年龄\n");
	scanf("%d", &(st.age));
	printf("在method函数中修改之后,学生的信息为: %s, %d\n", st.name, st.age);
}

细节:
如果函数中写的是结构体类型的变量, 相当于是定义了一个新的变量
此时是把 main函数中 stu中的数据,传递给了 method函数,并把 stu中的数据赋值给了新的变量st
我们在函数中,仅仅是修改了变量 st中的值,对 main函数中stu的值,是没有进行修改的

#include<stdio.h>
#include<string.h>
typedef struct Student
{
	char name[100];
	int age;
}S;
//细节: 因为这个函数用到了结构体,所以函数的申明必须写在结构体的下面,否则代码会报错
void method(S st);
void method2(S* p);
int main()
{
	S stu;
	strcpy(stu.name, "aaa");
	stu.age = 0;
	printf("学生的初始数据为: %s, %d\n", stu.name, stu.age);
	//调用函数修改学生中的数据
	method2(&stu);
	printf("学生的信息修改为: %s, %d\n", stu.name, stu.age);
}
void method2(S* p)
{
	printf("接收到 main函数中学生的初始数据为: %s, %d\n", (*p).name, (*p).age);// aaa 0
	//修改
	printf("请输入要修改的学生名字\n");
	scanf("%s", (*p).name);
	printf("请输入要修改的学生年龄\n");
	scanf("%d", &((*p).age));
	printf("在method函数中修改之后, 学生的信息为: %s, %d\n", (*p).name, (*p).age);// zhangsan 23
}

如果要在函数中修改 stu的值,此时就不要再定义新的变量了
直接接收 stu的内存地址, 通过内存地址就可以修改 stu中的数据了
指针p里面记录的是 main函数中 stu的内存地址 ( stu 学生)

5.结构体嵌套

定义一个结构体表示学生 Student
Student成员如下 :
名字、年龄、性别、身高、联系方式
联系方式 Message也是一个结构体, 成员如下:
手机号、电子邮箱

#include<stdio.h>
#include<string.h>
struct Message
{
	char phone[12];
	char mail[100];
};
struct Student
{
	char name[100];
	int age;
	char gender;
	double height;
	struct Message msg;
};
int main()
{
	struct Student stu;
	//给里面的每一个成员进行赋值
	strcpy(stu.name, "zhangsan");
	stu.age = 23;
	stu.gender = 'M';
	stu.height = 1.78;
	strcpy(stu.msg.phone, "13112345678");
	strcpy(stu.msg.mail, "12345678@qq");
	//输出打印
	printf("学生的信息为: \n");
	printf("姓名为: %s\n", stu.name);
	printf("年龄为: %d\n", stu.age);
	printf("性别为: %c\n", stu.gender);
	printf("身高为: % lf\n", stu.height);
	printf("手机号为: %s\n", stu.msg.phone);
	printf("邮箱为: %s\n", stu.msg.mail);
	printf("-------------------------------\n");
	//批量进行赋值
	struct Student stu2 = { "lisi",24,'F',1.65,{"13112347890","5678@qq"} };
	//输出打印
	printf("学生的信息为: \n");
	printf("姓名为: %s\n", stu2.name);
	printf("年龄为: %d\n", stu2.age);
	printf("性别为: %c\n", stu2.gender);
	printf("身高为: % lf\n", stu2.height);
	printf("手机号为: %s\n", stu2.msg.phone);
	printf("邮箱为: %s\n", stu2.msg.mail);
	return 0;
}

6.综合练习:投票选举

某班级组织野外郊游,想要在ABCD四个景点选择其中一个。
现在班上有80名同学进行投票,找出投票数最多的景点
Ps:
1.学生投票,用随机数模拟
2.如果多个景点投票一样的话,A优先B,B优先于C,C优先于D

#include<stdio.h>
#include<time.h>
#include<string.h>
struct spot
{
	char name[100];
	int count;
};
int main()
{
	// 1.定义数组存储4个spot类型的变量
	struct spot arr[4] = { {"A",0}, {"B",0}, {"C",0}, {"D",0} };
	//2.模拟80名同学的投票
	srand(time(NULL));
	for (int i = 0; i < 80; i++)
	{
		// choose变量有两个含义
		//含义一:表示用户的投票 0 A 1 B 2 C 3 D
		//含义二:表示arr中的索引,通过这个索引就可以获取到景点的名字和投票数量 
		int choose = rand() % 4; // 0 1 2 3
		//choose:表示同学的投票, 同时也表示数组中的索引
		// arr[ choose]: 表示获取景点的信息 (名字, 数量)
		// arr[ choose]. count: 表示这个景点已经投了多少票
		// arr[ choose]. count++: 给这个景点再投一票
		arr[choose].count++;
	}
	//找最大值
	int max = arr[0].count;
	for (int i = 1; i < 4; i++)
	{
		struct spot temp = arr[i];
		if (temp.count > max)
		{
			max = temp.count;
		}
	}
	//遍历数组,看谁的票数刚好是最大值
	for (int i = 0; i < 4; i++)
	{
		struct spot temp = arr[i];
		if (temp.count == max)
		{
			printf("投票最多的景点为: %s, 共计: %d张票\n", temp.name, temp.count);
			break;
		}
	}
	//遍历
	for (int i = 0; i < 4; i++)
	{
		struct spot temp = arr[i];
		printf("%s %d\n", temp.name, temp.count);
	}
	return 0;
}

7.内存对齐

确定变量位置:只能放在自己类型整数倍的内存地址上
最后一个补位:结构体的总大小。是组大类型的整数倍


内存对齐
不管是结构体, 还是普通的变量都存在内存对齐

规则
只能放在自己类型整数倍的内存地址上

简单理解
内存地址/占用字节 = 结果可以整除

举例
int存放的位置:内存地址一定能被4整除
long long存放的位置:内存地址一定能被8整除
double存放的位置:内存地址一定能被8整除

结构体的内存对齐
结构体在上面的基础上又多了一条,结构体的总大小,是最大类型的整数倍(用来确定最后一个数据补位的情况)
Tips:
对齐的时候会补空白字节,但是不会改变原本字节的大小,char补位之后,本身还是1个字节
心得:把小的数据类型放在最上面,大的数据类型放在最下面(节约空间)

四、共用(同)体/联合体

1.共用体

核心:一种数据可能有多个数据

需求:
金融项目中,钱有可能是整数,小数,字符串,请定义对应的共同体

#include<stdio.h>
#include<string.h>
union MoneyType
{
	int moneyi;
	double moneyd;
	char moneystr[100];
};
int main()
{
	//利用共同体定义钱的变量
	union MoneyType money;
	// 整数 moneyi、小数 moneyd、字符串 moneystr
	//而且每次只能赋一个值
	// money. moneyi = 99999;
	// money. moneyd = 123.32;
	strcpy(money.moneystr, "100万");
	//上面赋值给哪个类型,下面就从哪个类型取出来
	// printf("%d\n", money. moneyi);
	// printf("%1f\n", money. moneyd);
	printf("%s\n", money.moneystr);
	return 0;
}

2.共用体的特点

  • 特点
    1.共用体,也叫联合体,共同体
    2.所有的变量都使用同一个内存空间
    3.所占的内存大小=最大成员的长度 (也受内存对齐影响)
    细节:怎么存就怎么取出来
    4.每次只能给一个变量进行赋值, 因为第二次赋值时会覆盖原有的数据
    细节:以最大的单个成员的长度为准
    总大小一定是最大单个成员的整数倍
#include<stdio.h>
#include<string.h>
union MoneyType
{
	int moneyi;
	double moneyd;
	char moneystr[100];
};
int main()
{
	//利用共同体定义钱的变量
	union MoneyType money;
	//获取内存地址
	printf("%p\n", &(money.moneyi));
	printf("%p\n", &(money.moneyd));
	printf("%p\n", &(money.moneystr));
	printf("%zu\n", sizeof(money.moneyi));// 4
	printf("%zu\n", sizeof(money.moneyd));// 8
	printf("%zu\n", sizeof(money.moneystr));// 100
	printf("%zu\n", sizeof(money));// 104 (后面会补4个空白字节)
	money.moneyi = 99;
	money.moneyd = 1.23;
	printf("%lf\n", money.moneyd);
	return 0;
}

3.结构体和共用体的区别

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

  • 存储方式
    结构体:各存各的
    共用体:存一起,多次存会覆盖

  • 内存占用
    结构体:各个变量的总和 (受内存对齐影响)
    共用体:最大类型 (受内存对齐影响)

五、动态内存分配

1.常用函数

函数名全名作用
mallocmemory allocation申请空间(连续)
calloccontiguous allocation申请空间+数据初始化
reallocre-allocation修改空间大小
freefree释放空间
#include <stdio.h>
#include <stdlib.h>
int main()
{

    // 1.利用 malloc 函数申请一片连续的空间
    // 需求:申请一片空间,要存储 10 个 int 类型的整数
    // 返回这片空间的首地址
    int* p = (int*)malloc(10 * sizeof(int));
    // int* p = calloc(10, sizeof(int));
    // printf("%p\n", p);
    // 2.赋值
    for (int i = 0; i < 10; i++)
    {
        // 第一种赋值
        // *(p + i) = (i + 1) * 10; //10 20 30 40 50 60 70 80 90 100
        // 第二种赋值
        p[i] = (i + 1) * 10;
        // p[i] - - - > p + i
    }
    // 4.扩容,20 个 int 类型的整数
    int* pp = (int*)realloc(p, 20 * sizeof(int));
    // 3.遍历
    for (int i = 0; i < 20; i++)
    {
        // printf("%d", *(p + i));
        printf("%d ", p[i]);
    }
    //5.释放空间
    //如果申请的空间不需要再继续使用,那么记得一定要释放
    free(pp);
    return 0;
}

2.malloc函数的细节点

  • malloc
    1.malloc创建空间的单位是字节
    2.malloc返回的是void类型的指针,没有步长的概念,也无法获取空间中的数据,需要强转
    3.malloc返回的仅仅是首地址,没有总大小,最好定义一个变量记录总大小
    4.malloc申请的空间不会自动消失,如果不能正确释放, 会导致内存泄露
    5.malloc申请的空间过多,会产生虚拟内存
    6.malloc申请的空间没有初始化值,需要先赋值才能使用
  • free
    7.free释放完空间之后,空间中数据叫做脏数据,可能被清空,可能被修改为其他值
  • calloc
    8.calloc就是在malloc的基础上多一个初始化的动作
  • realloc
    9.realloc修改之后的空间,地址值有可能发生变化, 也有可能不会改变,但是原本的数据不会丢失
    10.realloc修改之后,无需释放原来的空间,函数底层会进行处理
#include <stdio.h>
#include <stdlib.h>
void method(int* p, int size);
int main()
{
   // int*: 指针的步长
   //p: 首地址
    int* p = (int*)malloc(25 * sizeof(int));
    int size = 25;
    method(p, 25);
    free(p);
    return 0;
}
void method(int* p, int size)
{
    for (int i = 0; i < size; i++)
    {
        printf("%d", p[i]);
    }
    printf("\n");
}

#include <stdio.h>
#include <stdlib.h>
int main()
{
	//malloc申请的空间过多, 会产生虚拟内存
	//表示单次申请空间的字节大小(1G)
	int number = 1024 * 1024 * 1024;
	//利用循环不断地申请空间
	// malloc 申请空间 (连续) 
	//如果申请空间成功,返回这个空间的首地址
	//如果申请空间失败,返回NULL
	int count = 0;
	while (1)
	{
		int* p = (int*)malloc(number);
		count++;
		if (p == NULL)
		{
			printf("申请失败");
			break;
		}
		printf("内存%d申请成功%p\n", count, p);
	}
	return 0;
}

发布者:admin,转转请注明出处:http://www.yc00.com/web/1754766300a5199592.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信