1. 内存函数
1-1 memcpy 内存拷贝
memcpy(参数1【目的】,参数2【源】,参数3【无符号整型】)
memcpy函数不能用来处理重叠的内存空间的数据拷贝
使用memmove实现重叠内存空间的数据拷贝
void* memcpy(void* dest,const void* src,size_t num){ }
void*:通用类型指针
1-2 memmove 内存拷贝
分两种情况拷贝:
(1)从前向后拷贝
dest < src
(2)从后向前拷贝
dest > src
示例:
int arr[ ] = {1,2,3,4,5,6,7,8,9,10...};
int arr1[ ] = {3,4,5,6,7};//src
int dest1[ ] = {2,3,4,5,6};//dest < src,从前向后拷贝
int dest2[ ] ={5,6,7,8,9};//dest > src,从后向前拷贝
1-3 memcmp 内存比较
一个字节一个字节进行比较
1-4 memset 内存设计
memset(目的空间,设置的值,设置的值的字节数)
int arr[] = "hello,world!";
memset(arr, 'h', 5); //从数组第一个元素开始,替换成h,共替换5个元素
printf("%s\n", arr);
替换后的数组:arr [ ] = “hhhhh,world!”;
int arr[] = "hello,world!";
memset(arr+5, 'x', 3); //从第arr+5个元素开始,将元素替换成x,共替换3个元素
printf("%s\n", arr);
替换后的数组:arr[ ] = “hello,xxxld!”;
2. 自定义类型
2-1 结构体
结构体:值的集合。
值被称为成员变量
2-1-1 结构体的声明:
struct 结构体标签
{
成员变量;
}变量列表; //变量列表是:1. 利用结构体类型创建的变量;2. 结构体的全局变量
结构体的自引用
自己找到与自己同类型的另一个结点
struct node
{
int data;
struct node* next;
};
数据结构:描述数据在内存中的存储结构
2-1-2 结构体变量的定义和初始化
struct point
{
int x;
int y;
}p1 = (2,3); //p1变量
2-1-3 结构体内存对齐
计算结构体大小
(1)结构体对齐规则:
(1-1)第一个成员变量的对齐数
放在结构体偏移量为0的地址处
(1-2) 其他成员变量对齐数
对齐到某个数字(对齐数)的整数倍的地址处
对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值
VS编译器默认的对齐数是8
(1-3) 结构体的总大小
最大对齐数的整数倍
(1-4)嵌套了结构体的情况
嵌套的结构体要对齐到自己的最大对齐数的整数倍处
VS编译器默认的对齐数是8
结构体的整体大小
所有最大对齐数(含嵌套结构体的对齐数)的整数倍
(2)为什么存在内存对齐
(2-1)平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据
某些硬件平台只能访问某些地址处某些特定类型的数据,否则抛出硬件异常
(2-2)性能原因
数据结构(尤其是栈)应该尽可能的对齐
对齐的内存访问仅需要一次访问
未对齐的内存需要两次内存访问
总体来说,结构体的内存对齐是 空间换时间的做法
设计结构体时,既要满足对齐,又要节省空间,如何做:
让占用空间小的成员变量尽量集中在一起
(3)修改默认对齐数
#pragma pack(4)//改成4
#pragma once //防止头文件按被多次引用
2-2 结构体传参
//定义结构体
struct S
{
int data[1000];
int num;
};
//主函数
int main()
{
struct S s = {(1,2,3),100};
print1(s);
print2(&s);
}
传值调用:
void print1(sturuct S ss)
{
int i = 0;
for(i = 0; i <3; i++)
{
printf("%d ",ss.data[i]);
}
printf("%d\n",ss.num);
}
传址调用:
void print1(sturuct S* ps)
{
int i = 0;
for(i = 0; i <3; i++)
{
printf("%d ",ps->data[i]);
}
printf("%d\n",ps->num);
}
优先选择传址调用
原因:函数在传参时,会有函数压栈,会有时间和空间上的开销
3. 位段
位:比特位
通过结构体实现
一种节省空间的做法
3-1 什么是位段
(1)位段的声明和结构体类似,有2个不同
位段的成员必须是int、unsigned int、signed int
位段的成员后面有:一个冒号和一个数字
冒号后的数字不能超过成员大小的比特位
int:4字节(32个比特位)
char:1字节(8个比特位)
示例:
struct A
{
int_a:2;
int_b:5;
int_c:10;
int_d:30;
};
3-2 位段的内存分配
位段的空间是根据需要按照4字节(int)或者1字节(char)开辟空间的
位段不跨平台
注重可移植的程序避免使用位段
VS2019中,位段存放由低位到高位(右向左)进行存放
3-3 位段的跨平台问题
int位段不确定被当成有符号数还是无符号数
位段中最大位的数目不确定
位段中的成员未定义在内存中是从左到右还是从右到左进行分配
第二个位段比较大,无法容纳于第一个位段剩余的位置时,不确定是舍弃剩余的位还是利用
综上:位段不跨平台
4. 实现通讯录
(1)存放人的信息
姓名
年龄
性别
...
(2)增加存放的人数的信息
增删查改
排序
显示联系人
...
4-1. test.c 测试功能
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"
menu()
{
printf("**********************************************\n");
printf("*** 1.add 2.del ************\n");
printf("*** 3.modify 4.search ************\n");
printf("*** 5.show 6.sort ************\n");
printf("*** 0.exit ************\n");
printf("**********************************************\n");
}
int main()
{
int input = 0;
//将存放联系人的信息和记录联系人的信息封装到contact.h中
//PeoInfo data[ MAX];//存放100个人的信息
//int count = 0;//记录当前通讯录的联系人信息
Contact con;//通讯录
//初始化通讯录
InitContact(&con);//传参,传地址
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("增加联系人:>\n");
AddContact(&con);
break;
case 2:
printf("删除联系人:>\n");
DelContact(&con);
break;
case 3:
printf("修改联系人:>\n");
ModifyContact(&con);
break;
case 4:
printf("查询联系人:>\n");
SearchContact(&con);
break;
case 5:
printf("显示联系人:>\n");
ShowContact(&con);
break;
case 6:
printf("联系人排序:>\n");
SortContact(&con);
break;
case 0:
printf("退出通讯录:>\n");
break;
default:
printf("输入错误,请重新输入:>\n");
break;
}
} while (input);
return 0;
}
4-2. contact.c 通讯录相关的实现
#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
//实现函数
//初始化函数
void InitContact(Contact* pc)
{
assert(pc);//断言一下pc不为空指针;需要引入头文件assert.h,引入到contact.h中
pc->count = 0;//初始化通讯录为空
memset(pc->data, 0, sizeof(pc->data));//memset函数需要引入头文件,引入到contact.h中
}
//增加联系人信息
void AddContact(Contact* pc)
{
assert(pc);
//判断通讯录是否满了
if (pc->count == MAX)
{
printf("通讯录已满,无法添加\n");
return;
}
//增加联系人信息
printf("请输入姓名:>");
scanf("%s", pc->data[pc->count].name);//name是数组名,数组名本身就是地址,不需要取地址&
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->count].age));//age是整型变量,需要取地址&
printf("请输入性别:>");
scanf("%s", pc->data[pc->count].sex);
printf("请输入电话;>");
scanf("%s", pc->data[pc->count].tel);
printf("请输入地址:>");
scanf("%s", pc->data[pc -> count].addr);//pc指向的数组data;下标是pc指向的count:pc->count;成员里面的addr
pc->count++;
printf("联系人信息增加成功\n");
}
void ShowContact(const Contact* pc)
{
assert(pc);
int i = 0;
printf("%-20s\t%-5s\t%-10s\t%-15s\t%-30s\n", "姓名", "年龄", "性别", "电话", "地址");//增加抬头信息。一个汉字至少2个字符,所以年龄对应的格式是%5s;‘-’号表示左对齐
for (i = 0; i < pc ->count; i++)
{
printf("%-20s\t%-5d\t%-10s\t%-15s\t%-30s\n", pc->data[i].name,
pc->data[i].age, pc->data[i].sex,
pc->data[i].tel, pc->data[i].addr);//\t是水平制表符,让他空开
}
}
static int FindByName(Contact* pc,char name[])//static修饰,FindByName函数只能在这个文件中看到
{
assert(pc);
int i = 0;
for (i = 0; i < pc->count; i++)
{
if (0 == strcmp(pc->data[i].name, name))
{
return i;
}
}
return -1;
}
void DelContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };//需要删除的联系人的姓名
assert(pc);
int i = 0;
if (pc->count == 0)
{
printf("通讯录为空,没有联系人可删除:\n");
return;
}
printf("请输入要删除的联系人的姓名:>");
scanf("%s",name );
//删除,前提通讯录里不是空的,有联系人存在
//1.查找。哪里都会用到查找这个操作,封装成函数
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("需要删除的联系人不存在\n");
return;
}
//2.删除
for (i = pos; i < pc->count-1; i++)
{
pc->data[i] = pc->data[i + 1];//覆盖,i+1覆盖i
}
pc->count--;
printf("已成功删除联系人\n");
}
void SearchContact(Contact* pc)
{
assert(pc);
char name[MAX_NAME] = { 0 };//需要查找的联系人的姓名
printf("请输入要查找的联系人的姓名:>");
scanf("%s", name);
//1.查找。哪里都会用到查找这个操作,封装成函数
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("需要查找的联系人不存在\n");
return;
}
printf("%-20s\t%-5s\t%-10s\t%-15s\t%-30s\n", "姓名", "年龄", "性别", "电话", "地址");//增加抬头信息。一个汉字至少2个字符,所以年龄对应的格式是%5s;‘-’号表示左对齐
printf("%-20s\t%-5d\t%-10s\t%-15s\t%-30s\n", pc->data[pos].name,
pc->data[pos].age, pc->data[pos].sex,
pc->data[pos].tel, pc->data[pos].addr);//\t是水平制表符,让他空开
}
void ModifyContact(Contact* pc)
{
assert(pc);
char name[MAX_NAME] = { 0 };//需要修改的联系人的姓名
printf("请输入要修改的联系人的姓名:>");
scanf("%s", name);
//1.查找。哪里都会用到查找这个操作,封装成函数
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("需要修改的联系人不存在\n");
return;
}
printf("需要修改的联系人已找到,请开始修改:>");
//修改
//重新录一遍信息
printf("请输入姓名:>");
scanf("%s", pc->data[pos].name);//name是数组名,数组名本身就是地址,不需要取地址&
printf("请输入年龄:>");
scanf("%d", &(pc->data[pos].age));//age是整型变量,需要取地址&
printf("请输入性别:>");
scanf("%s", pc->data[pos].sex);
printf("请输入电话;>");
scanf("%s", pc->data[pos].tel);
printf("请输入地址:>");
scanf("%s", pc->data[pos].addr);//pc指向的数组data;下标是pc指向的count:pc->count;成员里面的addr
printf("已成功修改联系人信息\n");
}
int cam_peo_by_name(const void* e1,const void* e2)//按名字比较
{
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
void SortContact(Contact* pc)
{
//按名字排序
assert(pc);
qsort(pc->data,
pc->count,
sizeof(PeoInfo),
cam_peo_by_name
);//4个参数:起始位置,元素个数(几个元素排序),需要排序的一个元素有多大,比较函数的函数指针(2个参数:指向要比较的2个元素的地址)
printf("已成功排序联系人信息\n");
}
4-3. contact.h 通讯录相关的声明
#pragma once
#include <stdio.h>
#include <string.h>
#include <assert.h>
//符号定义
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TEL 15
#define MAX_ADDR 30
//声明类型
//将结构体people重命名为peoInfo
//联系人的信息
typedef struct People
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tel[MAX_TEL];
char addr[MAX_ADDR];
}PeoInfo;
//重命名为Contact
//通讯录的信息
typedef struct Contact
{
PeoInfo data[MAX];//存放100个人的信息
int count;//记录当前通讯录的联系人信息
}Contact;
//声明函数
//初始化通讯录
void InitContact(Contact* pc);
//增加联系人的通讯录
void AddContact(Contact* pc);
//显示通讯录信息
void ShowContact(const Contact* pc);//只显示,不修改,加const保护,不能修改
//删除通讯录联系人
void DelContact(Contact* pc);
//查找联系人
void SearchContact(Contact* pc);
//修改联系人信息
void ModifyContact(Contact* pc);
//排序
void SortContact(Contact* pc);