王道C语言督学营课程代码(初级阶段——已更完)

文章目录


前言

本人于2024年3月份跟着王道C语言督学营学习,本文章仅作为课程代码记录,其间加入了一些本人的思考和见解,帮助新手小白更好地理解和掌握C语言,代码可供读者参考。


一、初级阶段(已更完)

1、课程导学-打印Hello world!

#include <stdio.h>
//打印Hello, World!
int main() {
    printf("Hello, World!\n");
    return 0;
}

2、 数据的类型、数据的输入输出

2.2 -1-printf

#include <stdio.h>
//练习printf
int main() {
    int age=21;
    printf("Hello %s, you are %d years old\n","Bob",age);
    int i=10;
    float f=96.3;
    printf("student number=%-3d,score=%5.2f\n",i,f);//默认是右对齐,加一个负号变为左对齐
    i=100;
    f=98.21;
    printf("student number=%3d,score=%5.2f\n",i,f);
    return 0;
}

2.2 -2-cast强制类型转换

#include <stdio.h>
//强制类型转换
int main() {
    int i=5;
    float j=i/2;//这里做的是整型运算,因为左右操作数都是整型
    float k=(float)i/2;
    printf("%f\n",j);
    printf("%f\n",k);
    printf("Hello, World!\n");
    return 0;
}

2.3 -1-decimal conversion十进制转换

#include <stdio.h>
//十进制转换
int main() {
    int i = 123;
    printf("%d\n",i);//十进制输出
    printf("%o\n",i);//八进制输出是%o
    printf("%x\n",i);//十六进制输出是%x
    return 0;
}

2.4 -1-scanf

#include <stdio.h>
//scanf用来读取标准输入,scanf把标准输入输入内的内容,放到某个变量空间,因此变量必须取地址。
//scanf会卡住是因为,标准输入缓冲区是空的,才会卡住,不是空的就不会卡住。
int main() {
    int i;
    char c;
    float f;
    scanf("%d",&i);//浮点数写%f,字符型写%c,%d表示带符号整数。scanf读取的是键盘输入的字符串 ,写类型是为了将这些输入的字符串转换成对应的类型。
                           // &i表示取地址,一定要取地址。
    printf("i=%d\n",i);//这里是把标准输入缓区的整型读走了,还有\n留在标准输入缓冲区
//    fflush用于清空标准输入缓冲区的内容
    fflush(stdin);//清空标准输入缓冲区里的内容,即\n
    scanf("%c",&c);
    printf("c=%c\n",c);//输出字符变量c
    scanf("%f",&f);//scanf会忽略‘\n’,空格符等字符,因此上一步标准输入缓冲区里的‘\n’,被scanf忽略了,所以此时相当于标准输入缓冲区是空的,就会卡住。
    printf("f=%f\n",f);//输出浮点数f
    return 0;
}


2.4 -2-scanf-more

#include <stdio.h>
//scanf一次读多种数据类型
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;
}

3、运算符与表达式

3.2 -1-arithmetic算术运算符

#include <stdio.h>
//练习算术运算符
int main() {
    int result=4+5*2-6/3+11%4;
    printf("result=%d\n",result);
    return 0;
}

3.2 -2-relation关系运算符

#include <stdio.h>
//关系运算符,优先级小于算术运算符
//不能用数学上的连续判断大小来判断某个数
int main() {
    int a;
    while(scanf("%d",&a))
    {
        if(3<a && a<10)//a大于3同时a小于10,要这样写,而不能写成啊3<a<10
        {
            printf("a is between 3 and 10\n");
        }
        else
        {
            printf("a is not between 3 and 10\n");
        }
    }
    return 0;
}

3.3 -1-logic逻辑运算符

#include <stdio.h>
//逻辑运算符练习
//记住优先级顺序,不要加多余的括号,会增加阅卷老师的阅卷难度,可能判错
int main()
{
    int year,i,j=1;
    while(scanf("%d",&year))
    {
        if(year%4==0 && year%100!=0 || year%400==0)//能被4整除并且不能被100整除 或 能被400整除 的年份称为闰年
        {
            printf("%d is leap year\n",year);
        }else{
            printf("%d is not leap year\n",year);
        }
    }
    i=!!j;   //非!的优先级大于赋值运算符=。    j=1,非一层是0,再非一层是1,所以i=1。 逻辑!是从右往左结合
    printf("i value = %d\n",i);
    return 0;
}

3.3 -2-short circuit短路运算

#include <stdio.h>
//逻辑与 和 逻辑或。  短路运算
int main() {
    int i=0,j=1;
    i && printf("This is i and &&: you can't see me\n");//当i为假时,不会执行逻辑与后的表达式,称为短路运算。当i为真时,则可以执行。
    j && printf("This is j and &&: you can see me\n");
    // 短路运算可以让我们的代码写的很短,很简洁。不需要写if,else语句
    i || printf("This is i and ||: you can see me\n");
    j || printf("This is j and ||: you can't see me\n");//当j为真时,不会执行逻辑或后的表达式,称为短路运算。当j为假时,则可以执行。
    return 0;
}

