指针
内存操作
我们对于内存的操作借助于 string.h 这个库提供的内存操作函数。
内存填充
头文件: #include
函数原型: void *memset(void *s,int c,size_t n);
函数功能:填充s开始的堆内存空间前n个字节,使得每个字节值为c。
函数参数:
void *s:待操作内存首地址。
int c:填充的字节数据。
size_t n:填充的字节数。
返回值:返回s
注意:
c常常设置为0,用于动态内存初始化
案例:
/**
* 内存操作函数-memset
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void test1()
{
// 在堆内存申请了一块存储空间
int *p = (int*)malloc(4 * sizeof(int));
if(!p)
{
puts("内存分配失败!");
return;// 后续代码不需要执行
}
// 给这块内存进行初始化操作(填充)
memset(p,0, 4 * sizeof(int));
printf("%d\n",*(p+1));
// 内存使用完毕,进行回收
free(p);
p = NULL;
}
int main()
{
test1();
return 0;
}
内存拷贝
头文件: #include
函数原型: void *memcpy(void *dest,const void *src,size_t n); 适合目标地址与源地址内存无 重叠的情况。
void *memmove(void *dest,const void *src,size_t n);
函数功能:拷贝src开始的堆内存空间前n个字节,到dest对应的内存中。
函数参数:
void *dest:目标内存首地址。
void *src:源内存首地址。
size_t n:拷贝的字节数。
返回值:
返回dest
注意:内存申请了几个内存空间,就访问几个内存空间,否则数据不安全 注意:memcpy与memmove一般情况下是一样的,更建议使用memmove进行内存拷贝;
因为memmove函数是从自适应(从后往前或者从前往后)拷贝,当被拷贝的内存和目的地的内存 有重叠时,数据不会出现拷贝错误。而memcpy函数是从前往后拷贝,当被拷贝的内存和目的地内 存有重叠时,数据会出现拷贝错误。
案例:
/**
* 内存操作函数-memcpy|memmove
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void test1()
{
// 申请内存
// int *p1 = (int*)malloc(4 * sizeof(int));
// int *p2 = (int*)malloc(6 * sizeof(int));
// 给p1,p2填充数据,可以使用for循环..
// for(int i = 0; i < 4; i++)
// p1[i] = 10 + i;
// memset(p2,0,6 * sizeof(int));
// 创建数组
int p1[4] = {11,12,13,14};
int p2[6] = {21,22,23,24,25,26};
// 将p1中的数据通过内存拷贝函数,拷贝到p2
// memcpy(p2+2,p1+1,2*sizeof(int)) // int p2[6] = {21,22,12,13,25,26}
memmove(p2+2,p1+1,2*sizeof(int));
// 测试输出数组
for(int i = 0; i < 4; i++)
printf("%4d",p1[i]);
printf("\n");
for(int j = 0; j < 6; j++)
printf("%4d",p2[j]);
printf("\n");
// 如果使用手动分配的指针,一定要记得释放内存
// free(p1);
// free(p2);
// p1 = NULL;
// p2 = NULL;
}
int main()
{
test1();
return 0;
}
内存比较
头文件: #include
函数原型: int memcmp(void *dest,const void *src,size_t n)
函数功能:比较src和dest所代表的内存前n个字节的数据;
函数参数:
void *dest:目标内存首地址
const void* src:源内存首地址
size_t n:比较的字节数
返回值:
0 :数据相同
>0 :dest中的数据大于
src <0 :dest中的数据小于src
注意:n一般和src,dest的总容量一样;如果不一样,内促比较的结果就不确定了。
案例:
/**
* 内存操作-memcmp
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void test1()
{
// 申请内存
int *p1 = (int*)malloc(3*sizeof(int));
int *p2 = (int*)malloc(4*sizeof(int));
// int p1[4] = {1,0,3,6};
// int p2[4] = {1,2,3,4};
// int result = memcmp(p1,p2,4*sizeof(int));
*p1 = 65;
*p2 = 70;
char *a = (char*)p1;// 类型转换
char *b = (char*)p2;
printf("%c,%c\n",*a,*b);
int result = memcmp(a+1,b+1,1*sizeof(char));
printf("%d\n",result);
}
int main()
{
test1();
}
内存查找
头文件: #include
函数原型: int *memchr|*memrchr(const void *s,int c,size_t n)
函数功能:在s开始的堆内存空间前n个字节中查找字节数据c
函数参数:
const void *s:待操作内存首地址;
int c:待查找的字节数据
size_t n:查找的字节数
返回值:返回查找到的字节数据地址
注意:如果内存中没有重复数据,memchr和memrchr结果是一样的;如果内存中有重复数据, memchr和memrchr结果就不一样;
举例:
void *memrchr(..);// 在使用时编译会报错,需要使用外部声明
// 外部申请
extern void *memrchr(..);
案例:
/**
* 内存操作-memchr | memrchr
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 声明外部函数
extern void *memrchr(const void *s,int c,size_t n);
void test1()
{
// 申请内存
int *p = (int*)malloc(4*sizeof(int));
if(!p)
{
puts("内存分配失败!");
return;
}
// 给变量赋值
for(int i = 0; i < 4; i++)
{
p[i] = i * 2;
}
p[3] = 4;
// 输出
for(int i = 0; i < 4; i++)
{
printf("%d,",p[i]);
}
printf("\n");
// 内存查找 memchr
int *x = (int*)memchr(p,4,4*sizeof(int));
printf("%p--%p--%d\n",x,p,*x);
// 内存查找 memrchr
int *y = (int*)memrchr(p,4,4*sizeof(int));
printf("%p--%p--%d\n",y,p,*y);
// 回收内存
free(p);
p = NULL;
}
int main()
{
test1();
}
构造类型
数据类型分类
1. 基本类型
整数型
短整型:short(2个字节)
整型(默认):int(4个字节)
长整型:long(8个字节)
长长整型:long long
浮点型
单精度:float(4个字节)
双精度:double(8个字节)
字符型:
char(1个字节)
2. 指针类型
数据类型*:
int*,char*,float*等
void*:任意数据类型的指针
3. 空类型 void:没有返回值或没有形参(不能定义变量)
4. 自定义类型/构造类型
结构体类型:struct
共用体类型(联合体):union
枚举类型:enum
注意:
整数型和字符型分有符号signed和无符号unsigned,默认是有符号,有符号可以省略关键字 signed
结构体
结构体的定义
定义:自定义数据类型的一种,关键字 struct ,结构体类型的变量可以存储多个不同数据类型的 数据。
定义格式:
struct 结构体名
{
数据类型1 成员名称1;
数据类型2 成员名称2;
...
}
注意:结构体中定义的变量,我们称之为成员变量。
格式说明:
结构体名:合法的标识符,建议单词的首字母大写
数据类型n:C语言支持的所有类型
成员名称:合法的标识符,就是变量的命名标准
数据类型n 成员名称n:类似于定义变量,定义了结构体中的成员
注意: 结构体在定义的时候,成员不能赋值
举例:
struct Cat
{
int age = 5;// 错误,结构体定义的时候,成员不能赋值
double height; // 正确
}
常见的定义格式:
方式1:常规定义(命名结构体,只定义类型)---推荐
struct Student
{
int num;// 学号
char name[20];// 姓名
char sex;// 性别
int age;// 年龄
char address[100];// 家庭住址
}
方式2:定义匿名结构体(常用于作为其他结构体的成员使用)
struct Dog
{
char *name;// 姓名
int age;// 年龄
struct // 此时这个结构体就是匿名
{
int year;// 年
int month;// 月
int day;// 日
} birthday;
}
注意:定义匿名结构体的同时必须定义结构体变量,否则编译报错,结构体可以作为另一个结 构体的成员。
总结:
1. 结构体可以定义在局部位置,也可以定义在全局位置;
2. 全局位置的结构体名和局部位置的结构体名可以相同,就近原则(和普通变量的定义 同理)
结构体类型的使用: 利用结构体类型定义变量,定义数组;
结构体类型的使用与基本数据类型的使用类似。
结构体变量的定义
三种形式定义结构体变量: 结构体变量也称为结构体的实力。
第一种
① 先定义结构体
② 然后使用 struct 结构体名 变量名 ;
// 先定义结构体(先定义结构体这个数据类型)
struct A
{
int a;
char b;
}
// 定义结构体变量
struct A x;
struct A y;
第二种 在定义结构体的同时,定义结构体变量;
// 定义结构体的同时定义结构变量
struct A
{
int a;
char b;
} x,y;
struct A z;
此时定义了一个结构体A,x和y是这个结构体类型的变量。
第三种:不推荐
在定义匿名结构体的同时,定义结构体变量;
struct
{
int a;
char b;
} x,y;
struct
{
int a;
char b;
} z;
此时定义了一个没有名字的结构体(称为匿名结构体);y,x是这个匿名结构体类型的变量;
匿名结构体:---弊大于利(尽量少用)
优点:少写一个结构体名称
缺点:只能使用一次;定义的结构体类型的同时就必须定义变量
应用场景: 当结构体的类型只需要使用一次,并且定义类型的同时定义变量。
作为其他结构体的成员使用。
定义结构体的同时,定义结构体变量初始化
struct Cat
{
int age;
char color[20];
} cat;
结构体成员部分初始化是,大括号不能省略。
结构体的成员,没有默认值,是不确定的数
案例:
/**
* 结构体变量的定义
*/
#include <stdio.h>
// 先定义结构体,再定义结构体变量
void fun1()
{
// 先定义结构体
struct A
{
int a;
char b;
};
// 再定义结构体变量
struct A x;
struct A y;
}
// 定义结构体的同时定义结构体变量
void fun2()
{
struct A
{
int a;
char b;
} x,y;
struct A z;
}
// 定义匿名结构体的同时定义结构体变量
void fun3()
{
struct
{
int a;
char b;
} x,y;
struct
{
int a;
char b;
} z;
}
int main()
{
fun1();
fun2();
fun3();
return 0;
}
结构体变量的使用
结构体变量访问结构体成员
格式
结构体变量名.成员名
可以通过访问给成员赋值(存数据)
可以通过访问获取成员的值(取数据)
结构体变量未初始化,结构体的成员值随机(不确定)
结构体变量在定义时,可以初始化 建议用大括号标明数据的范围
结构体成员初始化时,可以部分初始化,部分初始化时一定要带大括号标明数据的范围
案例:
/**
* 结构体变量的初始化
*/
#include <stdio.h>
/* 全局的结构体(数据类型) */
struct Dog
{
char *name;// 姓名
int age;// 年龄
char sex;// M:公,W:母
};
/* 先定义,再初始化 */
void fun1()
{
// 定义一个结构体
// struct Dog
// {
// char *name;// 姓名
// int age;// 年龄
// char sex;// M:公,W:母
// };
// 定义结构体变量
struct Dog dog;
// 给结构体变量的成员赋值
dog.name = "旺财";
dog.age = 5;
// 访问结构体变量的成员
printf("%s,%d,%c\n",dog.name,dog.age,dog.sex);
}
/* 定义的同时初始化 */
void fun2()
{
// 定义结构体变量并初始化
struct Dog dog = {"招财",23,'M'};
// 修改成员的值
dog.name = "进宝";
// 访问结构体变量的成员
printf("%s,%d,%c\n",dog.name,dog.age,dog.sex);
}
int main()
{
fun1();
fun2();
return 0;
}
结构体数组的定义
什么时候需要结构体数组?
比如:我们需要管理一个学生对象,只需要定义一个 struct Student majie;
假如:我们需要管理多个学生对象,此时就需要一个结构体的数组 struct Student students[6 4]; 。
三种形式定义结构体数组
第一种:先定义结构体类型,然后定义结构体变量,再将变量存储到结构体数组中
// 定义一个学生类型的结构体
struct Student
{
char *name;
int age;
float scores[3];// 三门课程的成绩
};
// 定义结构体对象
struct Student zhangsan = {"张三",23,{67.5,89.0,90.0}};
struct Student lisi = {"李四",21,{77.0,80.0,85.0}};
// 定义结构化数组
struct Student students[3] = {zhangsan,lisi};
第二种:定义结构体类型,然后定义结构体数组并初始化
// 定义一个学生类型的结构体
struct Student
{
int id;
char *name;
int age;
float scores[3];// 三门课程的成绩
};
// 定义结构体数组并初始化
struct Student students[3] = {
{1,"张三",23,{67.5,89.0,90.0}},// 注意:这里赋值的顺序需要跟成员在结构体中的顺序一致
{2,"李四",21,{77.0,80.0,85.0}}
};
第三种:定义结构体类型同时定义结构体数组并初始化
// 定义一个学生类型的结构体
struct Student
{
int id;
char *name;
int age;
float scores[3];// 三门课程的成绩
} students[3] = {
{1,"张三",23,{67.5,89.0,90.0}},// 注意:这里赋值的顺序需要跟成员在结构体中的顺序一致
{2,"李四",21,{77.0,80.0,85.0}}
};
第四种:定义结构体类型同时定义结构体数组,然后通过索引给结构体成员赋值
// 定义一个学生类型的结构体
struct Student
{
int id;
char *name;
int age;
float scores[3];// 三门课程的成绩
} sts[3];
sts[0].id = 1;
sts[0].name = "张三";
sts[0].age = 12;
sts[0].scores[0] = 98;
小贴士:
结构体数组名访问结构体成员:
格式:结构体数组名 -> 成员名