c语言
1.函数
1.1函数的运用
函数声明在主函数之后。
#include<stdio.h>
int main(){
//前置说明一个函数,
int add(int a,int b);
void fun();
//声明函数时,函数的类型其实就是他返回值的类型
float sum(float a,float b);
//那么这个函数返回的就是一个浮点型的数
printf("sum = %f\n",sum(1,2));
printf("add = %d\n",add(2,3));
}
int add(int a,int b){
return a + b;
}
float sum(float a,float b){
return a + b;
}
//如果一个函数不加类型的话,那么他默认就是整型的函数
test(){
……
}
//如果没有返回值的函数,应该带void类型
void fun(){
}
函数声明在主函数之前。
#include<stdio.h>
//声明一个整型返回值的两个参数的函数
int add(int a,int b){
return a + b;
}
int main(){
//这里的话我们直接调用这个add函数即可
add();
}
2.2函数的传参
//值传递和址传递
//值传递
#include<stdio.h>
void fun(int a)
{
a += 20;
}
int main()
{
int a = 10;
fun(a);
printf("%d",a);
}
//址传递
#include<stdio.h>
void fun(int* a)
{
*a += 20;
}
int main()
{
int a = 10;
fun(&a);
printf("%d",a);
}
/*
总结:除了数组名之外只要是写变量名的实参就是值传递,在变量名之前有&符号的为址传递
*/
2.3函数的递归
'什么叫函数的递归'
/*
就是函数自己调用自己,并以一定的条件返回跳出递归
*/
#include<stdio.h>
int digui(int);
int main()
{
int a;
scanf("%d",&a);
printf("%d",digui(a));
}
int digui(int num)
{
int digui_num;
if(num < 0)
{
printf("输入错误,请输入正确的参数");
}
else if(num == 0 || num == 1)
{
digui_num = 1;
}
else
{
digui_num = digui(num-1)*num;
}
return digui_num;
}
/*
当我输入的参数是5时,函数会经过以下步骤:d(4)*5,d(3)*4*5,d(2)*3*4*5,d(1)*2*3*4*5,
满足条件,那么d(1)的返回值是1,就是1*2*3*4*5,完成一个求阶层的过程
*/
2.指针
什么是指针。
指针就是指,一个内存单元的地址。因为知道地址之后我们就能找到他,所以把地址就形象化的称为指针。
2.1指针变量
2.1.1什么是指针变量
#include<stdio.h>
void fun(){
//指针变量,是指一个变量里面存放的是一个地址值
int number = 10;
printf("&num = %p\n",&number);
//p里面存放的是number的地址
//因为p声明为指针变量,所以他不管存什么值都会被当作是一个地址
int *p = &number;
printf("p = %p\n",p);
/*
关于*的问题:定义p时,*p表示p是一个指针变量而不是一个普通变量
在使用时,*p的意思是取到所保存的地址值的对应内容
*/
printf("%d\n",*p);
}
int main(){
fun();
}
2.1.2指针变量的大小
指针变量在32位平台是4字节,64位平台是8字节。不管类型的指针都是一样大小
那么问题来了,既然指针变量的大小相同我们为什么要区分他的类型呢。
2.1.3指针变量的类型
#include<stdio.h>
void fun1(){
/*
指针便量有两种类型:自身类型,指向的类型
自身类型:将指针变量名遮住,剩下的就是自身类型 如:int *p = # 那么他的类型就是 int *
指向的类型:将指针变量名遮住和离他最近的*遮住,剩下的就是自身类型 如:int *p = # 那么他的 类型就是 int
指向类型的作用:决定了指针变量所取的空间宽度,和指针变量加1的单位跨度
*/
int num = 120;
//跨越了四个单位长度
int *p = #
printf("num = %p\n",&num);
printf("p = %p\n",p);
printf("p+1 = %p\n",p+1);
//这时他跨越了两个单位长度
short *n = (short *)#
printf("n = %p\n",n);
printf("n+1 = %p\n",n+1);
}
//测试空间宽度
void fun2(){
int number = 0x10203040;
int *p = &number;
printf("%x\n ",*p);
//万能指针
void * zz = p;
//强制转换
short *a = (short *)zz;
//那么他只能容纳两个空间长度
printf("%x\n",*a);
}
int main(){
fun1();
fun2();
}
2.1.4野指针
什么叫野指针,
野指针就是其指向的位置是不可知的。
//三种情况会导致野指针
#include<stdio.h>
int* test(){
int a = 10;
return &a;
}
int main(){
//1.指针在初始化时未被赋值
int *p;
*p;//是在内存中随意指向一片空间
//2.指针越界
int arr[2] = {0}
for(int x=0;x<4;x++){
//这时候循环已经超出arr数组的范围,指针越界导致出现野指针
*(p+x) = 1;
}
//3.指针指向的空间被释放
//注意!这时,当函数test执行完毕后,系统会回收函数内变量的空间。
int* num = test();
}
当我们不知道指针变量去指向谁的时候应该怎么办呢。
#include<stdio.h>
int main(){
//这时候我们可以给指针变量一个空值
int * p = null;
}
2.1.5const关键字
#include <stdio.h>
int main() {
//常量指针,常量修饰的是*所以指向的值不能改
int a= 10;
int b = 20;
const int *p = &a;
p = &b;
//*p = 30; 错误
printf("%d ",*p);
//指针常量,常量修饰的是变量名所以指向空间不能改
int x = 10 , y = 20;
int* const por = &x;
//por = &y; 错误
*por = 90;
printf("%d ",*por);
//所以指向空间和值都不能改
int z = 100, l = 200;
const int* const portin = &z;
//*portin = 10;
//portin = &z;
printf("%d ",*portin);
}
2.1.6void空类型指针
#include<stdio.h>
int main()
{
char a = 'w';
void *pa = &a;
/*
因为void*是一个空类型的指针在引用他指向的内容时
他不知道往后移多少所以不能用于他指向的内容
并且他也不能进行指针的加减运算
*/
printf("%p",pa);
printf("%p",pa+1);
//printf("%c",*pa);
}
2.2指针运算
2.2.1指针加减整数
#include<stdio.h>
void sun();
int main(){
//指针加一个整数
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
int length = sizeof(arr)/sizeof(arr[0]);
for(int x=0;x<length;x++){
printf("%d ",*p);
//p是一个整型指针,他每次加1都会向后挪动4个字节的空间
p++;
}
/*
也可以让他加2
for(int x=0;x<5;x++){
printf("%d ",*p);
p +=2;
}
*/
}
//指针减一个整数
void sun(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = &arr[9];
int length = sizeof(arr)/sizeof(arr[0]);
for(int x=0;x<length;x++){
printf("%d ",*p);
//p是一个整型指针,他每次减1都会向前挪动4个字节的空间
p--;
}
}
2.2.2指针减去指针
前提条件是两个指针所指向的空间一致,如同一数组。
//指针减指针,其实就是用一个地址减去另外一个地址
int main(){
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = &arr[0];
int *p2 = &arr[9];
int result = p2 - p;
//那么得到的会是高位指针之前的元素个数
printf("%d ",result);
}
这种写法的运用。
//手写实现strlen
#include<stdio.h>
int my_strlen(char* a){
char* p = a;
char* p2 = a;
while(*p2 != '/0'){
*p2++;
}
return p2 - p;
}
int main(){
char arr[] = "bit";
int len = my_strlen(arr);
printf("%d ",len);
}
2.3指针与数组
#include<stdio.h>
int main(){
//数组名通常都是代表的是数组元素的首地址
int arr[10] = { 0 };
printf("%p\n",arr);
printf("%p\n",&arr[0]);
printf("%p\n",&arr[0]+1);
/*
有两种情况是例外的
1.用&取地址符时,取出的是整个数组的地址
2.用sizeof(arr)时,他计算的是整个数组的大小
*/
//那么我们如何证明呢
printf("%p",&arr);
printf("%p",&arr+1);
//这时我们会发现,他会直接跨过数组成员地址一次增加40
}
2.3.1指针与数组的连用
#include<stdio.h>
int main(){
int arr[10];
//数组名就是这个数组的首地址
int* p = arr;
for(int x=0;x<10;x++){
*(p+x) = x;
}
for(int i=0;i<10;i++){
printf("%d,",*(p+i));
}
}
2.3.2指针数组
就是一个数组里面存放的是指针。
#include<stdio.h>
int main()
{
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};
int arr3[] = {3,4,5,6,7};
//指针数组
int* parr[] = {arr1,arr2,arr3};
for(int x=0;x<3;x++){
for(int y=0;y<5;y++){
printf("%d ",*(parr[x]+y));
}
printf("\n");
}
}
2.3.3数组指针
就是他是一个指针他指向一个数组‘
#include<stdio.h>
int main()
{
int arr1[] = {1,2,3,4,5};
//数组指针
int (*p)[5] = &arr1; //意思是他是一个指针指向一个里面有五个int类型数据的数组
//使用
for(int x=0;x<5;x++)
{
printf("%d ",*(*p+x));
}
for(int x=0;x<5;x++)
{
printf("%d ",(*p)[x]);
}
}
//二维数组指针排序
#include<stdio.h>
void print(int (*p)[3],int x,int y)
{
int hanc = 0;
for(int i=0;i<x;i++)
{
for(int z=0;z<y-1;z++)
{
for(int j=0;j<y-1-z;j++)
{
if(*(*(p+i)+j) > *(*(p+i)+j+1))
{
hanc = *(*(p+i)+j+1);
*(*(p+i)+j+1) = *(*(p+i)+j);
*(*(p+i)+j) = hanc;
}
}
}
}
for(int i=0;i<x;i++)
{
for(int z=0;z<y;z++)
{
printf("%d ",*(*(p+i)+z));
}
printf("\n");
}
}
int main()
{
int score[2][3] = {{21,11,45},{45,76,12}};
print(score,2,3);
}
/*
首先我们把一个数组名当参数进行传递,传递的是他的首地址,也就是第一个一维数组的地址,
*/
2.4二级指针
二级指针就是,他指向的地址里面存放的还是一个地址.
#include<stdio.h>
int main(){
int arr [10] = { 0 };
int* p = arr;
int** pp = p;
//这时使用这个指针访问时需要进行如下操作
printf("%d ,",**pp);
}
2.5指针与函数
2.5.1实例
#include<stdio.h>
void charge(int* p1,int* p2){
int pump;
pump = *p1;
*p1 = *p2;
*p2 = pump;
}
int main(){
int a = 10,b = 20;
charge(&a,&b);
printf("%d %d",a,b);
}
//这时我们就把他交换过来了,因为我们是传入a和b的地址,所以可以利用地址去取到空间的内容然后进行交换
2.5.2指针,数组传递参数
//二维数组传参
#include<stdio.h>
//标准写法
void fun(int arr[2][3]){}
//错误写法,可以省略行但不能省略列
void fun1(int arr[2][]){}
//这样表示他是要存储一个整型指针,而我们传过来的是一个整型数组的地址
void fun2(int *arr){}
//这样也不能存储他表示他存储的是一个一维指针的指针,而不是我数组地址
void fun3(int **arr){}
//他表示他存储的是一个指向整型长度为3的数组的一个指针
void fun4(int (*p)[3]){}
int main()
{
int arr[2][3] = {{1,2,3},{4,5,6}};
fun(arr);
}
//二级指针传参
void fun(int **p){}
int main(){
//那么我们可以往里面传
int a = 20;
int *p = &a;
int **pp = p;
//可以往里面传一个指针数组
int *arr[10];
fun(&p);
fun(pp);
//arr是数组首地址,存放着一个整型指针
fun(arr);
}
2.5.3函数指针
#include<stdio.h>
void fun(const char* a)
{
printf("%s",a);
}
int main()
{
void (*p)(const char*) = fun;
(*p)("hello world");
}
#include<stdio.h>
int main()
{
//强制转换0的类型为一个无返回值的无参的函数指针类型,然后取内容调用
(* (void (*)()) 0)();
//定义一个fun函数,参数为整型和无返回值无参指针,fun返回值为一个函数指针
void(* fun(int,void(*)()) )(int);
}
2.5.4函数指针数组
函数指针数组就是一个数组里面存放着函数指针,
#include<stdio.h>
int add(int x,int y)
{
return x + y;
}
int sub(int x,int y)
{
return x - y;
}
int mian()
{
int (*p[2])(int,int) = {add,sub};
for(int x=0;x<2;x++)
{
printf("%d\n",p[x](3,2));
}
}
//使用函数数组指针来完成计算器的功能
#include<stdio.h>
//菜单函数
void meuo()
{
printf("*******************\n");
printf("***1.add 2.sub***\n");
printf("***3.mul 4.div***\n");
printf("*** 0.exit ***\n");
}
int add(int x,int y)
{
return x + y;
}
int sub(int x,int y)
{
return x - y;
}
int main()
{
int input = 0;
int x = 0,y = 0;
do{
meuo();
printf("请输入需要的功能");
scanf("%d",&input);
int (*parr[])(int ,int) = {0,add,sub};
if(input >=1 && input<=2)
{
printf("请输入你要操作的数");
scanf("%d%d",&x,&y);
printf("%d\n",parr[input](x,y));
}
else if(input == 0)
{
printf("退出成功");
}
else
{
printf("输入错误\n");
}
}while(input);
}
//指向函数数组指针的指针
#include<stdio.h>
int add(int x,int y){}
int main()
{
//函数指针
int (*p)(int,int) = add;
//函数指针数组,p是一个数组,数组里存放了两个元素为函数指针
int (*p[2])(int ,int) = {add,sub};
//指向函数数组指针的指针
int (*(*pp)[2])(int ,int) = p;
}
2.5.5回调函数
什么叫做回调函数,
就是把一个函数当成参数传递给了另外一个函数,并由另外一个函数进行调用。当成参数传递是传递的函数地址需要用函数指针来接收
//使用函数数组指针来完成计算器的功能
#include<stdio.h>
//菜单函数
void meuo()
{
printf("*******************\n");
printf("***1.add 2.sub***\n");
printf("***3.mul 4.div***\n");
printf("*** 0.exit ***\n");
}
int add(int x,int y)
{
return x + y;
}
int sub(int x,int y)
{
return x - y;
}
void club(int (*p)(int,int))
{
printf("请输入你要操作的数");
int x = 0,y = 0;
scanf("%d%d",&x,&y);
printf("%d\n",p(x,y));
}
int main()
{
int input = 0;
do{
meuo();
printf("请输入需要的功能");
scanf("%d",&input);
switch(input)
{
case 1:
club(add);
break;
case 2:
club(sub);
break;
case 0:
printf("退出成功");
break;
default:
printf("输入错误\n");
break;
}
}while(input);
}
2.6字符指针
2.6.1实例
#include<stdio.h>
int main()
{
char arr[] = "abcde";
//把数组的首地址给了p,*p打印的话就是a
char *p = arr;
printf("%s\n",arr);
printf("%s\n",p);
//这里并不是把p2的值付为hello,而是把h这个首地址给到p2存放
//这个hello是常量的形式,我们在char* 前面加上const,不许让其修改p2所指向的内容
const char* p2 = "hello";
printf("%s\n",p2);
}
3.结构体
结构就是一些值的集合,这些值称为成员变量,成员变量可以是任意数据类型,
3.1.1结构体的声明与使用
#include<stdio.h>
//声明一个结构体类型
struct Student{
//成员变量
char name[50];
int age;
char telNum[12];
char sex[5];
}s1,s2; //s1,s2为声明的全局结构体变量
int main(){
//声明局部的结构体变量
struct Student s;
}
#include<stdio.h>
//声明一个结构体类型
typedef struct Student{
//成员变量
char name[50];
int age;
char telNum[12];
char sex[5];
}stu; //加上typedef之后反括号后就变成这个Student结构体类型的别名
int main(){
//声明局部的结构体变量并初始化
struct Student s = {"jjs",12,"13277776987","男"};
printf("%s",s.name);
//使用别名声明结构体变量
stu s2;
}
//结构体里面存放结构体,初始化和调用的方式
#include<stdio.h>
struct Pre{
char name[10];
int age;
};
struct cher{
char cName[10];
struct Pre p;
};
int main(){
//声明结构体变量并初始化
struct cher c = {"jjs",{"jtt",13}};
printf("%d",c.p.age);
}
3.1.2结构体传参
箭头操作符(->)用于结构体指针指向结构体内的数据
#include<stdio.h>
//定义结构体变量
struct s{
char name[10];
int age;
};
int main(){
struct s s1 = {"jjs",12};
struct s* p = &s1;
printf("%s",p->name);
}
/*
因为p里面保存的是s1的地址,所以 p->name == s1.name
*/
#include<stdio.h>
struct Student{
char name[50];
int age;
char telNum[12];
char sex[5];
};
printf1(struct Student a){
printf("$d",a.age);
}
printf2(const struct Student* p){
printf("%s",p->name);
}
int main(){
//声明局部的结构体变量
struct Student s = {"jjs",12,"13277776987","男"};
//通过函数打印结构体变量s
printf1(s);
printf2(&s);
}
/*
推荐使用下面的那种方案进行结构体的传参,上面的方案直接拷贝结构体变量s,他需要的时间和内存都远远大于下面的直接给地址
*/
3.1.3结构体的大小
/*
如何计算结构体的大小:
1.首元素摆放着结构体的首地址
2.其他成员变量按照对齐数的倍数处进行存放(对齐数:默认对齐数和成员大小的较小值)
3.总大小要是最大对齐数的倍数,(比如我里面存放了一个整型成员变量那么总大小就要是4的倍数)
4.要是嵌套了结构体那么他的对齐数按照他自身成员变量的最大对齐数,总大小还是按照最大对齐数的倍数(包 括被嵌套的结构体)。
*/
#include<stdio.h>
struct Stu
{
char c;
int num;
char b;
};
struct Stu2
{
char c;
char b;
int num;
};
int main()
{
struct Stu s = { 0 };
printf("%d\n",sizeof(s));
struct Stu2 s2 = {0};
printf("%d",sizeof(s2));
}
//修改默认对齐数
#include<stdio.h>
//设置默认对齐数
#pragma pack(4);
struct Stu
{
char c;
double num;
char b;
};
//还原默认对齐数
#pragma pack();
int main()
{
struct Stu s = { 0 };
printf("%d\n",sizeof(s));
}
4 库函数的使用
4.1字符串函数
4.1.1strlen
/*
strlen函数,用于检测字符串的长度,需要传递一个字符数组的地址作为他的形参,他会返回一个无符号的整型类数 据(内部实现是通过查找'\0'字符)
*/
#include<stdio.h>
#include<string.h>
int my_strlen(char* p)
{
int count = 0;
while(*p != '\0')
{
count++;
p++;
}
return count;
}
int main()
{
char arr[] = "abcdfg";
printf("%d",my_strlen(arr));
//两个无符号的数相减得到的依然会是一个无符号的数
if(strlen("abc") - strlen("abcd") > 0)
printf("hehe");
else
printf("haha");
}
4.1.2strcpy
/*
strcpy函数,用于将源字符串拷贝到目标字符串上,并返回一个指向目标字符串的字符指针(内部实现,一一替换'\0'为止)
要求:1.目标字符的长度要足以容纳拷贝进来的源字符串
2.源字符串要求有\0结尾
*/
//实现
#include<stdio.h>
#include<string.h>
//普通写法
char* my_strcpy(char* dest,char* str)
{
char* ret = dest;
while(*str != '\0')
{
*dest = *str;
dest++;
str++;
}
//将\0拷贝进去
*dest = *str;
return ret;
}
//牛逼写法
char* my_strcpy(char* dest,char* str)
{
char* ret = dest;
//当这个表达式的结果为\0是阿斯克吗表对应为0
while(*dest++ = *str++){}
return ret;
}
int main()
{
char arr[] = "abcdefg";
char arr1[] = "licaz";
my_strcpy(arr,arr1);
printf("%s",arr);
}
4.1.3strcat
/*
strcat 函数将一个字符串追加到目标字符串上,并返回目标字符串地址(找到目标字符串的\0对其进行替换)
*/
#include<stdio.h>
char* my_strcat(char* desc,char* str)
{
char* ret = desc;
while(*desc)
{
desc++;
}
while(*desc++ = *str++);
return ret;
}
int main()
{
char arr[30] = "abcd";
char arr2[] = "efg";
my_strcat(arr,arr2);
printf("%s",arr);
}
4.1.4strcmp
/*
strcmp 比较两个字符串大小的函数,第一个比第二个大返回1等于返回0小于返回-1
*/
#include<stdio.h>
int my_strcmp(char* p,char* p2)
{
while(*p == *p2)
{
if(*p == '\0')
{
return 0;
}
p++;
p2++;
}
if(*p > *p2)
return 1;
else
return -1;
}
int main()
{
char *p = "abcde";
char *p2 = "efg";
printf("%d\n",my_strcmp(p,p2));
}
4.1.5strncpy
/*
strncpy 函数 用于将源字符串拷贝到目标字符串上根据我们给定的第三个参数指定替换掉多少个字符
*/
#include<stdio.h>
char* my_strncpy(char* p1,char* p2,int num) {
int i = 0;
while (num && *(p2 + i))
{
p1[i] = p2[i];
num--;
i++;
}
//当源字符串小于要替换的个数时,我们直接在他后面在\0
if (num)
{
while(num)
{
num--;
p1[i] = '\0';
i++;
}
}
return p1;
}
int main()
{
char str[] = "abcdefg";
char str2[] = "asd";
my_strncpy(str,str2,5);
printf("%s",str);
}
4.1.6strncat
/*
strncat 函数将一个字符串按照第三个参数所指定的要追加字符的个数追加到目标字符串上
*/
#include<stdio.h>
char* my_strncat(char* p1,char* p2,int count)
{
char* ret = p1;
while(*p1) p1++;
while(count)
{
count--;
if(*p2)
{
*p1++ = *p2++;
}
else
{
*p1 = '\0';
return ret;
}
}
return ret;
}
int main()
{
char str[20] = "abcdefg";
char str2[] = "asd";
printf("%s",my_strncat(str,str2,2));
}
4.1.7strstr
/*
strstr 函数,用于在母串中查找一个字串
*/
#include<stdio.h>
char* my_strstr(char* p,char* p2)
{
//用于记录我们进行查找的位置
char* cur = p;
char* s1 = NULL;
char* s2 = NULL;
//如果字串为空
if(!*p2) return p;
while(*cur)
{
s1 = cur;
s2 = p2;
//相当于(s1 != '\0')
while(*s1 && *s2 && (*s1 == *s2))
{
s1++;
s2++;
}
//如果为s2现在所指向的空间内容为\0证明查找完毕
if(!*s2) return cur;
if(!*s1) return NULL;
cur++;
}
return NULL;
}
int main()
{
char* p = "abbbcd";
char* p2 = "bbc";
printf("%s",my_strstr(p,p2));
}
4.1.8srttok
/*
strtok 函数,用于切割字符串,第二个参数传入要寻找切割的字符,这个函数找到后会把要寻找的那个字符替换成 \0。并返回首字符地址,函数下次执行传入空指针,他会从替换位置开始找
*/
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "www.123@163.com";
const char* p = "@.";
char* ret = NULL;
for(ret = strtok(arr,p);ret != NULL;ret = strtok(NULL,p))
{
printf("%s\n",ret);
}
//函数会把找到的字符替换成\0所以我们需要拷贝后使用最佳
printf("%s",arr);
}
strerror,函数
/*
使用这个函数之前我们首先要导入errno.h文件,在c语言执行的过程中,如果发生了一些错误c语言就会把这些错误代码都存放到errno.h这个文件里面我们通过errno这个变量就可以拿到这些错误代码
*/
#include<stdio.h>
#include<string.h>
#include "errno.h"
int main()
{
FILE* fp = fopen("test.txt","r");
if(fp == NULL)
{
printf("%s\n",strerror(errno));
}
else
{
printf("open file success");
}
}
4.2内存函数
4.2.1memcpy
/*
memcpy函数,内存拷贝函数,可以拷贝任意类型的数据,他有三个参数,目标数据,源数据,要拷贝的字节
*/
#include<stdio.h>
#include<string.h>
void* my_memcpy(void* p,void* p2,int sz)
{
while(sz)
{
sz--;
*(char *) p = *(char *) p2;
(char *)p++;
(char *)p2++;
}
return p;
}
int main()
{
int arr[] = {1,2,3,4,5};
int arr2[5] = {0};
my_memcpy(arr2,arr,sizeof(arr));
for(int i=0;i<5;i++)
{
printf("%d ",arr2[i]);
}
}
//我们要将数组arr中的123,拷到234的位置我们需要怎么做
int main()
{
int arr[] = {1,2,3,4,5};
int arr2[5] = {0};
/*
当这里我要想拷贝重叠的字符串,想要得到的结果是1,1,1,1,5而我们想要的是1,1,2,3,5
因为当他将第二个元素替换成1时那么我的2就被替换成了1所以后面的全是1,那么我们就要用到另外一个函数
memmove
*/
my_memcpy(arr+1,arr,12);
for(int i=0;i<5;i++)
{
printf("%d ",arr[i]);//1,1,1,1,5
}
}
4.2.2memmove
/*
memmove 函数,用于拷贝相同的字符串的重叠部分,用于解决上面所出现的问题
*/
#include<stdio.h>
void* my_memcpy(void* p,void* p2,int sz)
{
char* ret = (char*) p;
if(p > p2)
{
while(sz--)
{
//如果说我要拷的指针在我数组之前那么我们就从后往前拷
*((char*)p+sz) = *((char*)p2+sz);
}
}
else
{
while(sz)
{
*(char*) p = *(char*) p2;
(char*)p++;
(char*)p2++;
sz--;
}
}
return ret;
}
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9};
int arr2[5] = {0};
my_memcpy(arr+2,arr,20);
for(int i=0;i<9;i++)
{
printf("%d ",arr[i]);
}
}
5 自定义数据类型
5.1位段
/*
位段的主要作用就是节省空间
位:二进制位
根据后面的数字为其分配空间 1 = 1bit 注意数字不能超过他类型本身的bit位比如int类型32bit位就不能写大 于32的数
主要缺点就是跨平台性差,没有规定数据从右往左存还是从左往右存以及int类型的符号位判断
*/
#include<stdio.h>
struct wd
{
int mun : 4; //给他分配四个bit位
int mun2 : 4;
};
int main()
{
struct wd w = {0};
printf("%d",sizeof(w));
}
5.2枚举 enum
/*
枚举就是,一 一列举,把所有的可能性都放在枚举里面,枚举的成员是常量如果不进行赋值,那么他将会是一个整型 元素并且进行递加
枚举相对于#defien的好处
1.阅读性更高2.避免命名污染3.类型更确定
*/
#include<stdio.h>
enum S
{
MAN = 1,
WONAM
};
int main()
{
enum S s = MAN;
printf("%d\n",s);
}
5.3共用体 union
/*
共同体的大小由成员最大大小决定并且是最大对齐数的倍数,成员共用一块空间,所有只有一个成员变量起作用
*/
#include<stdio.h>
union Un
{
char a;
int num;
};
union un
{
char name[5];
int num;
};
int main()
{
union Un u;
printf("%d",sizeof(u));//4
union un u2;
printf("%d",sizeof(u2));//8
}
6.动态内存分配
什么是动态内存分配,
就是利用一些函数分配堆区的空间
6.1为什么要动态分配内存
/*
因为内存太宝贵,比如我由一个整型的数组,定义的时候定义了100个长度可是实际上我只使用了19个这样的话就会导致后面的空间浪费。所以要动态的开辟空间
*/
6.2malloc calloc and free
/*
malloc函数用于向堆区申请一块内存,他的参数是申请空间大小的字节,返回值是一个指向该空间首元素地址的空指针,注意:使用malloc函数申请的内存一定要释放,否则回导致内存泄漏。
free函数用于释放malloc函数申请的内存,参数为指向申请空间的指针
*/
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main()
{
//申请内存
int* p = (int*)malloc(40);
//是否申请成功
if(p == NULL)
{
printf("%s",strerror(errno));
}
else
{
for(int i=0;i<10;i++)
{
*(p+i) = i;
printf("%d ",*(p+i));
}
}
//释放内存
free(p);
//修改指针指向
p = NULL;
}
/*
calloc函数,也是用于动态的开辟空间,与malloc不同的是,他有两个参数,第一个参数是要申请元素的个数,第二个申请元素的字节大小,并且他将会把申请的元素全部赋值为0
*/
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main()
{
//申请内存
int* p = (int*)calloc(10,4);
//是否申请成功
if(p == NULL)
{
printf("%s",strerror(errno));
}
else
{
for(int i=0;i<10;i++)
{
printf("%d ",*(p+i));
}
}
//释放内存
free(p);
//修改指针指向
p = NULL;
}
6.3realloc
/*
realloc 该函数用于对申请的动态内存空间重新分配,(也就是我原本申请了20个字节现在觉得过大或者过小那么我们就可以对其进行重新分配)他有两个参数第一个为申请过的动态空间的指针,第二个为现在要修改成的大小
*/
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main()
{
//申请内存
int* p = (int*)calloc(5,4);
//是否申请成功
if(p == NULL)
{
printf("%s",strerror(errno));
}
else
{
for(int i=0;i<5;i++)
{
*(p+i) = i;
}
}
int* ptr = (int*) realloc(p,40);
if(ptr != NULL)
{
p = ptr;
for(int x=0;x<10;x++)
{
printf("%d ",*(p+x)); //后5个为随机值证明我们增加成功
}
}
//释放内存
free(p);
//修改指针指向
p = NULL;
}
/*
使用realloc函数也可以开辟空间,第一个参数传递空指针即可
*/
#include<stdio.h>
int main()
{
int *p = (int*) realloc(NULL,40);
if(*p == NULL)
{
return 0;
}
free(p);
}
6.4柔性数组
#include<stdio.h>
#include<stdlib.h>
struct S
{
int a = 10;
int arr[]; //结构体的最后一个元素是数组的话他可以是未知大小的
};
//对于柔性数组的使用
int main()
{
struct S s;
struct S* ps = (struct S*)malloc(sizeof(s)+sizeof(int)*5);
ps->a = 100;
for(int i=0;i<5;i++)
{
ps->arr[i] = i;
}
struct S* ret = (struct S*)realloc(ps,44);
if(ret != NULL)
{
ps = ret;
for(int x=5;x<10;x++)
{
ps->arr[x] = x;
}
}
for(int z=0;z<10;z++)
{
printf("%d ",ps->arr[z]);
}
free(ps);
ps = NULL;
}
//也可以使用另外一种方式来替代柔性数组
#include<stdio.h>
#include<stdlib.h>
typedef struct S
{
int n;
int* arr;
}test;
int main()
{
test* ps =(test*) malloc(sizeof(test));
ps->arr = (int*) malloc(5*sizeof(int));
for(int i=0;i<5;i++)
{
ps->arr[i] = i;
printf("%d ",ps->arr[i]);
}
//释放申请的内存,因为申请了两次所以都要释放,如果先释放ps指针会找不到ps->arr指针
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
}
7.文件 file
7.1文件的基本使用
7.1.1开关文件
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt","w");
if(pf == NULL)
{
printf("文件打开失败\n");
return 0;
}
printf("打开文件成功\n");
//关闭文件需要传递一个文件指针
fclose(pf);
pf = NULL;
}
7.2操作文件的函数
7.2.1输入输出字符函数
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt","w");
if(pf == NULL)
{
printf("文件打开失败\n");
return 0;
}
//fputc函数向文件内输出一个字符,第一个参数是要输出的字符第二个是文件流指针
fputc('j',pf);
fputc('t',pf);
fputc('t',pf);
fclose(pf);
pf = NULL;
}
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt","r");
if(pf == NULL)
{
printf("文件打开失败\n");
return 0;
}
//fgetc函数读取一个字符输入到屏幕上,只需传文件指针,多次调用便会输入到后面的字符
printf("%c",fgetc(pf));//j
printf("%c",fgetc(pf));//t
printf("%c",fgetc(pf));//t
fclose(pf);
pf = NULL;
}
7.2.2输入输出一行字符的函数
#include<stdio.h>
int main()
{
//读取文件的其中一行的内容
FILE* pf = fopen("text.txt","r");
char arr[1024] = {0};
//读取的内容存到哪里,读多少个字节,从哪个文件里面读
fgets(arr,1024,pf);
//专门用来打印字符串的函数
puts(arr);
}
#include<stdio.h>
int main()
{
//写入文件的其中一行的内容
FILE* pf = fopen("text.txt","w");
char arr[1024] = {0};
gets(arr);
//从哪里读取写入的内容,并写到哪里
fputs(arr,pf);
}
7.2.3输入输出格式内容
//输出
#include<stdio.h>
typedef struct test
{
int num;
float f;
char arr[10];
}test;
int main()
{
FILE* pf = fopen("text.txt","w");
test t = {10,3.14,"taotao"};
//格式输出,向指定文件内输出格式数据
fprintf(pf,"%d %f %s",t.num,t.f,t.arr);
fclose(pf);
pf = NULL;
}
#include<stdio.h>
typedef struct test
{
int num;
float f;
char arr[10];
}test;
int main()
{
FILE* pf = fopen("text.txt","r");
test t = {0};
//格式输入,找到指定文件的一些格式化内容
fscanf(pf,"%d %f %s",&(t.num),&(t.f),t.arr);
printf("%d %f %s",t.num,t.f,t.arr);
fclose(pf);
pf = NULL;
}
7.2.4以二进制的形式读写文件
#include<stdio.h>
typedef struct test
{
int num;
float f;
char arr[10];
}test;
int main()
{
test t = {10,56.3,"jjs"};
//以二进制的形式写入一个文件
FILE* pf = fopen("text.txt","wb");
//要写入内容的地址,元素大小,多少个,写入的文件的地址
fwrite(&t,sizeof(test),1,pf);
fclose(pf);
pf = NULL;
}
#include<stdio.h>
typedef struct test
{
int num;
float f;
char arr[10];
}test;
int main()
{
test tmp = {0};
FILE* pf = fopen("text.txt","rb");
fread(&tmp,sizeof(test),1,pf);
printf("%d %f %s",tmp.num,tmp.f,tmp.arr);
fclose(pf);
pf = NULL;
}