3.3 -3-assign复合运算符

#include <stdio.h>
//赋值的练习,左值和右值的区别。复合运算符,如+=,*=的练习
int main() {
    int a=1,b=2,c=3;
    b=a+25;//b为变量时即可执行,不会报错。而写成a+25=b则会报错。
    printf("b=%d\n",b);
    a+=3;//表达的是a=a+3,简写时可以提高编译速度
    printf("a=%d\n",a);
    c*=3;//表达的是c=c*3,简写时可以提高编译速度
    printf("c=%d\n",c);
    return 0;
}

3.3 -4-sizeof求变量的字节

#include <stdio.h>
//sizeof运算符
int main() {
    int i=0;
    printf("i size is %d\n",sizeof(i));
    return 0;
}

4 、选择和循环

4.2 -1-if-else语句

#include <stdio.h>
//练习if-else条件语句
int main() {
    int i;
    while(scanf("%d",&i))
    {
//        if(i>0)
//            printf("i is bigger than zero\n");//该语句属于if内部,不建议用这种书写格式。大厂里一般要求加大括号{}
//            printf("I know!\n");//该语句不属于if内部了。
        if(i>0)//在if下加一个大括号{},养成良好的编程习惯
        {
            printf("i is bigger than zero\n");
        }else{
            printf("i is not bigger than 0\n");
        }
    }
    return 0;
}

4.2 -2-if else Multi-branching多分支

#include <stdio.h>
//if-else 的多分支语句练习
int main() {
    //以家庭用电费为例
    int i;
    float cost;
    while(scanf("%d",&i))
    {
        if(i>500)//i>500
        {
            cost=0.15;
            printf("cost=%.3f\n",cost);
        }else if(i>300)//300<i<=500
        {
            cost=0.10;
            printf("cost=%.3f\n",cost);
        }else if(i>100)//100<i<=300
        {
            cost=0.075;
            printf("cost=%.3f\n",cost);
        }else if(i>50)//50<i<=100
        {
            cost=0.05;
            printf("cost=%.3f\n",cost);
        }else//i<=50
        {
            cost=0;
            printf("cost=%.3f\n",cost);
        }
    }
    return 0;
}


4.3 -1-while循环

#include <stdio.h>
//while循环计算从1-100的和
int main() {
    int i=1,total=0;//变量初始化
    while(i<=100)//注意while后面不加分号。加分号会导致while进入死循环。
    {
        total+=i;//把i加到total上
        i++;//i++等价于i=i+1。  循环内必须要有使循环趋近于假的表达式。
        // 在循环体内若没有让while趋近于假的操作,则会进入死循环。
    }
    printf("1 add to 100: \ntotal = %d\n",total);
    return 0;
}

4.3 -2-for循环

#include <stdio.h>
//for循环实现从1加到100
int main() {
    int i,total;
    for (i=1,total=0 ; i<=100 ; i++) //在for加分号不会死循环,但是会结果不对。
        //第一个表达式初始化,第二个表达式判断条件,第三个表达式使循环趋近于假
        // 且存在第三个表达式i++,不会忘记写使得循环趋近于假的表达式,因此比while循环更好。
    {
        total+=i;
    }
    printf("total=%d\n",total);
    return 0;
}

4.3 -3-continue语句

for循环的continue(推荐使用for循环)

#include <stdio.h>
//continue语句for循环计算1-100的奇数和
int main() {
    int i, total;
    for(i=1,total=0;i<=100;i++)
    {
        if(i%2==0)
        {
            continue;//跳过continue下面的所有语句,即跳过本次循环,直接进入到下一次循环。过滤掉偶数
        }
        total+=i;
    }
    printf("total=%d\n",total);
    return 0;
}

while循环的continue

#include <stdio.h>
//continue语句while循环计算从1-100的奇数和
int main() {
    int i=1,total=0;
    while(i<=100)//注意while后面不加分号。加分号会导致while进入死循环。
    {

        if(i%2==0)
        {
            i++;//这里循环内部也需要加i++,要不会使while循环进入死循环。
            continue;//continue是跳过后面所有的语句,因此若上面不加i++这条语句,则就不会执行使循环趋于假的语句,就会进入死循环。
        }
        total+=i;//把i加到total上
        i++;
    }
    printf("total = %d\n",total);
    return 0;
}

4.3 -4-break语句

#include <stdio.h>
//break:直接结束整个循环过程
//例子;从1开始累加,当累加的和total>2000时,结束for循环
int main() {
    int i,total;
    for (i=1,total=0 ; i<=100 ; i++)
    {
        if(total>2000)
        {
            break;//直接跳出循环,不执行下面的语句total+=i,也不执行i++。直接执行循环外的语句printf()
        }
        total+=i;
    }
    printf("i=%d,total=%d\n",i,total);
    //结果为i=64,total=2016//这里面的i=64,实际上没有加到total里,total是1-63的和。
    // 因为在i=64时进入循环,判断出total已经>2000,此时就跳出循环,而没有执行total+=64
    return 0;
}

