一、什么是C语言
C语言是一种强类型语言,变量的类型一旦被确定,在其整个生命周期中是不可被改变的。
int main()//主函数框架
{
return 0; //c语言中/**/不能嵌套
}
- 一个c语言源程序可以由多个源文件组成,但只能有一个主函数
- 一个源文件可以由多个函数组成
- 每个语句需要用;结尾
二、基本数据类型
//整型
char;//1字节
short; //2字节
int; //4字节
long int;//4字节
long long;//8字节
//浮点型
float;//4字节
double;//8字节
long double;//8/12/16
//
bool;//只有真假值 1字节
//
void//无类型 无大小
每一种类型在定义变量时,所分配存储单元个数不同
三、变量、常量、标识符
- 变量:可读可写;
- 常量:只可读;
- 标识符:见名知义
- 变量:为某一变量分配空间,并赋予变量名(标识符)。该变量与所分配的空间是共存亡的,并且内存位置也不会发生改变。一个变量名在同一个作用域中是不可以被重复定义的。
- 声明:
1、变量:
变量分为:全局变量(函数之外定义)、局部变量(函数内定义)、块内变量(函数中{}内定义,只在{}有效)
int a=100;//全局
void fun()
{
int b=100;//局部变量
}
int main()
{
int b; //b的可见性只在其函数内所以不冲突
{
int c;//块内变量
}
}
注:当全局变量名与局部变量名相同时,采用就近原则(与上一条语句就近)
int a=100;
void fun()
{
int b=a;
int a=50;
int c=a;
int d=::a//使用全局变量
{
int c=60;//该语句只在此块内有效
}
}
//b=100 c=50
2、常量:
常量分为:字面常量、用#defien定义的常量、用const修饰的常量(必须赋初始值)、枚举常量、字符常量和字符串常量
#define MAX 5//宏常量 无类型且不分配空间
enum week={mon=1};
int main()
{
int a=10;//10为字面常量
int b=3*MAX;//在预编译过程在MAX会被替换为5,因此宏常量起到的是替换作用。
//.i文件时该语句 int b=3*5
const int c=50;//不可写,可读
const int ARR[10]={1,2,3,4}//数组每个量都变为常量
enum week d=mon;//若枚举常量未赋值,则从0开始,下一个量累加1(只接收整型)
return 0;
}
注:
- 在编译的过程中才去识别类型
- .c .i文件都是文本文件
- .i文件时宏常量以不存在,即已经发生了替换
3、转义字符
- ‘ ’字符定界符;“ ”字符串定界符;\转义字符(普通->特殊;特殊->普通)
转义字符 | 功能 | ASCII |
---|---|---|
\n | 换行符 | 10 |
\r | 回车符 | 13 |
\t | 水平制表符 | 9 |
\000 | 1~3为8进制代表的字符 |
char ch='\''//单引号
char ch=‘0’(字符0) | ASCII 48 |
---|---|
char ch=0 | 0(false) |
char ch=’\0’(空字符) | 0(false) |
char ch=‘a’ | 97 |
int *p=NULL | 0(false) |
- 字符串以‘\0’为结束
字符 | ASCII |
---|---|
‘ ’(空格) | 32 |
‘0’ | 48 |
A | 65 |
a | 97 |
回车 | CR |
换行 | LF |
四、作用域与生存期
代码区 |
---|
数据区 |
堆区 |
栈区 |
1、作用域
-
作用域是指标识符能够被使用的范围。(此阶段针对编译与链接过程)在可用范围内变量才能被使用。
-
全局变量作用域时在定义后,对其一下的语句可见
-
局部变量的作用域是在其所定义的函数内可见
int a=20;//全局 void fun() { int b=0; int b=a;//a对b是可见的,此语句可执行 int b=c;//c对b是不可见的,因此无法进行赋值操作 } int c=15; int main() { int b=20;//主函数中的b和fun中的b是不冲突的,局部变量作用域范围不同 }
2、生存期(分配内存空间)
-
生存期针对函数的执行过程。不同的作用域下变量的生存期是不同的。(编译链接通过,程序才会被执行)
-
生存期是变量在程序执行过程中被创建,并分配相应内存,在程序结束时销毁,内存空间也被回收。
-
全局变量存放在数据区,局部变量存放在栈区。
-
全局变量是在主函数执行前被存放在数据区,程序结束后释放
-
局部变量在函数执行时,被存放在栈帧中,函数结束后被释放。
void fun() { int a,b; } int main() //在主函数执行到void()函数时,栈才会给a,b分配空间 { void() }
五、操作数与运算符
1、操作数
int a;
int b=a+10; //a,b,10都为操作数
操作数就时程序中的变量,常量,逻辑值等都可称为操作数(程序操作的数据实体)
2、左右值
在=左边为左值,右边为右值;若为左值则代表该值可读可写;只有右值只可读不可写(该值可以赋给其它变量,但不可被其他变量赋值)。
int a=0;
a=10;
10=a;//不能执行,常量是不可修改的
3、运算符
- 对操作数进行相应操作
- 分类:一目运算符,二目运算符,三目运算符
- 三目运算符解决简单的if语句((a>b)?a:b)
1、前置、后置++
-
++在赋值语句中:
int a=1,b=0; b=a++;//后置++是先将a中的值取出放入一个临时的空间中,将a赋值给b,然后将a的值++回写给a(先赋值后++) b=++a;//前置++是先将a的值取出放入临时空间,将a进行++后回写给a并赋值给b
-
在不进行赋值的情况
for(int i=0;i<100;i++/++i)无区别
2、取余操作符
//打印0~100内的偶数
for(int i=0;i<100;i++)
{
if(i%2==0)
{
printf(....)
}
}
//
n%(10,100,....)其所得结果则是在(0~10,0~100范围内)
3、赋值运算符
✨想要改变某个变量的值(除++)一定要进行赋值的操作
六、关键字
-
数据类型关键字:
char int short float double signed unsigned struct union enum typedef sizeof auto static register extern const volatile(多线程使用)
-
流程控制关键字:
if else switch case default for while do return continue break goto
1、sizeof
sizeof:既不是宏也不是函数
int a=10;
int b=sizeof(++a);//sizeof只解析类型大小,++a不进行运算
2、typedef
typedef:给复杂的类型(合法的变量定义)名起一个简单的别名;与其他关键字不能出现在同一表达式;并且需注意作用域问题。(将变量名转化为类型)
typedef char ch;
typedef int Array[10];
typedef int *PIN;
struct student
{
char name[10];
int age;
}stud,*sp;
//stud=>struct student stud
//*sp=>struct student *sp
typedef struct student
{
char name[10];
int age;
}stud, * sp;//stud,*sp被定义为了类型名
int main()
{
char a = 'A';
ch b = 'B';
//ch不再是变量名,变为类型名
Array br = { 1,5,6,3,7,9 };
PIN p=NULL;//p为整型指针
}
3、static
-
static关键字可以延长变量的生产周期存放在数据区
-
static修饰的变量值被初始化一次(作用域问题)
void fun(int x) { int a = 0; int static b = 0; a += x; b += x; printf("%d %d\n", a, b); } int main() { for (int i = 0; i < 10; i++) { fun(i); } return 0; }
4、extern
- extern:用于声明某变量/函数来自同一工程下的其他文件,在此文件下使用。(局部变量与被static所修饰的变量不能使用extern)
七、顺序、循环、选择语句
bool类型
- c语言中真有多种(非0),而假只有0
- bool类型只有两个值true和false
- bool类型一字节0000 000 0最后一位用于表示真假
- 关系表达/逻辑表达式运算结果为bool值(a&&b:当a为假时表达式b将不参与运算;a||b a为真时表达式b不参与运算)
- !虽是单目运算符但没有回写能力与++/–不同,要进行赋值操作。
- 优先级:= < &&和|| < 关系运算符 <算术运算符 < 非(!)
1、顺序语句
逐行执行
int main()
{
int a=10;
int b=a;
printf(.....);
}
2、选择语句
1、if语句
if(true)
{
......
}
else
{
......
}
注:if语句中进行条件判断时,若是进行等于判断需用==
2、switch
-
switch语句是多分支语句
switch(整形变量表达式) { case 常量:语句块 break; default: break; }
-
break用于退出switch。
3、循环语句
1、while循环
while(条件)
{
.....
}
先判断后执行
2、do…while循环
do
{
......
}while(条件)
先执行后判断
3、for循环
-
for(表达式1;表达式2;表达式3)
{循环语句}
-
表达式一只执行一次,再由表达式2进行判断(true Or false),为真执行表达式三,执行循环体。
4、空语句
-
空语句仅由分号组成,不执行任何操作
int a=10; ;
-
空语句所带来的问题:若在if、while、for后加上空语句那么其本身的逻辑结构将会被破坏,不会完成其功能。
if(true) ; for(int i=0;i<n;i++) ; while(true) ;
5、逗号表达式
逗号表达式优先级最低,执行顺序从左向右。
八、函数
-
函数是用来完成某种特定且单一的功能;函数分为库函数与自定义函数
-
库函数:库函数是由系统自身提供的函数。
-
自定义函数:用户自己编写,由返回类型,函数名,形参列表,函数体构成
int fun(int a,int b) { int c=0; c=a+b; return c;//return 时将c存入临时空间,再将c赋给max,因为作用域的问题c不能在fun与main中同时发挥作用 } int main() { int a=10,b=20; int max=fun(a,b); }
-
-
形参与实参
- 形参是再函数被调用时接受传递进来的参数,只有函数被调用时才会被分配空间,函数结束后空间被释放
- 实参是函数被调用时传递给形参的参数(可为常量、变量、表达式、函数)
- 函数调用中数据的传递是单向的,可以把实参的值传递给形参,但不能把形参的值反传给实参
- 将实参值传递给形参也称为实参的入栈(从右向左依次入栈)
-
值传递与址传递
void fun(int a, int b) { int temp = a; a = b; b = temp; } int main() { int a = 10; int b = 20; fun(a, b); printf("%d %d\n", a, b); }
无法实现数值的交换,因为值传递的方向时单向的
址传递:
void fun_2(int* a, int* b) { int temp = *a; *a = *b; *b = temp; }
void fun(int a, int b) { int temp = a; a = b; b = temp; } int main() { int a = 10; int b = 20; fun(a, b); printf("%d %d\n", a, b); }
-
void Prin_ar(int br,int n) { sizeof(br);//==4;当数组进行传参时,数组会退化为指针,因此需将数组的大小一并传入。 } int main() { int ar[4]={0,1,2,3} Prin_arr(ar,4); }
九、数组
-
数组=类型+元素个数
int arr[10]={};//定义数组时元素个数需是大于零整型常量表达式
-
数组在内存中是连续存放的
-
访问数组时,通过数组名和下标访问,下标从0开始。(下标为整形常量或变量)
for(int i=0;i<5;i++) { printf("%d",arr[i]); } printf("%d",arr[3]);
-
数组的打印方式
int main() { int arr[5] = { 23,34,45,56,67 }; int* p = arr; for (int i = 0; i < 5; i++) { printf("%d %d %d\n", arr[i], *(arr + i), i[arr]); }//arr[i]=>*(arr+i) i[arr]=>*(i+arr) return 0; }
十、指针
-
指针变量用于访问存储地址的变量(存放地址);指针就是内存的地址
-
无论什么类型的指针,大小都为4字节(32位平台下)。
-
指针分为野指针(声明但未初始化)与空指针(初始化为空)
-
*在不同情况下的含义:
int a*b;//*表示乘法运算符 int *a;//*表示声明,声明a为指针变量 *a=20;//*表示指向,及解引用
在定义指针中*与变量名结合
int *a,b;//a是指针,b是整形变量
-
int *p=NULL;//声明指针变量p int a=10; //int *p=&a; p=&a;//p存放a的地址 *p=100;//*p就是a本身,改变*p的值也就是改变a的值(解引用)
-
指针有两个值一个是其自身的值(存储的地址),还有一个是其指向的值(解引用)
-
指针存放的是各种类型变量的首地址(低址值),地址由32位二进制构成,故指针的大小为4字节。
int main()
{
int arr[5] = { 12,23,34,45,56 };
int* p = arr;
int x = 0, y = 0;
//*与++优先级相同,因此从右向左依次结合
x = *++p;
y = *p;
printf("%d %d\n", x, y);//23 23
x = ++ * p;
y = *p;
printf("%d %d\n", x, y);//24 24
x = *p++;
y = *p;
printf("%d %d\n", x, y);// 23 34
x = (*p)++;
y = *p;
printf("%d %d\n", x, y);//34 35
return 0;
}
-
指针的运算
-
指针可以与整形相加(增加的为所对于变量类型所占字节的大小)
-
指针之间不可相加(例如日期不可相加)
-
同类型指针指向连续空间可以相减,减后结果是数据元素的大小(int型结果是整型元素的个数)
int main() { const int n = 5; int arr[n] = { 10,20,30,40,50 }; int* pl = &arr[0]; int* pf = &arr[4]; printf("(pf-pl)=%d\n", (pf - pl)); return 0; }
-
函数与指针
void fun(int* p)
{
int a = 200;
*p = 100;
p = &a;
}
int main()
{
int x = 0;
int* s = &x;
fun(s);
printf("%d %d\n", x, *s);
return 0;
}
数组与指针
-
数组的数组名只有在sizeof()中代表整个数组。其他情况下表示的是数组首元素地址。
-
某类型指针加一,增加所占类型所占字节
-
int main() { const int n = 5; int arr[n] = { 12,23,34,45,56 }; int* p = arr; for (int i = 0; i < n; i++) { //printf("%d %d\n", arr[i], *(arr + i),i[arr]); //arr[i]==*(arr+i),i[arr]=*(i+arr) printf("%d %d\n", p[i], *(p + i)); } return 0; } //arr是存储int类型数据的数组,arr是指向数组第一个元素的指针,当arr+1,arr指向数组第二个元素,但int类型的大小为四字节,所以arr+1,就相当于指针跳过了四个字节。
-
传参时数组会被退化为指针(节省时间与空间)
指针变量与const
int const *p;
const int *p;
从右向左,p先于*结合,再与const结合,因此p的值可以修改及可以指向另一个地址,但不能修改*p的值
int *const p;
同上,p先于const结合,其指向的地址不可被修改,但地址所存储的值可以被更改
const int * cosnt p;
自身值与指向都不可该
无类型指针
-
void 不可以定义变量,但可以定义指针变量
-
无类型指针可以存放任意类型的地址
int main() { const int n = 5; int ar[n] = { 1,2,3,4,5 }; void* p = ar;//p可以存放ar的地址 //int* ip = p;//但p不可以直接被读取,需进行强转 int* ip = (int*)p; }
-
无类型指针大小依旧为4字节,但是无法对无类型指针进行+1,因为内存无法解析无类型的对应的大小(int型加1,地址加4)
十一、二维数组
二维数组在内存中是行优先连续存放的。
-
int a = 10, b = 20; int* p = nullptr;//*与变量名结合 int** s = nullptr; s = &p;//s存放指针p的地址 *s = &a;//对s解引用,*s即为p本身存放变量a的地址,相当于p=&a;*s=>*&p=>p=&a **s = 100;//对*s解引用,**s及为*p,相当于*p=100;**s=>*p=>a=100 return 0;
一级指针存放变量的地址,二级指针存放一级指针的地址
s int**(type) *s int* **s int -
double a_3 = 0, a_2 = 0, a_1 = 0, a_0 = 0; double* p_3 = &a_3, * p_2 = &a_2, * p_1 = &a_1, * p_0 = &a_0; double** s = &p_0;
s+1增加4字节,因为指针的大小为4字节,而*s+1增加8字节,因为double类型变量占8字节
-
int *p[4];//开辟4个存储单元的数组,数组中每个元素为整型指针 int a=4,b=5,c=6,d=7; p[4]={&a,&b,&c,&d}; int (*s)[4];//s是指针存放数组的地址,该数组开辟四个空间,每个元素为整型,及该数组只能存放int [4]类型的数组地址(同类型数组) int ar[3];
-
int ar[4]; ar;//代表首元素的地址 ar+1;//指向数组的下一元素 &ar;//代表数组的地址 &ar+1;//指向下一数组的地址 //ar与&ar的值是一样的
-
二维数组与二级指针的关系
int ar0[4],ar1[4],ar2[4],ar3[4]; int (*s)[4]={&ar0,&ar1,&ar2,&ar3}; //通过s改变ar3[3]的值 *(*(s+3)+3)=10;//*(*(s+3)+3)=10=>*(s[3]+3)=s[3][3]
二维数组是由多个一维数组构成的
int arr[3][4]是由三个一维数组组成,每个一维数组的大小为4
-
定义二维数组时,高位可缺省(arr[][4])
-
int ar[4]; sizeof(ar);//16 int* p = ar; int(*s)[4] = &ar;//相同类型才可存放其数组地址 int br[3][4]; sizeof(br);//48 int(*k)[4] = br;//二维数组的首元素是一维数组的地址 int(*l)[3][4] = &br;
-
二维数组传参
//void Print_arr_1(int **br, int row, int col)//error二维数组的低位不能缺省 void Print_arr_2(int(*br)[3], int row, int col) { int size = sizeof(*(br[3])); printf("%d", size);//size=4 } int main() { int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} }; Print_arr_2(arr, 3, 3); return 0; }
十二、结构体
-
用户设计的数据类型
struct 结构体名 { 成员列表(可以是基本数据类型、指针、数组、其他结构类型) }
-
struct student { char id[20]; char name[20];//结构体内的变量是不可赋值的,它并没有被分配空间 int age; char sex[4]; };//;是不可缺少的
结构体是一种数据类型,所设计的结构体是创建变量的模板,不占用内存空间,结构体中声明的变量为属性。
-
int a;//内置类型可以创建变量 struct student s1;//结构体类型也可以创建变量
-
结构体的初始化
struct student { char id[20]; char name[20]; int age; char sex[4]; }; int main() { struct student s1={10001,"qiao",20,"man"};//未初始化的部分会自动补零,与数组相同;初始化的顺序需按照结构体设计的顺序进行初始化 }
结构体与数组声明时都需要用{},但数组中存放的数据类型是相同的,而结构体不一定
-
struct studentA { const char*s_id; const char *s_name; const char *s_sex; int age; }; struct studentB { char id[20]; char name[20]; int age; char sex[4]; }; int main { struct studentA s1={10001,"qiao",20,"man"};//sizeof(s1)=16 struct studentB s2={10001,"qiao",20,"man"};//sizeof(s2)=48 }
-
结构体的嵌套
struct date { int year; int month; int day; }; struct student { char id[20]; char name[20]; struct date birthdat; }; int main() { struct student s1={"10001","qiao",2001,12,4}; struct student s2={"10001","qiao",{2001,12,4}}; }
-
struct studentB { char id[20]; char name[20]; int age; char sex[4]; struct studentB s; }; struct studentC { char id[20]; char name[20]; int age; char sex[4]; struct studentB *s; }; int main() { struct studentB s1; //s1是不完整类型,sizeof无法计算其大小 struct studentC s2;//可编译通过,可以定义结构体变量 }
-
结构体需用‘.’运算符访问(对象访问),指针用->进行访问
struct student { char name[20]; int age; char sex[5]; }; int main() { struct student s_1={"xiaotutou",19,"man"};//s_1结构体变量 struct student s_2=s_1;//结构体变量可以相互赋值 struct student* p=&s_1;//结构体变量 *p=s_1; printf("%s %d %s\n",s_1.name,s_1.age,s_1.sex); printf("%s", (*p).name); printf("%s", p->name);//用指针访问时用->(成员指向符) return 0; }
```c
struct student
{
char name[20];
int age;
char sex[5];
};
void print_A(struct studnet s)//s 29字节
{
printf("%s",s.name);
printf("%d",s.age);
}
void print_B(struct student *s) //4字节
{
printf("%s",s->name);
printf("%d",s->age);
```
- 结构体数组的输出
void Print_stu(struct student stu[], int n)//stu为指针占四字节
{
assert(stu != NULL);
for (int i = 0; i < n; i++)
{
//printf("%-8s % -8s %-8s %-8d", stu[i].s_no, stu[i].s_name ,stu[i].s_sex, stu[i].age);//-为左对齐
//printf("%-8s % -8s %-8s %-8d", (*(stu+i)).s_no, (*(stu + i)).s_name, (*(stu + i)).s_sex, (*(stu + i)).age);
printf("%-8s % -8s %-8s %-8d",stu->s_no,stu->s_name,stu->s_sex,stu->age);
stu++;
printf("\n");
}
}
十一、引用(起别名)
int main()
{
int a=10;
int b=a;
int &c=a;//a是c,c也是a,它们的地址也是相同的
//引用必须初始化,没有空引用
//int &&b=c;//是不可行的,没有引用的引用
int &x=c;
}
void swap(int &a,int &b)
{
int tem=a;
a=b;
b=tem;
}
int main()
{
int x=10;
int y=20;
}
a为x的别名,b为y的别名,因此对a,b的交换也就是对x,y的交换。
十二、结构体的大小与联合体
-
结构体的大小
- 结构体变量的首地址,必须是结构体变量中“最大基本数据类型所占字节”的整数倍
- 结构体变量中的每个成员相对于结构体首地址的偏移量,都是该成员基本数据类型所占字节的整数倍
- 结构体变量的总大小,为结构体变量中最大基本数据类型所占字节整数倍
struct sdata { int year; int month; int day; }; struct node { char s_id[10]; char s-name[8]; struct sdata birthady; double grade; };
从0地址开始计算,该结构体大小为40;s_id[10]虽为数组但按其基本数据类型大小计算char的大小为1,data结构体依旧看基本数据类型int为4,此结构体大小为(10+8+2)+(4+4+4)+(8)
-
联合体(共用体)
联合体中,所有成员共享一段空间,其大小为各成员中最长的长度变量。
-
哑元结构
无名的结构体
struct Node { char ch; union { int a; float f; }; };//该结构体大小为8 struct Node { char ch; union Af { int a; float f; }; };//结构体大小为1,此时的联合体为类型,并非属性
十三、字符串
-
字符串一定是以‘\0’为结束标志
-
int main() { int size = sizeof("copxyw"); printf("%d", size);//7 const char* sp = "copxyw"; char ch_1 = sp[1];//*(sp+1) ch_1==o char ch_2 = "copxyw"[1];//ch_2==o return 0; }
十四、递归函数
-
分治策略:将一个规模较大的问题,划分为规模较小的相同问题
-
递归:函数自己调用自己
-
递归函数分为递推与回归过程,函数在执行时不断进行递推,直至执行递推的中止条件,再进行回归。再递推过程中栈不断的分配栈帧
-
当进行回归时需要用return将值存放在临时空间
十五、位运算
-
int main() {//整型可以进行位运算 //浮点型和任意指针类型不能进行位运算 char a = 12;//0000 1100 char b = 27;//0001 1011 char c = 0; c = a & b;//(位与) //c = a && b;(逻辑与)结果为1,true c = a | b; //(位或) //c = a || b;简洁或/逻辑或 c = a ^ b;//异或 c = !a;//逻辑反c为false,a的值不变,没有赋值操作 c = ~a;//位反 a=a>>1;//右移一位 b=b<<1;//左移一位 }
-
当要设置一个整型数据某位的bit位为1时,将改位为1的数和其相或,设为0和其相与
char a=0;//要将a的第七位bit位设为1; //a=0=0000 0000(1字节=8bit) a=a|0x80;//1000 0000 a=a|0x10;//1001 0000 //将第4位设为0 a=a&0x80;//1000 0000
十六、库函数
printf:
int len=printf("a=%d to b=%d\n",a,b);
返回的是格式化后字符串的长度
scanf:
int s=scanf_s("%d %d",&a,&b)
返回的值为正确接收数据的个数
十七、字节对齐问题
1、什么是字节对齐
字节,cup在读写内存时并非逐字节读取,而是以2,4,8的倍数的字节来读写内存;从偶地址读取一个读周期就可读取32 bit,而奇地址需要读两个周期再将数据进行拼接才可得到该32 bit数据,字节对齐的目的是为了提高效率。不同的平台对齐方式也不同,信息传述过程可能出现错乱。
2、指定对齐值
通过预处理指令#pragma pack(n)可改变默认对齐数。n可取1,2,4,8,16
#pragma pack (1) //size=17
#pragma pack (2) //size=18
#pragma pack (4) //size=20
#pragma pack (16) //size=24
//当指定的对齐值比结构体中最大的基本类型大时,以结构体最大的基本类型为对齐值
struct date
{
char year;
int month;
int day;
double time;
};
int main()
{
date d;
int size = sizeof(d);
printf("%d\n", size);
return 0;
}
十八、动态内存分配malloc
-
使用malloc函数手动向堆区申请一块指定大小连续的内存空间;申请完成后需用free()进行释放
void *malloc (size_t size);//按字节分配大小
int main() { int n = 0; int i = 0; int* ip = NULL; scanf_s("%d", &n); ip = (int*)malloc(sizeof(int) * n); if (NULL == ip) exit(1);//需要对ip进行判空 for (int i = 0; i < n; i++) { ip[i] = i; } for (int i = 0; i < n; i++) { printf("%2d", ip[i]); } printf("\n"); free(ip);//此时ip为空悬指针 ip = NULL;//free后ip依旧指向开辟空间的首地址,因此需将ip置为空 return 0; }
-
动态分配虽与数组相似,但其也有自身的优点,数组的大小需是常量,并且当其大小超过栈区的内存大小时是不能被运行的。而使用malloc开辟连续的空间其开辟的大小是可以进行自定义,及可使用scanf进行输入。堆区的大小远大于栈区。
-
动态内存管理的四个函数:malloc、calloc、realloc(用来申请空间)、free(用于释放)
-
当malloc 申请成功是返回指向新分配内存的指针,为避免内存泄漏,必须使用free()或realloc()再分配返回的指针;申请失败则返回空指针
-
使用malloc申请的空间有上下越界,用户只可使用上下越界内的空间,上下越界为系统默认值,是不可以被修改的
int main() { int* ip; ip = (int*)malloc(sizeof(int) * 5); if (ip == NULL) exit(1); for (int i = 0; i < 5; i++) { ip[i] = i; } return 0; }
-
int main() { int* ip = (int*)malloc(sizeof(int)); if (NULL == ip) exit(1);//一定要进行判空 int* is = (int*)malloc(sizeof(int)); if (NULL == is) exit(1); *ip = 200; printf("%d\n", *ip); free(ip); //ip=NULL; *is = 1000; printf("%d\n", *ip); *ip = 100;//ip依旧指向malloc所申请空间的首地址 printf("%d\n", *ip); free(ip);//同一块空间不可二次free已free的空间 return 0; }//当free(ip)后未将ip赋值为NULL,将会导致ip依旧指向malloc所开辟空间的首地址,则is开辟空间时可能与ip指向同一块空间,从而导致,ip与is可同时操控此空间,出现程序隐藏的危险
-
malloc开辟空间,有头部信息(大小为28字节),其中有用于记录开辟空间大小的标记,因此当使用free时,free可知道应释放多大的空间,实际开辟的空间比真实申请的空间大
-
内存泄漏:用malloc申请时,丢失了malloc空间的地址,及再未释放的情况下,让指针指向其他地址;只malloc不释放导致堆区空间被用完
int main() { int* ip = (int*)malloc(sizeof(int)*5); if (NULL == ip) exit(1); int* ip = (int*)malloc(sizeof(int) * 10); } //最终只能释放第二次开辟的40字节,而无法释放第一次所开辟的空间,因为没有记录其地址,导致无法释放,地址丢失
-
int *is=(int *)calloc(n,sizeof(int));
将开辟空间中的值赋值为0
void *my_calloc(int num, int size) { void* vp = (void*)malloc(num * size); if (vp != NULL) { memset(vp, num, size);//memset(void *vp,int value,size num) } return vp; }
-
堆区与栈区的区别:
区别 栈 堆 管理方式 由系统自动管理 由程序员控制,使用方便,但容易内存泄漏 生长方向 栈向低地址扩张(由下向上) 堆向高地址扩张,是不连续的内存区域 空间大小 1M或10M 受限于计算机中有效的虚拟内存 存储内容 在一次函数调用后,局部变量先出栈,指令地址出栈,最后栈平衡,由该点继续向下执行 堆用于存储生存期与函数无关的数据,由程序员进行具体管理 分配方式 栈可静态或动态分配 只能动态开辟,手动释放 分配效率 高;由专门的寄存器,压栈出栈有专门的指令 低;由库函数提供,机制复杂 碎片化问题 栈不存在碎片化问题 堆区空间被平凡的释放与分配造成堆内存空间不连续,从而导致大量碎片化 分配后系统响应 只要申请内存小于栈剩余内存,系统将为程序提供内存,否则将会报错
二十、文件
-
stdin //标准输入流 stdout //标准输出流 stderr //标准出错流(将错误显示在屏幕上,无缓冲区)
注:
-
#include<assert.h> assert(sp!=NULL&&se!=NULL); //以下为错误写法 assert(sp!=NULL)&&assert(se!=NULL); if(assert(sp!=NULL));
e+1
listnode* p = pl->head;
int i = 1;
while (pos > i)
{
p = p->next;
i++;
}
return p;
}
```c
bool Insert_Node(linklist* pl, int pos, Elemtype val)//插入节点
{
assert(pl != NULL);
listnode* p_per = Findpos_per(pl, pos);
if (p_per == NULL) return NULL;
listnode* p_insert = buynode();
p_insert->data = val;
p_insert->next = p_per->next;
p_per->next = p_insert;
pl->cursize += 1;
return true;
}
[外链图片转存中…(img-H63veAt8-1636506856289)]
bool Erase(linklist* pl, listnode* p_per)
{
if (p_per == NULL || p_per->next == NULL) return NULL;
listnode* p_record = p_per->next;
p_per->next = p_per->next->next;
free(p_record);
p_record = NULL;
pl->cursize -= 1;
return true;
}
void Erase_Node(linklist* pl, int pos)//删除节点
{
assert(pl != NULL);
listnode* p_per = Findpos_per(pl, pos);
Erase(pl, p_per);
}
[外链图片转存中…(img-cEiBpIl4-1636506856291)]
void Invert_list(linklist* pl)//链表的逆置(头插法)
{
assert(pl != NULL);
if (pl->cursize < 2) return;
listnode* p = pl->head->next;
listnode* s = NULL;
pl->head->next = NULL;
while (p != NULL)
{
s = p;
p = p->next;
s->next = pl->head->next;
pl->head->next = s;
}
}
二十、文件
-
stdin //标准输入流 stdout //标准输出流 stderr //标准出错流(将错误显示在屏幕上,无缓冲区)
/*
stdin 标准输入流
stdout 标准输出流
stderr 标准出错流(将错误显示在屏幕上,无缓冲区)
scanf("%d",&a) stdin
sscanf(buff ,"%d",&a)
fscanf(fp,"%d",&a)
序列化与反序列化???
*/
int main()
{
//char ch;
char ch[10];
FILE* fp = NULL;
errno_t res = fopen_s(&fp, "2021-7-16.cpp", "r");
if (fp == NULL)
{
printf("fopen file error %d\n", res);
return 1;
}
while (!feof(fp))//未到末尾
{
//ch = fgetc(fp);
fgets(ch, 10, fp);
printf("%s", ch);
Sleep(200);
}
fclose(fp);
fp = NULL;
return 0;
}
int main()
{
char buffa[128];
char buffb[128];
scanf_s("%s", buffa,128);//空格作为输入结束符
fgets(buffb, 128, stdin);//回车作为输入结束符
return 0;
}
int main()
{
char ch = '\0';
ch = getchar();//stdin
ch = fgetc(stdin);
}
int main()
{
int ar[10];
FILE* fp = NULL;
errno_t res = fopen_s(&fp, "qch.txt", "r");
if (fp == NULL)
{
printf("fopen file error %d\n", res);
return 1;
}
for (int i = 0; i < 10; i++)
{
fread(ar, sizeof(int), 10, fp);
}//此时数据在缓冲区中
fclose(fp);//数据写入文件
fp = NULL;
return 0;
}
int main()
{
//二进制写
int ar[] = { 10,20,30,40,50,60,70,80,90,100 };
int size = sizeof(ar) / sizeof(ar[0]);
FILE* fp = NULL;
errno_t res = fopen_s(&fp, "qch.txt", "wb");
if (fp == NULL)
{
printf("fopen file error %d\n", res);
return 1;
}
fwrite(ar, sizeof(int), size, fp);
//此时数据在缓冲区中
fclose(fp);//数据写入文件
fp = NULL;
return 0;
}
int main()//读
{
int ar[10] ;
FILE* fp = NULL;
errno_t res = fopen_s(&fp, "qch.txt", "r");
if (fp == NULL)
{
printf("fopen file error %d\n", res);
return 1;
}
for (int i = 0; i < 10; i++)
{
//fprintf(fp, "%d ", ar[i]);
//fscanf(stdin,"%d", &ar[i]);
fscanf_s(fp, "%d", &ar[i]);
}//此时数据在缓冲区中
fclose(fp);//数据写入文件
fp = NULL;
return 0;
}
int main()//写
{
int ar[] = { 10,20,30,40,50,60,70,80,90,100 };
int size = sizeof(ar) / sizeof(ar[0]);
FILE* fp = NULL;
errno_t res= fopen_s(&fp,"qch.txt", "w");
if (fp == NULL)
{
printf("fopen file error %d\n", res);
return 1;
}
for (int i = 0; i < size; i++)
{
//printf("%d", ar[i]);
fprintf(fp, "%d ", ar[i]);
}//此时数据在缓冲区中
fclose(fp);//数据写入文件
fp = NULL;
return 0;
}
int main()
{
int a = 10, b = 20;
char buff[30];
int len = sprintf_s(buff, 30, "a=%d b=%d\n", a, b);//格式化后的字符串存放到buff里
len = printf("a = % d b = % d\n", a, b);//格式化后的字符串输出到屏幕上
len = fprintf(stdout, "a= % d b = % d\n", a, b);//显示到标准输出设备上与printf功能相同
/*float fa = 20.5, fb = 36.3;
len = sprintf_s(buff, 30, "fa=%f fb=%f\n", fa, fb);*/
return 0;
}
int main()
{
//文件操作(1.打开文件并判空 2.读写文件 3.关闭文件)
int printf(const char* str, ...);//...为可变参数
int sprintf(char* buff, const char* ptr);
int fprintf(FILE * fp, const char* ptr);
}