Bootstrap

王道C语言笔记(初级语法篇)

目录

第一章:数据的类型、数据的输入输出

第一节:数据类型-常量-变量

第二节:混合运算-printf

第三节:整形进制转换

第四节:scanf读取标准输入

第二章:运算符与表达式

第一节:算术运算符与关系运算符

第二节:逻辑运算符与赋值运算符,求字节运算符

第三章:选择、循环

第一节:if-else

第二节:循环while,for,continue,break

第四章:一维数组与字符数组

第一节:一维数组

第二节:数组访问越界与数组的传递

第三节:字符数组与scanf读取字符串

第四节:gets与puts,strlen-strcmp

第五章:指针

第一节:指数的本质(间接访问原理)

第二节:指针的传递使用场景

第三节:指针的偏移使用场景

第四节:指针与malloc动态内存申请,栈与堆的差异

第六章:函数

第一节:函数的声明与定义-嵌套调用

第二节:函数的递归调用

第三节:局部变量与全局变量

第七章:结构体及C++引用

第一节:结构体-结构体数组-结构体对齐

第二节:结构体指针与typedef的使用

第三节:C++引用

第四节:C++引用案例实战


第一章:数据的类型、数据的输入输出

第一节:数据类型-常量-变量

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;
}

;