4.3 -5-two-level loop循环嵌套(两层for循环)

例子1:输出每次外循环的次数,内循环的0-4

#include <stdio.h>
//两层for循环
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",j);//打印五次j,没有加\n,就会输出在一行内。
        }
        printf("\n");//内层循环五次后,在外层循环中,输出一个换行。也就是0 1 2 3 4后面换行
    }
    return 0;
}

4.3 -6-two-level loop-printf star循环嵌套(两层for循环)

例子2:输出一个✳三角形,即第一行输出1个✳,第二行输出2个✳,第三行3个,第四行4个,第五行5个

#include <stdio.h>
//输出一个✳三角形,即第一行输出1个✳,第二行输出2个✳,第三行3个,第四行4个,第五行5个
int main() {
    int i,j;
    for(i=1;i<6;i++)//控制要输出多少行
    {
        for(j=0;j<i;j++)//内层循环控制一行中每一列显示什么
        {
            printf("*");
        }
        printf("\n");//一行结束输出一个换行
    }
    return 0;
}

5 、一维数组与字符数组

5.2 -1-array数组

#include <stdio.h>
//一维数组
int main() {
//(1)定义数组时对元素赋初值
    int a[10]={1,2,3,4,5,6,7,8,9,10};//定义一个整型数组,数组名为a,它有10个元素。
    //数组名与变量名命名规则相同:由数字,字母,下划线组成,且只能由字母和下划线开头。
    //方括号内只能为常量表达式,即为常量或者符号常量。但是不能为变量(最新的C标准支持,但是考研初试的时候一般不支持,所以不建议这么写。例如:int k=3,a[k];)。
    //数组实际访问的时候是a[0]到a[9]
    //不能写成如下这种形式,是错误写法,会编译不通的。
    //int a[10];
    //a[10]={1,2,3,4,5,6,7,8,9,10};

//(2)可以只给一部分元素赋值
    int b[10]={1,2,3,4,5};//这表示只给前五个元素赋初值,后五个元素的值为0
//(3)如果要使一个数组中全部元素的值为0
    int c[10]={0};//语言规定会把后面9个都初始化成0
//(4)在对全部数组元素赋初值时,由于数据个数已经确定,因此可以不指定数组的长度
    int d[]={1,2,3,4,5};//但是不建议这么写,因为需要导师自己数个数,很大程度上给阅卷造成困难。
//(5)查看数组的大小,用sizeof()运算符
    printf("%d\n",sizeof(d));
    return 0;
}

5.3 -1-array-access out of bounds数组的访问越界

1.访问越界非常危险,在后来出现的语言中,会对访问越界有检查,因为每次赋值时都做检查的话,对语言的性能会有所降低,因此我们的C语言是没有检查的,所以这个访问越界的问题一直存在,只能在写代码时规避这个问题。初试不考,而复试可能会考。
2.访问越界最有可能发生在写for循环时,在for循环中访问每个数组的元素,但是这个for循环的边界没有控制好,就会造成访问越界。
3.数组另一个值得注意的地方是:编译器并不检查程序对数组下标的引用是否在数组的合法范围内。

#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我们并没有赋值,但是值却发生改变
    return 0;
    //访问越界最有可能发生在写for循环时,在for循环中访问每个数组的元素,但是这个for循环的边界没有控制好,就会造成访问越界。
    //数组另一个值得注意的地方是:编译器并不检查程序对数组下标的引用是否在数组的合法范围内。
}

在这里插入图片描述


5.3 -2-array-transmit数组的传递

一维数组的传递,数组长度无法传递给子函数,下面这个例子,只能输出,1和2

#include <stdio.h>
//子函数是把某一个常用功能封装起来的作用
//void是函数不需要返回值,就是最后可以不需要写return
//数组名传递到子函数后,子函数的形参接收到的是数组的起始地址,因此不能把数组的长度传递给子函数。
void print(int a[])//一维数组的传递,数组长度无法传递给子函数,就是a[]内可以不写数字
{
    int i;
    for(i=0;i<sizeof(a)/sizeof(int);i++)//在子函数内,不能用sizeof(a)得到数组大小的,因为数组名传递到子函数的时候,弱化成了指针,指针是8个字节的。
    {
        printf("%d\n",a[i]);
    }
}

//main函数就是主函数
int main() {
    int a[6]={1,2,3,4,5,6};
    print(a);//调用print函数
    return 0;
}

在这里插入图片描述
以下正确的写法,可以在子函数内部再定义一个额外的变量length来存储数组的长度。在外部函数得到数组的长度,赋值给子函数的形参length。

//以下是正确的写法:
#include <stdio.h>
void print(int a[],int length)
{
    int i;
    for(i=0;i<length;i++)
    {
        printf("%d",a[i]);
    }
    a[3]=20;//在子函数内可以修改数组的值
    printf("\n");
}

