目录
第二节:循环while,for,continue,break
第一章:数据的类型、数据的输入输出
第一节:数据类型-常量-变量
1、数据类型
#include <stdio.h>
/*
stdio.h是C语言标准库中的一个头文件,它提供了标准输入输出功能;
这里的“stdio”是“standard input/output”的缩写;
当你在C程序中包含了stdio.h头文件,你就能够使用它提供的各种输入输出函数;
stdio.h头文件中定义了一些宏、类型以及函数,用于处理文件和数据流。
*/
#define PI 3+2
//符号常量,是直接替换的效果,定义符号常量结尾不用写';'
int main(){
int i = PI*2;//i就是一个整型变量
printf("i=%d\n",i);//printf是用来输出的
printf("i size=%d\n",sizeof(i));//sizeof可以用来计算某个变量的空间大小
return 0;
}
2、常量
常量氛围整型8、实型(也称为浮点型)3.14、字符型' '和字符串型" ";
3、变量
变量的值是可以以在执行过程中可以改变的,编译时,由编译系统为每个变量名分配对应的内存地址(也就是空间)
命名规则:C语言规定标识符只能由字母、数字、下划线三种字符组成,并且第一个字符必须为字母或者为下划线;
4、整型数据
4.1、符号常量
符号常量是直接替换过去
4.2、整型变量
int 类型大小为4个字节
5、浮点型数据
5.1、浮点型常量
小数形式:0.123;
指数形式:3e-3,即为0.003,[注意]:字母e(或E)之前必须有数字,且e后面的指数必须为整数;
5.2、浮点型变量
通过float f来定义浮点变量,float类型占用4个字节的空间;
6、字符类型数据
6.1、字符型常量
用单引号括起来的一个字符就是字符型常量,且只能包含一个字符,例如:'A';
以"\"开头的特殊字符称为转义字符,”\n“的作用是换行,”\b“的作用是退格,”\\“的作用是反斜杠;
6.2、字符数据在内存中的存储形式及其使用方法
字符型变量使用关键字char进行定义,一个字符型变量占用了1个字节大小的空间;
7、字符串型常量
''是字符型常量,""是字符串常量,字符串常量最后一个内存空间是\0;
8、ASCll码
A是65,a是97,之间相差32
#include <stdio.h>
/*
int main(){
float f = 3e-3;
printf("f=%f",f);
return 0;
}
*/
int main(){
char c='A';
printf("c=%c\n",c+32);//以字符形式输出
printf("c=%d\n",c+32);//以数值形式输出
}
第二节:混合运算-printf
1、混合运算
类型强制转换场景
整型数进行除法运算时,如果运算结果为小数,那么存储浮点数时一定要进行强制类型转换;
#include <stdio.h>
//强制类型转换
int main() {
int i = 5;
float f = i/2;//这里做的是整型运算,因为左右操作数都是整型
float k = (float)i / 2;
printf("%f\n",f);
printf("%k\n",k);
return 0;
}
2、printf函数介绍
实际原理:printf函数将这些类型的数据格式化为字符串后,放入标准输出缓冲区,然后将结果显示到屏幕上;
I/O input/output
'%c'是字符,'%d'是带符号整型,'%f'是浮点数,'%s'是一串字符,'%u'是无符号整数,'%x'是无符号十六进制数,用小写字母;
#include <stdio.h>
int main(){
int age = 21;
printf("Hello,you are %d years old\n",age);
int i = 1001;
float f = 96.3;
printf("student number=%3d,score=%5.2f\n",i,f);//d前面的’3‘代表控制三格输出,f前面’5.2‘,’5‘是控制5格输出,’.2‘是小数点后两位输出;
i = 100;
f = 98.21;
printf("student number=%-3d,score=%5.2f\n",i,f);//默认是右对齐,加一个符号是代表左对齐
}
第三节:整形进制转换
1、整型常量的不同进制表示
计算机中只能存储二进制数,即0和1,对应物理硬件上的高电平和低电平,除了我们正常使用的十进制数外,计算机还提供了十六进制数和八进制数;
在计算机中,1字节为8位,1位即二进制的1位,它存储0或1;
1KB = 1024K
1MB = 1024KB
1GB = 1024MB
十进制:0-9
八进制:0-7
十六进制:0-9 a-f (在观察内存时会需要频繁使用)
进制转换:
十进制转其他进制,转几进制就除以几再保留余数,将余数倒置写出就是所要转出的进制
十进制:123
二进制:0000 0000 0000 0000 0000 0000 0111 1011
十六进制:7b 00 00 00 (因特尔的CPU采用了小端方式进行数据存储,因此低位在前、高位在后)
八进制:001 111 011
173 (1*(8*8)+7*(8)+3)
程序员计算器:
快捷键:Win + R 输入'calc'打开计算器;
#include <stdio.h>
int main() {
int i = 123;
printf("%d\n",i);//十进制
printf("%o\n",i);//八进制
printf("%x\n",i);//十六进制
return 0;
}
第四节:scanf读取标准输入
常用的数据输入/输出函数
C语言通过函数库读取标准输入
1、scanf函数的原理
#include <stdio.h>
//scanf用来读取标准输入,scanf把标准输入内的内容,需要放到某个变量空间里,因此变量必须取地址
//sacnf会阻塞,是因为标准输入缓冲区是空的
int main() {
int i=10;
char c;
float f;
scanf("%d",&i);
printf("i=%d\n",i);//把标准缓存区中的整型数读走了
fflush(stdin);//清空标准输入缓冲区
scanf("%c",&c);
printf("c=%c\n",c);//输出字符变量c
scanf("%f",&f); //scanf函数在读取整型数、浮点数、字符串时会忽略‘\n’(回车符)、空格等字符
// 但字符串‘%c’不会忽略仁和字符,所以会读还在缓冲区中残留的'\n'
printf("f=%f\n",f);
return 0;
}
2、多种数据类型的混合输入
#include <stdio.h>
int main(){
int i,ret;
float f;
char c;
ret = scanf("%d %c%f",&i,&c,&f);//ret是指scanf匹配成功的个数
//%c前面有其他格式时在前面加个空格
printf("i=%d,c=%c,f=%5.2f\n",i,c,f);
return 0;
}
第二章:运算符与表达式
第一节:算术运算符与关系运算符
1、运算符的分类
C语言提供了13种类型的运算符,如下所示:
(1)算术运算符(+ - * / %)
(2)关系运算符(> < == >= <= !=)
(3)逻辑运算符(! && ||)
(4)位运算符(<< >> - | ^ &)
(5)赋值运算符(=及其扩展赋值运算符)
(6)条件运算符(?:)
(7)逗号运算符(,)
(8)指针运算符(*和&)——讲指针时讲解
(9)求字节数运算符(sizeof)
(10)强制类型转换运算符(类型)
(11)分量运算符(- —>)——讲结构体时讲解
(12)下标运算符([])——将数组时讲解
(13)其他(如函数调用运算符[])——将函数时讲解
2、算术运算符与算术表达式
乘、除、取余运算符的优先级高于加、减运算符;
'%'取模运算符,它接收两个整型操作数,将左操作数除以右操作数,但它的返回值是余数而不是商。
3、关系运算符与关系表达式
关系运算符‘>、<、==、>=、<=、!='依次为大于、小于、是否等于、大于等于、小于等于和不等于;
关系表达式的值只有真和假,对应的值为1和0;
关系运算符的优先级低于算术运算符;
4、运算符优先级表达式
#include <stdio.h>
//关系运算符,优先级小于算术运算符
int main(){
int a;
while(scanf("%d",&a)){
if(3<a && a<10){
printf("a is between 3 and 10\n");
}else{
printf("a is not between 3 and 10\n");
}
}
return 0;
}
运算符优先级表
第二节:逻辑运算符与赋值运算符,求字节运算符
1、逻辑运算符与逻辑表达式
逻辑运算符'!'、'&&'、'||'依次为逻辑非、逻辑与、逻辑或;
逻辑非得优先级高于算术运算符,逻辑与和逻辑或的优先级低于关系运算符,逻辑表达式也是只有真和假。
#include <stdio.h>
//逻辑与和逻辑或 短路运算
int main(){
int i=1;
i&& printf("you can‘t see me\n");//当i为假时,不会执行逻辑与后的表达式,称为短路运算
// if(i){
// printf("you can‘t see me\n");
// }
i=0;
i||printf("you can‘t see me\n");
return 0;
}
2、赋值运算符
左值就是能出现在赋值符号左边的东西,右值就是出现在赋值运算符有右边的东西;
左值一般为一个特定的位置(并不对应特定的内存空间),不能为表达式;
#include <stdio.h>
int main(){
int a=1,b=2;
//a+25=b;//error: lvalue required as left operand of assignment
a+=3;//简写可以提高编译速度
b*=5;
printf("a=%d\n",a);
printf("b=%d\n",b);
return 0;
}
3、求字节运算符sizeof
sizeof是一个运算符,不是函数,sizeof是字母组成的,用于求常量或变量所占用的空间大小
#include <stdio.h>
//sizeof运算符
int main(){
int i=0;
printf("i size is %d\n",sizeof(i));
return 0;
}
第三章:选择、循环
第一节:if-else
1、关系表达式与逻辑表达式
算术运算符(+、-、*、/、%)的优先级高于关系运算符、关系运算符(>、<、=、<=、>=)的优先级高于逻辑与(&&)和逻辑或(||)运算符、
相同优先级的运算符从左至右进行结合;
双目运算符:左操作数+右操作数;
单目运算符:!操作数 (逻辑非就是单目运算符)。
2、if-else 语句
if判断条件(表达式)为真就执行某个语句,反之用else分支执行另一个语句;
if和else语句也可以多个同时使用(多分支语句),同时,if语句也支持多层嵌套,在if语句中又包含一个或多个if语句称为if语句的嵌套;
C语言中的else子句从属于最靠近它的不完整的if语句;
在if语句中的语句列表前后加上花括号,可以防止不小心加了一句代码后,使实际未被包含的语句被包含在某个if语句中的错误。
#include <stdio.h>
int main() {
int i;
while(scanf("%d",&i)){
if(i>0)//if下面加个大括号
{
printf("i is bigger than 0\n");
}else{
printf("i is not bigger than 0\n");
}
}
return 0;
}
第二节:循环while,for,continue,break
1、while循环
while语句用来实现“当型”循环结构,其一般形式为“while(表达式) 语句;”,当表达式的值非0时,执行while语句中的内嵌语句。
其特点是:先判断表达式,后执行语句;
#include <stdio.h>
//计算从1到100的和
int main() {
int i=1,total=0;
while(i<=100)//不能在while()后加分号';',加分号会造成死循环
{
total=total+i;//把i加到total上
i++;//i=i+1; 在循环体内没有让while判断表达式趋近于假的操作,造成死循环
}
printf("total=%d\n",total);
return 0;
}
2、for循环
C语言中的for循环语句使用最为灵活,不仅可以用于循环次数已经确定的情况,而且可以用于循环次数不确定而只给出循环结束条件的情况;
for循环的一般形式: for(表达式1;表达式2;表达式3) 语句;
具体执行流程:
(1)、先求解表达式1;
(2)、求解表达式2,若其值为真(值为非0),则先执行for语句中指定的内嵌语句,
后执行第(3)步,反之其值为假(值为0),则结束循环转到第(5)步;
(3)、求解表达式3;
(4)、转回第(2)步继续执行;
(5)、循环结束,执行for语句下面的语句。
for循环语句小括号必须且只能有两个分号,用于分隔三个表达式,表达式3可以省略,表达式1可以使用逗号初始化多个变量
表达式3的作用是是表达式2趋近于假跳出循环;
3、continue语句
continue语句的作用为结束本次循环,即跳过循环体中下面尚未执行的语句,接着进行是否执行下一次循环的判断;
一般形式:continue;
当continue用于while和do while循环中时,注意不要跳过让循环趋近于假的语句;
4、break语句
break的作用是结束整个循环过程,不再判断执行循环的条件是否成立;
break语句也可以在while循环和do while循环中,起结束对应循环的作用;
5、循环嵌套
循环嵌套,对于考研最常见的,是for循环,嵌套for循环,即两层for循环,三层循环的情况考研没有出现;
#include <stdio.h>
int main(){
int i,j;
for(i=0;i<5;i++)
{
printf("i=%d\n",i);//打印i,就知道外层循环是第几次进行外层循环
for(j=0;j<5;j++)
{
printf("%d ",i*j);
}
printf("\n");//输出一个换行
}
return 0;
}
#include <stdio.h>
int main(){
int i,j;
for(i=1;i<=5;i++)
{
for(j=1;j<=i;j++){
printf("*");
}
printf("\n");
}
return 0;
}
第四章:一维数组与字符数组
第一节:一维数组
1、数组的定义
(1)、具有相同的数据类型,使用过程中需要保留原始数据;
(2)、一维数组的定义格式为:
类型说明符 数组名[常量表达式];
例如: int a[10];
(3)、声明数组时要遵循以下规则:
1)、数组名的命名规则和变量名的相同,既遵循标识符命名规则;
2)、在定义数组时,需要指定数组中元素的个数,方括号中的常量表达式用来表示元素的个数,即数组长度;
3)、常量表达式中可以包含常数和符号常量,但不能包含变量。也就是说,C语言不允许对数组的大小做动态定义,
即数组的大小不依赖于程序运行过程中变量的值;
(4)、数组声明的其他常见错误如下:
1)、数组大小不能为0没有意义,例:float a[0];
2)、不能使用圆括号'()';
3)、不能用变量说明数组大小,例:int k=3,a[k];
2、一维数组在内存中的存储
数组的初始化方法:
1)、在定义数组时对数组元素赋初值,例如:int a[10]={0,1,2,3,4,5,6,7,8,9};
2)、可以只给一部分元素赋值,例如:int a[10]={0,1,2,4};
3)、如果要是一个数组中全部元素的值为0,那么可以写成:int a[10]={0,0,0,0,0,0,0,0,0,0,0}; 或 int a[10]={0} (建议写第二个);
4)、在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组的长度,例如:int a[]={1,2,3,4,5};
#include <stdio.h>
int main() {
int a[]={1,2,3,4,5};//建议在方括号中不要写变量
printf("%d\n",a[4]);
return 0;
}
第二节:数组访问越界与数组的传递
1、数组的访问越界
操作系统对内存中的每个位置也给予一个编号,对于Windows32位控制台应用程序来说,这个编号的范围是从0x00 00 00 00 到0xFF FF FF FF,
总计2的32次方,大小为4G,为32位;
数组另一个值得关注的地方是,编译器并不检查程序对数组下标的引用是否在数组的合法范围内;
#include <stdio.h>
int main() {
int a[5]={1,2,3,4,5};
int j=20;
int i=10;
a[5]=6;//访问越界
a[6]=7;
printf("i=%d\n",i);//i我们并没有赋值,但是值却变化了
printf("j=%d\n",j);
return 0;
}
2、数组的传递
调试按钮:
步过(折弯) F8 当前函数一步一步往下走
步入(向下箭头) F7 到达某个函数,要进入自己的子函数时,
一维数组的传递,数组长度无法传递给子传递;
数组名 传递给子函数后,子函数的形参接收到的是数组的起始地址,因此不能把数组的长度传递给子函数;
#include <stdio.h>
//子函数把某一个常用功能,封装起来的作用
//数组名传递到子函数后,子函数的形参接收到是数组的起始地址,因此不能把数组的长度传递给子函数
void print(int a[],int length) //形参
{
int i;
//for(i=0;i<sizeof(a)/sizeof(int);i++)
for(i=0;i<length;i++)
{
printf("%3d",a[i]);
}
a[3]=20;
printf("\n");
}
第三节:字符数组与scanf读取字符串
1、字符数组初始化及传递
定义字符数组,例如:char c[10];
字符数组的初始化可以采取以下方式:
(1)、对每个字符单独赋值进行初始化,例如:c[0]='I';c[1]='a';c[2]='m';
(2)、对整个数组进行初始化,例如:char c[10]={'I','a','m','h','a','p','p','y'};
#include <stdio.h>
//模拟pinrtf("%s",c)操作
void print(char d[])
{
int i=0;
while(d[i])//当走到结束符'\000',循环结束
{
printf("%c",d[i]);
i++;
}
printf("\n");
d[0]='H';
}
//如何初始化字符数组,字符串如何输出
//输出字符串乱码时,要去查看字符数组中是否存储了结束符'\0'
int main() {
//char c[10]={'I','a','m','h','a','p','p','y'};
char c[6]="hello";//使用这种方式初始化字符数组
char d[5]="how";
printf("%s\n",c);//使用%s来输出一个字符串,直接把字符数组名放到printf后面位置
print(d);
printf("%s\n",d);
return 0;
}
2、scanf读取字符串
scanf在使用%s读取字符串时,会忽略空格和回车(这一点与%d和%f类似);
#include <stdio.h>
//scanf读取字符串操作,会自动往字符数组中放结束符'\000'
int main(){
char c[10];
char d[10];
// scanf("%s",c);//之前中用'&'取地址符号是因为非字符数组没有起始地址,字符数组名中存储了数组的起始地址,因此不需要取地址
// printf("%s\n",c);
scanf("%s%s",c,d);//%s读取会忽略空格'\0';
printf("c=%s,d=%s",c,d);
return 0;
}
第四节:gets与puts,strlen-strcmp
1、gets函数与puts函数
gets函数类似于scanf函数,用于读取标准输入。
gets函数的格式如下:char *gets(char *str);
puts只能输出数组字符串;
#include <stdio.h>
int main(){
char c[20];
gets(c);//gets中放入我们字符数组的数组名即可
puts(c);//puts等价于printf("%s\n",c); puts内放的参数是字符数组名
return 0;
}
2、str系列字符串操作函数(初试没有那么重要,对计时更为重要一些)
str系列字符串操作函数主要包括strlen、strcpy、strcmp、strcat等。
strlen函数用于统计字符串长度;
strcpy函数用于将某个字符串复制到字符数组中;
strcmp函数用于比较两个字符串的大小;
strcat函数用于将两个字符串连接到一起;
具体格式:
size_t strlen(char *str);
char *strcpy(char *to,const char *from);
int strcmp(const char *str1,const char *str2);
char *strcat(char *str1,const char *str2);
对于传参类型char*,直接放入字符数组的数组名即可;
字符串比较大小
hello how
#include <stdio.h> //std是标准 io是input输入output输出流
#include <string.h>
int mystrlen(char c[])
{
int i=0;
while(c[i])//找到结束符后,
{
i++;
}
return i;
}
int main()
{
int len;
char c[20];
char d[100]="world";
char e[100];
gets(c);
puts(c);
len = strlen(c);//统计字符串的长度
printf("len=%d\n",len);
len = mystrlen(c);
printf("my len=%d\n",len);
strcat(c,d);//把d中的字符串拼接到c中
puts(c);
strcpy(e ,c);
puts(e);
//c大于”how“,返回值是正值,相等是0,c小于”how“
printf("c?d=%d\n",strcmp(c,"how"));
return 0;
}
第五章:指针
第一节:指数的本质(间接访问原理)
1、指针(间接访问)
通俗的讲,指针就是地址,我们通常说的指针其实是指针变量,也就是保持地址的变量;
指针分不同类型是因为取值拿到的空间大小不同;
指针变量本身的大小是固定的,sizeof(i_point)和sizeof(c_point)对于64位系统是都是8个字节,sizeof(i_point)和sizeof(c_point)对于32位系统都是8个字节
1)、指针变量的定义方式
"int* a"和"int *a"是等价的,"int* a,b,c"是错误的("*"实际上是"*标识符"的一部分,只对标识符起作用)
正确的定义语句: int *a,*b,*c;
2)、这种写法是毫无意义而且会出错的(类型不同):
float a;
int *p;
p=&a;
3)、"&"和"*"两个运算符的优先级别相同,但要按自右向左的方向结合,因此"*&a"与a等价,我们不会将取值和取地址放在一起这么去使用;
2、间接访问
i_pointer指针变量中存的内容是i的地址(起始地址),可以讲只恨变量理解为藏宝图,通过简介访问可以帮助我们找到宝藏(变量);
#include <stdio.h>
int main() {
int i=5;//定义整型变量i,四个字节
char c='a';//定义字符型变量c,一个字节
int *i_pointer=&i;//定义整形指针变量i_pointer,并初始化
char *c_pointer;//定义字符型变量c_pointer
c_pointer=&c;//变量c的地址赋值给c_pointer
*i_pointer=10;//通过间接访问修改变量i的值
printf("i=%d\n",i);//输出i的值
return 0;
}
第二节:指针的传递使用场景
1、指针的使用场景——传递
函数调用是值传递;
实际效果是j=&i;,依然是值传递,只是这时j是一个指针变量,存储的是变量i的地址,所以通过*j就间接访问到了与变量i相同的区域,
通过*j=5就实现了对变量i的值的改变;
#include <stdio.h>
//void change(int j)//j是形参
void change(int *j)//等价于j=&i
{
//j=5;
*j=5;//通过间接访问拿到了变量i的空间
}
int main() {
int i=10;
printf("before change i=%d\n",i);
//change(i);//调用change函数时,实参i赋值给形参j
change(&i);//实参是&i
printf("after change i=%d\n",i);
return 0;
}
第三节:指针的偏移使用场景
1、指针的偏移
指针的另一个场景就是对其进行加减(但无乘除,也没有意义);
我们将指针的加减称为指针的偏移,加就是向后偏移,减就是向前偏移;
指针变量加1后,偏移的长度是其基类型的长度,也就是偏移sizeof(int),这样通过*(p+1)就可以得到元素a[1];
编译器在编译时,数组取下标的操作正是转换为指针偏移完成的;
#include <stdio.h>
//指针的偏移使用场景,就是对指针进行加和减运算
#define N 5 //符号常量定义结尾无';'
int main() {
int a[N]={1,2,3,4,5};//数组名内存储了数组的起始地址,a中存储的就是一个地址值
int *p;//定义指针变量p
p=a;
int i;
for(i=0;i<N;i++)
{
//printf("%3d",a[i]);
printf("%3d",*(p+i));//这里和a[i]是等价的
}
printf("\n------------------\n");
p=&a[4];//指针变量p指向了数组的最后一个元素
printf("%3d\n",*p);
for(i=0;i<N;i++)
{
printf("%3d",*(p-i));//逆序输出
}
return 0;
}
2、指针与一维数组
数组名作为实参传递给子函数时,是弱化为指针的
#include <stdio.h>
//指针与一维数组的传递
void change(char *d)
{
*d = 'H';
d[1] = 'E';
*(d + 2) = 'L';
d[3] = 'L';
*(d + 4) = 'O';
}
int main(){
char c[10] = "hello";
puts(c);
change(c);
puts(c);
return 0;
}
第四节:指针与malloc动态内存申请,栈与堆的差异
1、指针与动态内存申请
C语言的数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中,而栈空间的大小在编译时是确定的,
如果想要使用的空间大小不确定,那么就要使用堆空间;
malloc定义格式:(void*)malloc(size);
同时需要注意指针本身大小,和其指向的空间大小,是两码事,不能和前面的变量类比去理解;
#include <stdio.h>
#include <stdlib.h> //malloc使用的头文件
#include <string.h>
/*
标准库(stdlib)是C语言中提供的一个库,它包含了一些基本的、通用的函数,这些函数在很多程序中都会用到。
stdlib.h头文件中声明了这些函数的原型,以及一些类型定义和宏。
当你在程序中包含了stdlib.h头文件后,就可以使用这些函数了。
<stdlib.h>标准库中的函数覆盖了以下几个方面:
内存分配:如malloc、calloc、realloc和free。
程序控制:如exit、abort和atexit。
转换函数:如atoi、atol、atof等,用于将字符串转换为数值。
随机数生成:如rand和srand。
数学计算:虽然C语言的标准库中不包含复杂的数学函数,但提供了一些基本的数学操作,如abs(求绝对值)。
字符串处理:虽然C语言中有专门的string.h头文件,但stdlib.h中也包含了一些字符串处理函数,如strtok。
搜索和排序:如bsearch和qsort。
*/
int main() {
int size;//size代表我们要申请多大字节的空间
char *p;//void*类型的指针不能偏移,因此不会定义无类型指针
scanf("%d",&size);//输入要申请的空间大小
//malloc返回的 “void*" 代表无类型指针
p=(char*)malloc(size);
strcpy(p,"malloc success");
puts(p);
free(p);//释放申请的空间,给的地址,必须是最初malloc返回给我们的地址
printf("free success\n");
return 0;//正确退出码是0 ,错误是其他
}
2、栈空间与堆空间的差异(了解)
堆的效率要比栈低的多;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//堆和栈的差异
char* print_stack()
{
char c[100]="I am printf_stack func";
char *p;
p=c;
puts(p);
return p;
}
char *print_malloc()
{
char *p=(char*)malloc(100);//堆空间在整个进程中一直有效的,不因为函数结束而消亡
strcpy(p,"I am print malloc func");
puts(p);
return p;
}
int main()
{
char *p;
p=print_stack();
puts(p);
p=print_malloc();
puts(p);
free(p);//只有free时,堆空间才会释放
return 0;
}
第六章:函数
第一节:函数的声明与定义-嵌套调用
1、函数的声明与定义
函数间的调用关系是,有主函数调用其他函数,其他函数也可以互相调用;
同一个函数可以被一个或多个函数调用任意次;
Clion中创建源文件,右键项目名称,新建C/C++源文件
ctrl+鼠标左键,点击对应的函数,就可以跳转到对应的函数查看源码
(1)、一个C程序由一个或多个程序模块组成,每个程序模块作为一个源程序文件,对于较大的程序,通常将程序内容分别放在若干源文件中,再由若干源程序文件组成一个C程序;
这样处理便于分别编写、分别编译,进而提高调试效率(复试有用)。
(2)、一个源程序文件有一个或多个函数及其他有关内容(如命令行、数据定义)组成;
一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位而不是函数为单位进行编译的。
(3)、C程序的执行是从main函数开始的,如果在main函数中调用其他函数,那么在调用后会返回到main函数中,在main函数中结束整个程序的运行。
(4)、函数不能嵌套定义,函数间可以互相调用,但不能调用main函数;
main函数是由系统调用的,main主函数中调用函数1,函数1中又调用函数2,这种调用称为嵌套调用
函数的声明与定义的差异:
1)、函数的定义是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型、函数体等,它是一个完整的、独立的函数单位
2)、函数的声明的作用是把函数的名字、函数类型及形参的类型、个数和顺序通知编译系统,一遍在调用该函数时编译系统能正确识别函数并检查调用是否合法
隐式声明:C语言中有几种声明的类型名可以省略;
函数如果不显式地声明返回值的类型,那么它默认返回整型;
#include "func.h" //双引号是自定义的头文件,单引号是官方头文件
int main(){
int a=10;
a= print_star(a);
print_message();//调用print_message()
print_star(5);
return 0;
}
2、函数的分类与调用
(1)、标准函数:即库函数,这是由系统提供的,用户不必自己定义的函数,可以直接使用它们,如printf函数、scanf函数;
(2)、用户自己定义的函数:用以解决用户的专门需要。
1)、无参函数:一般用来执行指定的一组操作,在调用无参函数时,主调函数不向被调用啊哈双女户传递数据。
无参函数的定义形式如下:
类型标识符 函数名()
{
声明部分
语句部分
}
2)、有参函数:主调函数在调用被调用函数时,通过参数向调用函数传递数据;
有参函数的定义形式如下:
类型标识符 函数名(形式参数表列)
{
声明部分
语句部分
}
第二节:函数的递归调用
1、递归调用
我们把函数自身调用自身的操作,称为递归函数,递归函数一定要有结束条件,否则会产生死循环;
1)、递归的核心是找公式;f(n)=n*f(n-1)
2)、编写递归结束条件
#include <stdio.h>
//递归求阶乘,是为了大家理解什么是递归
int f(int n){
//一定要有结束条件
if(1==n){
return 1;
}
return n*f(n-1);//写公式
}
//上台阶,到第n个台阶,有多少种走法
int step(int n)
{
if(1==n||2==n)//当台阶是1个,或2个时,递归结束
{
return n;
}
return step(n-1)+ step(n-2);
}
int main() {
int n;
scanf("%d",&n);
//printf("f(%d)=%d\n",n,f(n));
printf("step(%d)=%d\n",n,step(n));
return 0;
}
第三节:局部变量与全局变量
1、全局变量解析-形参-实参解析
在不同的函数之间传递数据时,可以使用的方法如下:
(1)、参数:通过形式参数和实际参数;
(2)、返回值:用return语句返回计算结果
(3)、全局变量:外部变量
如果局部变量与全局变量重名,那么将采取就近原则,即实际获取和修改的值是局部变量的值;
关于形参与实参的一些说明如下:
1)、定义函数中指定的形参,如果没有函数调用,那么它们并不占用内存中的存储单元,
只有在发生函数调用时,函数print中的形参才被分配内存单元,
在调用结束后,形参所占的内存单位也会被释放;
2)、实参可用是常量、变量或表达式,但要求它们有确定的值;
3)、在被定义的函数中,必须制指定形参的类型,如果实参列表中包含多个实参,那么各参数间用逗号隔开,
实参与形参的个数应相等,类型应匹配,且实参与形参应按顺序对应,一一传递数据;
4)、实参与形参的类型应相同或赋值应兼容;
5)、实参向形参的数据传递是单向“值传递”,只能由实参传给形参,而不能由形参传回给实参,
在调用函数时,给形参分配存储单位,并将实参对应的值传递给形参,调用结束后,形参单位被释放,实参单元仍保留并维持原值;
6)、形参相当于局部变量,因此不能再定义局部变量与形参同名,否则会造成编译不通;
2、局部变量与全局变量
1)、内部变量
在一个函数内部定义的变量称为内部变量,它只在本函数范围内有效,即只有本函数内才能使用这些变量,故也称局部变量;
关于局部变量需要注意如下几点:
(1)、主函数中定义的变量只在主函数中有效,而不因为在主函数中定义而在整个文件或程序中有效,主函数也不能使用其他函数中定义的变量;
(2)、不同函数中可以使用相同名字的变量;
(3)、形式参数也是局部变量;
(4)、若离开花括号,则在其下面使用该变量会造成编译不通;
(5)、for循环的小括号内定义的int i,在离开for循环后,是不可以再次使用的;
2)、外部变量
C语言一般要求把程序中的函数做成一个封闭体,除可以通过“实参-->形参"的渠道与外界发生联系外,没有其他渠道;
#include <stdio.h>
int i=10;//i是一个全局变量(放在数据段),不建议使用
void print(int a)
{
printf("I am print i=%d\n",i);
}
int main() {
{
int j=5;//局部变量只在离自己最近的大括号内有效
}
int i=5;
printf("main i=%d\n",i);
for(int k=0;k<-1;){
}
// printf("k=%d",k); //for循环括号内定义的变量,循环体外不可以
print(5);
return 0;
}
第七章:结构体及C++引用
第一节:结构体-结构体数组-结构体对齐
(1)、需要将不同类型的数据组合为一个整体,以便于引用,为此,C语言提供结构体来管理不同类型的数据组合; (2)、结构体类型声明最后一定要加分号,否则会编译不同,另外,定义结构体变量时,使用struct student来定义, 不能只有strcuct或student,否则也会编译不通; (3)、采用“结构体变量名.成员名”的形式来访问结构体成员,例如用s.num访问学号。在进行打印输出时,必须访问到成员, 而且printf中的%类型要与各成员匹配,使用scanf读取标准输入时,也必须是各成员取地址 (4)、结构体对齐三大法则:
#include <stdio.h>
struct student{
int num;
char name[20];
char sex;
int age;
float score;0
};
//结构体的初始化只能在一开始定义时进行,例如:struct student s={1001,"lele",'M',20,85.4};
//如果s已经定义,那么只能对它的每个成员单独赋值,如:s.num=1003
int main() {
struct student s={1001,"lele",'M',20,85.4};
printf("%d %s %c %d %5.2f\n",s.num,s.name,s.sex,s.age,s.score);
//读取信息到结构体的每个成员里
// scanf("%d%s %c%d%f",&s.num,&s.name,&s.sex,&s.age,&s.score);
// printf("____________________________________________________\n");
// printf("%d %s %c %d %5.2f\n",s.num,s.name,s.sex,s.age,s.score);
int i;
struct student sarr[3];
i=0;
sarr[i]=s;
printf("____________________________________________________\n");
printf("%d %s %c %d %5.2f\n",sarr[i].num,sarr[i].name,sarr[i].sex,sarr[i].age,sarr[i].score);
return 0;
}
#include <stdio.h>
struct student_type1{
double score;//8个字节
int height;//4个字节
short age;//2个字节
};
struct student_type2{
int height;
char sex;
short age;
};
int main()
{
struct student_type1 s1={4,5,6};
struct student_type2 s2={7,'M',22};
printf("s1 size=%u\n",sizeof(s1));
printf("s2 size=%u\n",sizeof(s2));
return 0;
}
第二节:结构体指针与typedef的使用
1、结构体指针
一个结构体变量的指针就是该变量所占据的内存段的起始地址。
可以设置一个指针变量,用它指向一个结构体变量,此时该指针的值是结构体变量的起始地址。
指针变量也可以用来指向结构体数组中的元素,从而能够通过结构体指针快速访问结构体内的每个成员。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student{
int num;
char name[20];
char sex;
};
//结构体指针的练习
int main() {
struct student s={1001,"wangle",'M'};
struct student sarr[3]={1001,"lilei",'M',1005,"zhangsan",'M',1007,"lili",'F'};
struct student *p;//定义了一个结构体指针变量
p=&s;
printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式1 访问结构体指针区访问成员
printf("------------------------------\n");
printf("%d %s %c\n",p->num,p->name,p->sex);//方式2 访问结构体指针区访问成员,考研用这种方式
printf("------------------------------\n");
p=&sarr[0];
printf("%d %s %c\n",p->num,p->name,p->sex);
printf("------------------------------\n");
p=p+1;
printf("%d %s %c\n",p->num,p->name,p->sex);
printf("------------------------------\n");
p=p+1;
printf("%d %s %c\n",p->num,p->name,p->sex);
//下面给结构体指针p通过malloc申请空间,并对其成员赋值,再访问
p=(struct student*)malloc(sizeof(struct student));
p->num=100;
p->sex='M';
strcpy(p->name,"longge");
printf("------------------------------\n");
printf("%d %s %c\n",p->num,p->name,p->sex);
return 0;
}
2、typedef的使用
typedef可以声明心得类型名代替已有的类型名;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//stu 等价于 struct student,*pstu等价于struct student*
typedef struct student{
int num;
char name[20];
char sex;
}stu,*pstu;
typedef int INGETER;//特定地方使用
//typedef的使用,typedef起别名
int main(){
stu s={1001,"wagle",'M'};
stu *p=&s;//定义了一个结构体指针变量
pstu p1=&s;//定义了一个结构体指针变量
INGETER num=10;
printf("num=%d,p->num=%d\n",num,p->num);
return 0;
}
第三节:C++引用
1、C++的引用讲解
可以提高代码的便捷性,我们在修改函数外的某一变量时,使用了引用后,在子函数内的操作和函数外操作手法一致,这样编程效率较高;
#include <stdio.h>
//当你在子函数中要修改主函数中变量的值。就用引用,不需要修改就不用
void modify_num(int &b){//形参中写&,要称为引用
b=b+1;
}
//C++的引用的讲解
//在子函数内修改主函数的普通变量的值
int main() {
int a=10;
modify_num(a);
printf("after modify_num a=%d",a);
return 0;
}
#include <stdio.h>
void modify_pointer(int *&p,int *q){//引用必须和变量名紧邻
p=q;
}
//代码的目的是子函数内修改主函数的一级指针变量
int main(){
int *p=NULL;
int i=10;
int *q=&i;
modify_pointer(p,q);
printf("after modify_pointer *p=%d\n",*p);
return 0;//当代码最终执行退出代码为 -1073741819 ,不为0,那么代表进程异常结束
}
2、C++的布尔类型
#include <stdio.h>
int main(){
bool a=true;
bool b=false;
printf("a=%d,b=%d\n",a,b);
return 0;
}
第四节:C++引用案例实战
引用内部如何实现不用管
#include <stdio.h>
#include <stdlib.h>
typedef struct student{
int num;
float score;
}stu;
//在这里增加引用&,在子函数中操作s和主函数中是等价的
void change(stu &s){
s.num=2002;
s.score=85.0;
}
int main() {
stu s={1001,90.5};
printf("num:%d,score=%.1f\n",s.num,s.score);
change(s);
printf("after num:%d,score=%.1f\n",s.num,s.score);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
typedef struct student{
int num;
float score;
}stu;
//子函数中并没有改变p的地址,只是修改了p指向的结构体中的数据
//因此,并不需要在这里加引用
void change(stu *p){
p->num=2002;//(*p).num=2002这种写法也可以
p->score=85.0;
}
int main() {
stu* p=(stu*) malloc(sizeof(stu));
p->num=2001;
p->score=90.5;
printf("num:%d,score=%.1f\n",p->num,p->score);
change(p);
printf("after num:%d,score=%.1f\n",p->num,p->score);
return 0;
}