//main函数就是主函数
int main() {
    int a[5]={1,2,3,4,5};
    print(a,sizeof(a)/sizeof(int));//数组在传递给子函数时,他的长度是传递不过去的,但是我们可以定义一个新的变量length
    printf("a[3]=%d\n",a[3]);//查看结果是否被修改为20
    return 0;
}

5.4 -1-char array字符数组

字符数组初始化表达的两种方法

#include <stdio.h>
//字符数组初始化
int main() {
    char c[10]={'I','a','m','h','a','p','p','y'};//一般不用这种初始化,太麻烦
    char m[10]="Iamhappy";//使用这种方式初始化字符数组。
}   

在这里插入图片描述


字符串如何输出:
%s的输出规则,其实是内部有一个循环的,需要有’\0’来结束循环。
因此如果用此赋值方法 char b[5]={‘h’,‘e’,‘l’,‘l’,‘o’};是没有结束符’\0’的,会导致出现乱码。

#include <stdio.h>
int main() {
//因为C语言规定字符串的结束标志为'\0',所以写char c[5]="hello";时就会访问越界
    char a[6]="hello";
    printf("%s\n",a);//使用%s来输出一个字符串,直接把字符数组名放到printf后面位置。
    // %s内部是有循环的,才能将字符串一个一个输出,而字符串里的'\0'就是循环结束的标志,只是我们感知不出这个循环。
    
//输出字符串乱码时,要去查看字符数组中是否存储了结束符'\0'
    char b[5]={'h','e','l','l','o'};//强行给每个元素赋值,就没有结束符'\0',会导致%s输出结果时出现乱码。
    printf("%s\n",b);
    return 0;
}

在这里插入图片描述


模拟printf(“%s”,c)的操作

#include <stdio.h>
//模拟printf("%s",c)的操作
void print(char d[])
{
    int i=0;
    while(d[i])//当走到结束符'\0'时,循环结束
    {
        printf("%c",d[i]);
        i++;
    }
    printf("\n");
}
int main() {
//模拟printf("%s",c)的操作
    char d[5]="how";
    print(d);
    return 0;
}

5.4 -2-scanf-string scanf读取字符串

%s会忽略空格和换行符,当它到达空格时,匹配就结束了。

#include <stdio.h>
//scanf读取字符串。
//初试不会用到scanf,但是机试的时候需要用到。
int main() {
    char c[10];
    scanf("%s",c);//整型、浮点型、字符型变量都要取地址,是因为scanf必须要放一个地址。
    //而这里不需要取地址是因为,字符数组名c中,本来就存储了数组的起始地址,记住就好。当然取地址&c也没错。
    // scanf函数输入会自动帮你放结束符,就是输入的是hello,但其实传进去的是hello\0
    // 在scanf里,%s会忽略空格和换行符,当它到达空格时,匹配就结束了。因此输入how are 输出是how
    printf("%s\n",c);
    return 0;
}

在这里插入图片描述


要解决这个问题,就分别用两个数组来存储

#include <stdio.h>
int main() {
    char d[10];
    char e[10];
    scanf("%s%s",d,e);
    printf("d=%s,e=%s\n",d,e);
    return 0;
}

在这里插入图片描述


5.5 -1-gets-puts

#include <stdio.h>
//gets一次读取一行
int main() {
    char c[20];
    gets(c);//gets()中放入我们字符数组的数组名即可。
    //gets遇到\n,就不会存储\n,而是将其翻译为空字符\0
    printf("%s\n",c);
//puts()只能用于输出字符串,同时多打印一个换行符
    puts(c);//puts等价于printf("%s\n",c);并且puts()里面所放的参数只能是字符数组名
    return 0;
}

在这里插入图片描述


5.5 -2-str系列字符串操作函数

#include <stdio.h>//头文件含义:std是standard的意思,i是input,o是output。
#include <string.h>//字符串处理头文件
//模拟strlen的操作
int mystrlen(char c[])
{
    int i=0;
    while(c[i])//找到结束符后,循环结束,从而得出字符串长度。
    {
        i++;
    }
    return i;
    // 例如:输入how,i[0]进入循环,i++,i=1;i[1]进入循环,i++,i=2;i[2]进入循环,i++,i=3;
    // i[3]='\0',此时就不进入循环了,返回i的值即为3,恰为字符串的长度。
}
int main() {
    int length;
    char c[20];
    char d[100]="world";
    char e[100];
    gets(c);
    puts(c);
//strlen()统计字符串的长度,并且\0作为结束符,是不会统计进去的。
    length=strlen(c);//使用strlen()必须先引用字符串处理头文件#include <string.h>
    printf("strlen: length=%d\n",length);

//模拟strlen的操作
    length=mystrlen(c);
    printf("mystrlen: length=%d\n",length);


//strcat练习——字符串拼接
    strcat(c,d);//把d中的字符串拼接到c中。且前一个实参只能是字符串数组,后一个实参可以是“how”这种直接输入的字符串。
    puts(c);

//strcpy练习——字符串复制
    strcpy(e,c);//把c中的字符串原模原样的复制到e中。且前一个实参只能是字符串数组,后一个实参可以是“how”这种直接输入的字符串。
    puts(e);

//strcmp练习——字符串比较大小
    printf("c:hello ? e =%d\n", strcmp(c,e));//c和e相等,返回值是0
    printf("c:hello ? how =%d\n", strcmp(c,"how"));//c="hello"与“how”相比较,实际是从左到右比较每一个字母的ASCII码值。
    //总结:
    // c>"how",返回值是正值
    // c=e,返回值是0
    // c<"how",返回值是负值
    return 0;
}

运行结果如下:
在这里插入图片描述


6、指针

6.2 -1-pointer

指针的定义,取地址操作符&与取值操作符*,指针本质
说某个变量的地址的时候,都是说它的起始地址。一个变量的地址称为该变量的指针。直接访问就类似直接拿到金元宝;间接访问就是通过指针这个藏宝图,去拿到金元宝。指针的大小:64位的程序就是8字节;32位的程序就是4字节。可以通过单步调试,输入sizeof(i_pointer)来查看大小。

#include <stdio.h>
//指针的定义
int main() {
    int i=5;
    int *i_pointer;//定义了一个指针变量,指针变量前面的*,表示该变量为指针型变量。i_pointer就是指针变量名,而不是*i_ipointer。
    //定义指针变量时,要将*与变量挨着。因此要声明三个指针变量,就要这么写:int *a,*b,*c;
    printf("%d\n",i);//直接访问

//取地址操作符&与取值操作符*,指针本质
    int a=6;
    int *b;
    b=&a;//指针变量的初始化,一定是某个变量取地址来赋值,不能随机写个数。
    //变量的类型和指针的类型一定要相同。
    //&和*的运算符优先级相同,但要按自右向左方向结合。
    printf("*b=%d\n",*b);//间接访问
    return 0;
}

在这里插入图片描述


6.3 -1-change不使用指针,在子函数改值

#include <stdio.h>
//不使用指针变量,看在子函数内去改变某个变量的值是否能实现?
void change(int j)
{
    j=5;
}

int main() {
    int i=10;
    printf("befoer change i=%d\n",i);
    change(i);//C语言的函数调用是值传递,实参i赋值给形参j,但是i和j分别各有一个地址,所以改变了j=5是j地址内的值变成5,而i地址内的值不会改变。
    printf("after change i=%d",i);
    //操作系统必考概念:程序启动起来,就是进程
    return 0;
}

在这里插入图片描述


6.3 -2-change-pointer指针的传递(在子函数改值)

#include <stdio.h>
//使用指针变量,看在子函数内去改变某个变量的值是否能实现?
void change(int *j)//j是形参
{
    *j=5;//间接访问得到变量i的空间,就可以把i的值变为5。
    // *是取值运算符,取的是j的值,而j就是i的地址,而i的地址对应的值就是i的值,这就是间接访问。
    //*j等价于变量i,只是间接访问。
}

int main() {
    int i=10;
    printf("befoer change i=%d\n",i);
    change(&i);//此时并没有违背C语言的值传递规则,这时是把变量i的地址值传给j
    printf("after change i=%d",i);
    return 0;
}

在这里插入图片描述


6.4 -1-pointer-calculate指针的偏移(整型数组)

下面以整型数组为例子,帮助大家更好地理解

#include <stdio.h>
//指针的偏移使用场景,也就是对指针进行加和减。
//下面以整型数组为例
#define N 5
int main() {
    int a[N]={1,2,3,4,5};//定义一个数组.
    // 数组名内存储了数组的起始地址,a中存储的就是一个地址值。
    int *p;//定义了一个指针变量p
    p=a;//因为数组存储的就是一个地址值,所以无需取地址,并且该地址是a的起始地址。
    int i;
//不使用指针来遍历循环
    printf("no use pointer:");
    for(i=0;i<N;i++)
    {

        printf("%3d",a[i]);
    }
    printf("\n--------------------------------------\n");
//使用指针来遍历循环
    printf("use pointer:");
    for(i=0;i<N;i++)
    {

        printf("%3d",*(p+i));//p是a的起始地址,那么*p对应的就是a[0],因为*p一次取的长度就是基类型的长度。
        //p+1,是指针的偏移,偏移的长度也是其基类型的长度,也就是sizeof(int),这样通过*(p+1)就可以获得元素a[1]。
        //因此写*(p+i)与a[i]是等价的。
    }
    printf("\n---------------------------------------\n");
//使用指针,逆序输出
    p=&a[4];//让p指向a的最后一个元素的起始地址
    printf("use pointer and reverse:");
    for(i=0;i<N;i++)
    {

        printf("%3d",*(p-i));
    }
    printf("\n");

    //float *p; p的加减也是偏移4个字节
    return 0;
}

在这里插入图片描述


6.4 -2-pointer-array指针的偏移(字符数组)

下面以字符数组为例,指针与一维数组的传递和偏移练习

#include <stdio.h>
//指针与一维数组的传递和偏移练习
//C语言的子函数形参位置,并没有数组变量的设计,我们把数组名实参传递给子函数时,形参接收到的是数组名内存储的数组的起始地址。
//数组名作为实参传递给子函数时,是弱化为指针的,因此sizeof时,就是指针的大小,即8个字节。
//下面以字符数组为例
void change(char *d)
{
    *d='H';//改值方式第一种,指针改值
    d[1]='E';//改值方式第二种,下标法改值
    *(d+2)='L';//指针偏移改值

}
int main() {
    char c[10]="hello";
    puts(c);
    change(c);
    puts(c);
    return 0;
}

在这里插入图片描述


6.5 -1-pointer-malloc指针与动态内寸申请(非常重要)

数组需要固定长度很不方便,其实C语言的数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中。如果需要使用的空间大小不确定,那么就要使用堆空间。
本节内容非常重要,考研初试,机试,复试都会考到!

#include <stdio.h>
#include <stdlib.h>//malloc要使用的头文件
#include <string.h>//strcpy要使用的头文件
//本节内容非常重要,考研初试,机试,复试都会考到
int main() {
    int size;
    char *p;
    scanf("%d",&size);//输入要申请的空间大小
    //malloc返回的void*代表无类型指针
    //void*类型的指针是不能偏移的,因此不会定义无类型指针。
    p=(char*) malloc(size);//前面的(char*)是强制类型转换
//可以用类似数组的方法存储字符数据
    p[0]='H';
    p[1]='O';
    p[2]='W';
    p[3]='\0';
    puts(p);//gets和puts接口里所放的是指针类型。
//也可以用strcpy()函数存储字符数据
    strcpy(p,"malloc success");
    puts(p);
//使用free(),释放申请的空间
    free(p);//我们申请的空间,使用完之后必须要释放申请的空间,否则初试时会扣分。
    //free时必须使用malloc申请时返回的指针值,不能进行指针偏移,也就是不能写p+1。
    //若要进行指针偏移,可以另外再定义一个指针变量,到时候free该变量就行。
    printf("free success\n");
    return 0;
}

在这里插入图片描述


6.5 -2-heap-stack栈空间与堆空间的差异

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//堆和栈的差异

//函数栈空间释放后,函数内的所有局部变量消失
//出现这种情况,就说明是对原理理解不够透彻
char* print_stack()
{
    char c[100]="I am print_stack function";
    char *p;
    p=c;
    puts(p);
    return p;
}

//堆空间在整个进程中一直有效,不会因为函数执行结束而释放(可以这么写,不出现乱码)
char *print_malloc()
{
    char *p=(char*) malloc(100);
    strcpy(p,"I am print_malloc function");
    puts(p);
    return p;
}
int main() {
    char *p;
    p=print_stack();
    puts(p);//因为在上一条代码运行完之后,print_stack()的栈空间就被释放掉了,供下一个函数使用。
    //而在子函数内把栈空间内数据的起始地址给返回回来,然后这里外部主函数接着去打印,就会出现乱码。因为栈空间被释放了,后面的内容被别人踩了。

    printf("----------------------------\n");

    p=print_malloc();
    puts(p);
    free(p);//只有通过free时,堆空间才会释放。
    return 0;
}

栈空间
在这里插入图片描述
堆空间
在这里插入图片描述


7、函数

7.2 -1-function函数的声明与定义

(1)不创建子文件的代码:

#include "func.h"//双引号是在该目录下去找头文件,相当于是找我们自定义的头文件时要用双引号。
//函数的声明与定义
//这里为函数的声明
void print_message();//先声明。函数的声明。
int main() {
    print_message();//后调用。函数的调用。当函数没有声明时,默认返回的类型是int类型,与定义的void类型冲突,运行时会有警告。
    printf("Hello, World!\n");
    return 0;
}
//这里为函数的定义
void print_message()
{
    printf("how do you do\n");
}

(2)创建子文件的代码:
创建.c源文件/.h头文件:右键项目名称,新建,选择C/C++源文件/C/C++头文件,勾上添加到目标,点确定。
①func.h

//
// Created by lenovo on 2024/3/30.
//

#ifndef INC_1_FUNCTION_FUNC_H
#define INC_1_FUNCTION_FUNC_H \
//共同使用的头文件,可以写在这里。
#include <stdio.h>//<>是在标准库下去找头文件
//函数声明也放在这里,有可能会经常使用到,避免重复编写代码。
void print_message();//print_message函数的声明
int print_star(int i);//print_star函数声明,有形参,有返回值。
#endif //INC_1_FUNCTION_FUNC_H

②func.c
函数可以嵌套调用,但是不能嵌套定义。如下例:print_message函数嵌套调用print_star。
各个函数之间可以互相调用,但是不能调用main函数。

//
// Created by lenovo on 2024/3/30.
//

#include "func.h"
//这里为函数的定义,也叫函数的实现
//print_star函数
int print_star(int i)
{
    printf("***************************\n");
    printf("print_star%d\n",i);
    return i+3;
}
//print_message函数
//可以调用print_star,就是嵌套调用
void print_message()
{
    printf("how do you do\n");
    print_star(3);//3是实参
}

③main.c
按住ctrl+鼠标左键,可以跳转到对应的函数,查看源码。

#include "func.h"//双引号是在该目录下去找头文件,相当于是找我们自定义的头文件时要用双引号。
//函数的声明与定义
//这里为函数的声明
int main() {
//print_star调用,返回值的应用
    int a=10;
    a=print_star(a);//print_star函数有返回值,return i+3。
    printf("a=%d\n",a);//所以此时a变为13。
    printf("-------------------------------------------\n");
//print_message嵌套调用print_star
    print_message();//按住ctrl+鼠标左键,可以跳转到对应的函数,查看源码。
    printf("-------------------------------------------\n");
//print_star调用,返回值也可以不使用
    print_star(5);
    return 0;
}

7.3 -1-recursion递归函数

递归的核心是①找公式②编写结束条件
例子一:求n的阶乘

#include <stdio.h>
//例子一:求n的阶乘
int f(int n)
{
    //一定要有结束条件,结束条件一定是在return之前
    if(1==n)//第②步写结束条件
    {
        return 1;
    }
    return n*f(n-1);//第①步找递归关系式
}
int main() {
    int n;
    scanf("%d",&n);
    printf("f(%d)=%d\n",n,f(n));
    return 0;
}

例子二:上台阶
假如有n个台阶,一次只能上1个台阶或2个台阶,请问走到第n个台阶有几种走法? 为便于读者理解题意,这里举例说明如下:假如有3个台阶,那么总计就有3种走法:第一种为每次上1个台阶,上3次;第二种为先上2个台阶,再上1个台阶;第三种为先上1个台阶,再上2个台阶。

#include <stdio.h>
//例子二:上台阶
int step(int n)
{
    if(1==n||2==n)//第②步写结束条件,结束条件一定是在return之前
    {
        return n;//当台阶是1个或者两个时,递归结束
    }
    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, step(n));
    return 0;
}

7.4 -1-global-variable全局变量和局部变量

#include <stdio.h>
int i=10;//i是一个全局变量,不建议使用,初试非必要不使用。全局变量在数据段,不会被释放掉。
//全局变量是从定义的位置到文件的末尾,这个区段内有效,并不是全局有效,即定义位置的上面是无效的。
void print(int a)//形参可以看成一个局部变量
{
    printf("I am print i=%d\n",i);//这里获取的是数据段的i
}
int main() {
    {
        int j=5;//局部变量只在离自己最近的大括号内有效
    }
    //printf("j=%d",j);此语句运行时会报错,因为j只在离自己最近的大括号内有效
    //for循环括号内定义的变量,也是局部变量,在循环体外也不可用
    print(5);
    printf("main i=%d\n",i);
    int i=5;//这是局部变量在栈空间
    print(5);
    return 0;
}

8、 结构体及C++引用讲解

8.2 -1-struct结构体的定义、初始化、结构体数组

#include <stdio.h>

struct student{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};//结构体类型声明,注意最后这里一定要加分号

int main() {
    struct student s={1001,"lele",'M',20,85.4,"Shenzhen"};//结构体变量定义及初始化
    //结构体输出必须单独去访问内部的每个成员
    s.num=1003;
    printf("%d %s %c %d %5.2f %s\n",s.num,s.name,s.sex,s.age,s.score,s.addr);
    scanf("%d%s %c%d%f%s",&s.num,s.name,&s.sex,&s.age,&s.score,s.addr);//%c前必须要空格,因为它不会忽略空格和换行符。
    // s.name和s.addr不需要取地址,因为s.name和s.addr是字符数组,本身存储的就是变量的起始地址。
    printf("%d %s %c %d %5.2f %s\n",s.num,s.name,s.sex,s.age,s.score,s.addr);

    printf("---------------------------------------------------------------------------------------------------------\n");

    struct student sarr[3];//定义一个结构体数组变量
    int i;
    for(i=0;i<3;i++)//结构体数组的读取
    {
        scanf("%d%s %c%d%f%s",&sarr[i].num,sarr[i].name,&sarr[i].sex,&sarr[i].age,&sarr[i].score,sarr[i].addr);
    }
    for(i=0;i<3;i++)//结构体数组的输出
    {
        printf("%d %s %c %d %5.2f %s\n",sarr[i].num,sarr[i].name,sarr[i].sex,sarr[i].age,sarr[i].score,sarr[i].addr);
    }
    return 0;
}

在这里插入图片描述


8.2 -2-struct-size结构体对齐

结构体对齐也叫计算结构体的大小。
为什么要对齐?答:就是为了cpu高效地去取内存上的数据。
结构体的大小必须是其最大成员的整数倍。
如果两个小存储之和是小于最大长度的,那么它们就结合到一起。

#include <stdio.h>
//结构体对齐,实际就是计算结构体的大小
struct student_type1{
    double score;//double是一种浮点类型,8个字节,浮点类型分为float和double,记住这两种即可。
    short age;//short是整型,占两个字节。2要按8个对齐。
    // 所以s1的大小是16。
};
struct student_type2{
    double score;//这是8个字节
    int height;//这是4个字节
    short age;//这是2个字节
    //如果两个小存储之和是小于最大长度的,那么它们就结合到一起。
    //int和short的字节总数是6,是小于8的,可以填到一起,因此这两个总的是8。
    //若把int改成char,char和short挨在一起就可以一起占用4个字节,这样写可以减小结构体占用的内存大小,而这里的大小改变就是由4变为8。
    // 所以s2的大小是16。
};
struct student_type3{
    int height;//这里是4个字节
    char sex;//这里是1个字节
    short age;//这是2个字节
    //char和short的字节总数是3,是小于4个字节的,可以填到一起,因此这两个是占4个字节的。
    // 所以s3的大小是8。
};
int main() {
    struct student_type1 s1;
    struct student_type2 s2;
    struct student_type3 s3;
    printf("s1 size=%d\n",sizeof(s1));
    printf("s2 size=%d\n",sizeof(s2));
    printf("s3 size=%d\n",sizeof(s3));
    return 0;
}

8.3 -1-struct-pointer结构体指针

#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',1002,"zhangsan",'M',1003,"lili",'F'};
    struct student *p;//定义了一个结构体指针变量
// 通过结构体指针访问一个结构体变量
    p=&s;
    printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式一通过结构体指针去访问成员。加括号是因为取值的优先级没有点的优先级高。
    printf("%d %s %c\n",p->num,p->name,p->sex);//方式二通过结构体指针去访问成员,使用这种方式。
    printf("--------------------------------------\n");
// 通过结构体指针访问一个结构体数组变量
    p=sarr;//sarr与&sarr[0]等价
    printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式一通过结构体指针去访问成员。加括号是因为取值的优先级没有点的优先级高。
    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("%d %s %c\n",p->num,p->name,p->sex);//方式二通过结构体指针去访问成员,使用这种方式。
    printf("--------------------------------------\n");
// 下面给结构体指针p通过malloc申请空间,并对其成员赋值,再访问
    p=(struct student*)malloc(sizeof(struct student));//强制类型转换,因为malloc的返回类型是void。
    p->num=100;
    p->sex='M';
    strcpy(p->name,"longge");
    printf("%d %s %c\n",p->num,p->name,p->sex);//方式二通过结构体指针去访问成员,使用这种方式。
    return 0;
}

在这里插入图片描述


8.3 -2-typedef声明新的类型名来代替原有的类型名

#include <stdio.h>
//typedef的使用,typedef就是起别名
typedef struct student{
    int num;
    char name[220];
    char sex;
}stu,*pstu;//这里就是起别名。stu等价于struct student;pstu等价于struct student*

typedef int INTEGER;//给int起别名。
int main() {
    stu s={0};
    stu *p=&s;//定义了一个结构体指针变量
    pstu p1=&s;//定义了一个结构体指针变量
    INTEGER num=10;
    printf("num=%d,p->num=%d\n",num,p->num);
    return 0;
}

8.4 -1-quoteC++的引用讲解

注意这里要创建的文件是C++文件。
在子函数内修改主函数的普通变量的值

#include <stdio.h>
//C++的引用
//在子函数内修改主函数的普通变量的值
//使用场景当你在子函数中,要修改主函数中变量的值,就用引用&;不要修改,就去掉引用&。
void modify_num(int &b)//形参中写&这个符号,要称为引用,不是取地址
{
    b=b+1;
}
int main() {
    int a=10;
    modify_num(a);
    printf("after modify_num a=%d\n",a);
    return 0;
}

在这里插入图片描述


8.4 -2-modify-pointerC++的指针引用

C++文件
在子函数内修改主函数的一级指针变量的值

#include <stdio.h>
//在子函数内修改主函数的一级指针变量的值
void modify_pointer(int *&p,int *q)//引用&必须和变量名紧邻,否则会编译不通
{
    p=q;
}
int main() {
    int *p=NULL;//NULL就是0,当是0时,就是什么地方都不指向
    int i=10;
    int *q=&i;
    modify_pointer(p,q);
    printf("after modify_pointer *p=%d\n",*p);
    return 0;//进程已结束,退出代码为 -1073741819 (0xC0000005)   //执行退出代码不为0,那么代表进程异常结束了。
}

在这里插入图片描述


8.4 -3-bool C++的布尔类型

#include <stdio.h>
//C++的布尔类型
int main() {
    bool a=true;
    bool b=false;
    printf("a is true=%d,b is false=%d\n",a,b);//C++规定true的值是1,false的值是0
    return 0;
}

在这里插入图片描述


总结:以上就是C语言初级阶段的内容,本文仅仅简单介绍了C语言和C++的使用。欢迎读者批评指正,多多交流,有意者可以一起探讨有关编程的相关问题。私信可领取源代码压缩包。

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值