C语言督学营(初级阶段)

初级阶段

1.编程环境搭建、调试

(1)C的历史故事

1.C语言之父:
①肯·汤普森 (Ken Thompson)。也是Go语言的发明者
②丹尼斯·里奇 (Dennis Ritchie)。对C语言的评价:奇怪的、有缺陷的、取得巨大成功的

2.程序出现的错误,称为bug(虫子形状)

写代码快速提升的方法

1.增量编写法:写一点,及时验证
2.调试:遇到问题,用调试去定位bug
步过:当前函数,一步一步向下走
步入:进入该行函数的内部


(2)快捷键

Ctrl+Alt+L代码格式化快捷键,一键标准化你的代码,专治强迫症
crtl + 鼠标左键:快速到函数定义位置


(3)编程环境 Clion、断点调试(单步调试)



2.数据类型、标准输入输出

(1)数据分类

1.数据的分类

基础数据类型:①整型 ②浮点型 ③字符型

2.常量
①整型 int
②实型(浮点型) float、double
③字符型
④字符串型
在这里插入图片描述

3.变量
(1)变量的命名规则:
只能包含字母、下划线、数字,且只能以字母或下划线开头。(不能包含特殊符号,不能以数字开头)
在这里插入图片描述
在这里插入图片描述


(2) C语言关键字
在这里插入图片描述

避免命名的变量名与关键字重名



4.符号常量

#define N 5
//符号常量
#include <stdio.h>
#define PI 3+2   //注意 define后面不加分号

int main() {
    int i = PI*2; //这里PI是直接替换为 3+2 。 i = 3+2*2 = 7
    printf("i=%d\n",i);
    return 0;
}

5.浮点型常量
在这里插入图片描述
3e-3,即3×10-3=0.003


(2)printf

强制类型转换

int i = 5;
float f = (float)i/2;

1.printf格式控制
%-3d:长度为3,左对齐(负号)
%5.2f:长度为5,小数点后保留2位


2.printf的七种输出类型
%d:带符号整型
%u:无符号整型
%f:单精度浮点数float
%lf:双精度浮点数double
%c:字符型
%s:字符串
%o:八进制
%x:十六进制


(3)整型进制转换

10转2:短除法,除2。从最底下开始为高位
10转8:短除法,除8。从最地下开始为高位

#include <stdio.h>

int main() {
    //int i = 123; 
    //int i = 0173 // 八进制
    int i = 0x7b; //十六进制
    printf("%d\n",i);
    printf("%o\n",i);//八进制
    printf("%x\n",i);//十六进制
    return 0;
}
①内存视图

在这里插入图片描述
英特尔CPU采用小端方式,低位在前,高位在后

②ASCII码表

在这里插入图片描述
A 65 0x41
B 66 0x42
C 67 0x43
D 68 0x44


③计算器

快捷键:Win+R + calc
在这里插入图片描述


(4)scanf的原理

①标准输入缓冲区,是一段内存。大小为4096B = 4KB
scanf卡住(阻塞),当且仅当标准输入缓冲区为空时。
scanf是 行缓冲。输入值后必须回车,才能触发scanf。标准输入缓冲区内是输入的字符串,外加一个\n
在这里插入图片描述
③清空标准输入缓冲区

fflush(stdin);

④scanf也有返回值。ret是scanf匹配成功的个数,方便单步调试。
在这里插入图片描述

scanf多种数据混合输入(混合读取),注意 %c前面必须加空格%s不加取地址
注意,【输出%d %c时,前面要用空格间隔开。否则%c会读取输入的空格。因为%c不能忽略空格和\n】

printf("%d %c",i,c);//正确,%d %c用控制间隔开
printf("%d%c",i,c);//错误,输入%c会读入你敲的空格(为了分词的空格)

3.运算符与表达式

(1)C语言的13种运算符

①算数运算符 + - * / %
②关系运算符 > < == >= <= !=
③逻辑运算符 ! && ||
④指针运算符 *,&
⑤成员选择运算符 . ->

*:①乘号 ②指针 ③解引用

在这里插入图片描述

记住优先级的目的:不加多余的括号(过多括号,降低代码阅读速度,看起来也很不专业)

在这里插入图片描述

1.算数运算符和算数表达式


2.关系运算符和关系表达式
①关系表达式的值只有真和假,真为1,假为0。(C语言中没有布尔类型…)
②优先级:算数运算符 > 关系运算符


3.逻辑运算符和逻辑表达式
①逻辑表达式也只有真和假,真为1,假为0
②优先级:逻辑非 > 算数运算符 > 关系运算符 > 逻辑与、逻辑或
短路运算

&&:当i为假时,不会执行逻辑与后面的表达式
||:当i为真时,不会执行逻辑与后面的表达式
称为短路运算

#include <stdio.h>

int main(){
    int i = 0;
    i&&printf("you can't see me!\n");//与运算,前面为假,则整体为假,短路后面的运算
    i||printf("you can see me!\n"); //或运算,前面为真,则整体为真,短路后面的运算
    return 0;
}



int i = 0;
i&&printf("you can't see me!");

i为真,输出后面的。i为假,短路后面的。
这种写法等价于下面。短路运算,缩短了代码长度。

int i = 0;
if(i){
	printf("you can't see me!");
}

4.赋值运算符
左值 L-value:可以放在赋值运算符左边的值,表达式不能作为左值,只能放变量
右值 R-value:可以放在赋值运算符右边的值
②复合赋值运算符 : +=、-=、*=、/=

(复合赋值运算符,可以提高编译速度)


5.求字节运算符 sizeof
sizeof运算符的作用:求常量或变量所占的空间大小
在这里插入图片描述


6.位运算符
(1)移位运算符

①左移运算符 <<
左移乘2

②右移运算符 >>
右移除2


1 << i:1左移i位,为2的i次方

#include <iostream>
#include <cmath>
using namespace std;

int main() {
    int i = pow(2,5);  //2的5次方
    cout << "i = " << i << endl;
    int j = 1 << 5;   //2的5次方
    cout << "j = " << j << endl;
    return 0;
}

(2)运算符优先级

自增运算符++ 的优先级高于 解引用*
所以在表达式 *p++ 中,先执行自增操作,然后解引用,取得新地址p指向的值。
正确的写法应该是:(*p)++ ,先取内容(解引用),再自增

在这里插入图片描述



4.选择、循环

1.选择分支结构:if、else if、else


2.循环结构:while、for、continue、break

1.while循环
while循环体里,一定要有使得while判断表达式趋近于假的操作(如i++),否则会死循环
在这里插入图片描述

2.for循环
for(表达式1;表达式2;表达式3){ 循环体 }
表达式1,一般用来初始化。可以初始化多个变量。
表达式2,是循环判断语句,相当于while括号里的内容。
表达式3,是while中容易漏掉的,让循环判断语句趋近于假的操作。

3.continue语句
跳过本次循环,进入下一轮循环。

注意,while中使用continue要写两次i++

4.break语句
跳出并提前结束整个循环



OJ-4.3:换钞票
思路:暴力枚举

#include <stdio.h>
//某人想将手中的一张面值100元的人民币换成10元、5元、2元和1元面值的票子。
// 要求换正好40张,且每种票子至少一张。问:有几种换法?
int main(){
    int method = 0;
    int R10,R5,R2,R1;
    for(int R10 = 1; R10 <= 10; ++R10){ //100元最多换10张10元
        for(int R5 = 1; R5 <= 20; ++R5){//100元最多换20张5元
            for(int R2 = 1; R2 <= 40;++R2){
                for(int R1 = 1; R1 <= 40; ++R1){
                    if(R1*1+R2*2+R5*5+R10*10==100 && R10+R5+R2+R1==40){
                        //printf("%d %d %d %d\n",R10,R5,R2,R1);
                        method++;
                    }
                }
            }
        }
    }
    printf("%d\n",method);
    return 0;
}

5.数组

1.数组使用场景
①具有相同的数据类型
②需要保存原始数据

2.数组是构造类型,数组名保存的是数组的起始地址,对应保存的是数组的首元素。

3.声明数组长度的时候不能用变量。即使某些IDE不报错,OJ可能也会报错。尽量使用常量定义数组长度,选题目声明的最大范围即可,稍微浪费一些空间。


(1) 申请动态数组 和 申请静态数组 的区别

①若要申请静态数组,则长度必须为常量。不可以是变量n。int A[10];
②若数组长度是变量n,则必须申请动态数组

//C语言
int *A = (int *)malloc(n*sizeof(int)); //申请动态数组A

//C++
int *A = new int[n];

(2)一维数组 int a[10]

(1)声明/定义

int a[10]; //a[0] ~ a[9],共10个

(2)初始化
①全部初始化

int a[10]={1,2,3,4,5,6,7,8,9,10};

②部分部分初始化,未写到的部分,值为0

int a[10] = {0};

③不指定长度,由初始化的个数确定数组长度

int a[ ] = {1,2,3,4,5}; //长度为5

①访问越界

访问越界的危险之处:i,j没有重新赋值,却在访问越界时被迫改变了值

#include <stdio.h>

int main(){
    int a[5]= {1,2,3,4,5,6};
    int i = 10;
    int j = 20;
    a[6] = 6,a[7] = 7;//访问越界
    printf("i=%d,j=%d\n",i,j);
    return 0;
}
②数组的传递

1.【数组传递给子函数时,长度无法传过去,只能传递数组的首地址。若想要传递数组长度,需要再用一个参数(int变量)保存数组的长度】
数组的长度无法直接传递到子函数,需要额外再用一个int len 变量保存数组长度,双参数传递。
因为传递到子函数后,数组就被弱化成了指针(8字节),实际上传递到子函数的是数组的起始地址
2.在子函数中可以修改数组元素的值,主函数里能接收到。因为操作的是内存地址。
在这里插入图片描述

#include <stdio.h>

void print(int a[],int len){ //注意这里写的是 int a[],传递到子函数里的是 数组的起始地址
    for(int i = 0; i < len; ++i){
        printf("%d ",a[i]);
    }
}

int main(){
    int a[5]= {1,2,3,4,5};
    print(a,sizeof(a)/sizeof(int));
    return 0;
}

(3)字符数组 char c[10]

①初始化:双引号内放字符串

char c[10] = "Iamhappy";

在这里插入图片描述


②实验:用越界访问操纵内存

字符数组只有碰到结束符\0,即0x00才会停止输出。正常情况下给字符数组赋值,一定要留一个位置放\0。否则 %s可能会多输出几个乱码。

#include <stdio.h>

int main(){
    char c[10] = "I am happy";
    c[10] = 0x41;
    c[11] = 0x42;
    c[12] = 0x43;
    c[13] = 0x44;
    printf("%s\n",c);
    return 0;
}

在这里插入图片描述


③scanf读取字符数组
读入时,会自动往字符数组中放\0
(scanf会忽略空格和\n)
在这里插入图片描述


④gets函数、puts函数、str系列函数

1.gets(字符数组名c):读取一整行
或者fgets(c,sizeof(c),stdin)

fgets是从标准输入stdin中读取 若干个(第二个参数为读取长度)到p所指空间中,是安全的。
而gets是不安全的,可能访问越界,没有限制长度。读取内容可能会超出p本身的空间大小

2.puts(字符数组名c):输出字符数组内容

等价于printf("%s\n",c);

例:wangdaoOJ 8.2

#include <cstdio>
#include <cstdlib>

void func(char *&p){
    p = (char *)malloc(100);
    fgets(p,100,stdin);
}

int main() {
    char *p;  //字符指针p
    func(p);
    puts(p);
    free(p);
    return 0;
}

3.str系列函数
strlen(c):求字符数组长度
strcat(c,d):把后一个字符数组内容拼接到前一个中。
strcpy(a,b):把后一个内容,完全复制给前一个
strcmp(e,"how"):<,返回负值;=,返回0;>,返回正值。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述



例题1:王道OJ 5-2
读取一个字符串,字符串可能含有空格,将字符串逆转,原来的字符串与逆转后字符串相同,输出0,原字符串小于逆转后字符串输出-1,大于逆转后字符串输出1。例如输入 hello,逆转后的字符串为 olleh,因为hello 小于 olleh,所以输出-1

#include <stdio.h>
#include <string.h>

int main() {
    char c[100], d[100]={0};//初始化,防止反转后没有\0
    gets(c);
    int len = strlen(c);
    for (int i = 0; i < len; ++i) {//字符数组逆置
        d[i] = c[len-1-i];
    }
//    printf("%s\n%s\n", c, d);
    int res = strcmp(c,d);
    if(res<0) printf("%d\n",-1);
    if(res==0) printf("%d\n",0);
    if(res>0) printf("%d\n",1);
    return 0;
}

注意使用 “增量编写法”,随时检查已写代码是否正确,方便快速定位问题。




6.指针

指针大小:32位系统为4字节,64位系统为8字节。(多少位系统就是地址是多少位,指针就是多少位。)

1.指针的定义、初始化

基本类型名 *指针名;
int i = 5;
int *i_pointer = &i;  //指针变量的初始化,是某个变量的地址,不能随意赋值
int *a,*b,*c;//定义指针变量,*和后面的指针变量名挨着

指针就是地址。指针变量就是用来保存地址的变量。


2.取地址、取值操作符,指针的本质
&:取地址操作符,引用
*:取值操作符,解引用


3.直接访问、间接访问

int i = 5;
int *i_pointer; //定义
i_pointer = &i; //初始化
printf("%d\n",i);          //直接访问
printf("%d\n",*i_pointer); //间接访问

指针的使用场景:指针传递与偏移

4.指针的传递

C语言的函数调用是值传递,实参赋值给形参

想要在子函数中能改变main函数中的值:
方法一:使用指针传递:实参传地址,形参和子函数内解引用
方法二:C++引用
两者的效果都是,使得子函数内的形参变量,和main函数内的实参变量,的地址相同。操纵同一块内存空间。



例题1:6-1
用子函数改变main函数中函数的值,C语言实现

#include <stdio.h>

int change(int* j){ //形参j现在是指针类型,即地址。(变量地址,弱化为指针)
    *j /= 2;
    return *j;
}

int main(){
    int i;
    while(scanf("%d",&i) != EOF){
        change(&i);//C语言只能值传递,把变量的地址传给子函数
        printf("%d\n",i);
    }
    return 0;
}

例2:数组是引用传递,函数中的int array[ ]会直接改变主函数中数组的值。int array[ ] 等价于 int *array,传递的是数组的首地址

#include <cstdio>

void changeArray(int array[]){
    for(int i = 0; i < 5; ++i){
        array[i] = i;
    }
}

void printArray(int array[]){
    for(int i = 0; i < 5; ++i){
        printf("%d ",array[i]);
    }
    printf("\n");
}

int main() {
    int array[5];
    changeArray(array);
    printArray(array);
    return 0;
}



(1)进程地址空间:堆、栈

在这里插入图片描述
栈空间:函数结束,操作系统立刻回收栈空间
堆空间:进程结束,堆空间才消亡


5.指针的偏移
即指针(地址)的加减。
加,往后偏;减,往前偏。

*(a+i) //等价于 a[i]

a[i] 等价于 *(a+i)
char d[ ] 等价于 char *d


(2)malloc、free

①malloc头文件:<stdlib.h>
②malloc返回值为 void *,空指针类型。但空指针类型不能偏移(加减),无意义。所以要强转为我们定义的类型指针。
③使用malloc申请堆空间,就要使用free释放申请的堆空间。
free释放的地址,必须是当时malloc申请的地址。若要进行偏移,则用 *q 保存 *p,p进行偏移,q进行释放——free(q)。

#include <stdio.h>
#include <stdlib.h>//malloc头文件

int main() {
    char *p;
    int size;
    scanf("%d",&size);
    p = (char *)malloc(size);//使用malloc动态申请堆空间
    p[0]='H';
    p[1]='o';
    p[2]='w';
    p[3]='\0';
    puts(p);
    free(p);//使用free释放堆空间。释放的地址必须是malloc申请的地址,不能修改。
    return 0;
}


例题6-2:使用malloc动态申请变长字符数组

#include <stdio.h>
#include <stdlib.h>

int main(){
    int size;
    scanf("%d",&size);
    char *p = (char *)malloc(size); //使用malloc
    getchar();//吞掉回车:清除标准输入缓冲区中的\n
    gets(p); //输入字符串
    puts(p); //输出字符串
    return 0;
}



7.函数

(1)子函数

①子函数的作用:将某个常用功能封装起来,下次可以直接调用
②子函数的局限:无法将main函数中变量的值传递回去,只能传递指针或者使用C++引用(&)

想要在子函数中能改变main函数中的值:
方法一:使用指针传递:实参传地址,形参和子函数内解引用
方法二:C++引用
两者的效果都是,使得子函数内的形参变量,和main函数内的实参变量,的地址相同。操纵同一块内存空间。


(2)递归

①缩小问题规模的return
②递归出口


例题1:跳台阶问题:一次只能跳1阶或者2阶,问到n阶共有多少种跳法?

#include <stdio.h>

int fibonacci(int n){
    if(n==1 || n==2){
        return n;
    }else{
        return fibonacci(n-1)+fibonacci(n-2);
    }
}

int main(){
    int n;
    while(scanf("%d",&n) != EOF){
        printf("%d\n",fibonacci(n));
    }
    return 0;
}


(3)局部变量与全局变量

在这里插入图片描述
①局部变量的生命周期:离自己最近的大括号内有效
②全局变量的生命周期:从定义位置到该文件末尾都有效。建议少用全局变量
③不同函数内部可以使用同名的局部变量,互不干扰。

①静态局部变量

静态局部变量只会初始化一次,仅在函数内部有效。
全局变量也只会初始化一次,在整个.cpp文件内都有效,全局有效
在这里插入图片描述



8.结构体与C++引用

(1)结构体

1.结构体定义
①结构体可以嵌套定义,即在结构体里定义结构体
在这里插入图片描述

2.结构体初始化
①加struct,使用大括号,按照定义顺序进行初始化
②调用,用对象名.属性名方式

3.结构体数组

4.结构体对齐(计算结构体的大小)
①结构体的大小,必须是其最大成员的整数倍
②为什么要对齐:为了CPU能高效地取走内存上的数据
③各种数据类型所占内存空间:
char:1字节
short:2字节
int、float:4字节
double:8字节

5.结构体指针
指针->成员 等价于 (*指针).成员


6.typedef 起别名
给结构体、结构体指针起别名

取别名前:

struct student{
	int num;
	char name[20];
	char sex;	
};

int main(){
    struct student s = {1001,"wangdao",'M'};  //结构体对象
    struct student *p = &s; //结构体指针
    printf("%d %s %c\n",s.num,s.name,s.sex);
    printf("%d %s %c\n",p->num,p->name,p->sex);
    return 0;
}

给结构体、结构体指针 取别名后:可用 stu 代替 struct student,pstu 代替 struct student *

typedef struct student{
    int num;
    char name[16];
    char sex;
}stu,*pstu; //stu等价于struct student, pstu等价于struct student *

int main(){
    stu s = {1001,"wangdao",'M'};  //结构体对象(stu等价于 struct stu)
    pstu p = &s; //结构体指针 (pstu等价于 struct student * 等价于 stu *)
    printf("%d %s %c\n",s.num,s.name,s.sex);
    printf("%d %s %c\n",p->num,p->name,p->sex);
    return 0;
}

②给标准数据类型起别名
typedef int INTERGER;

INTERGER i; //等价于 int i;
INTERGER j;
INTERGER k;

应用场景:i,j,k的数据类型可能经常要变动,从int变为short,或变为float。如果不用别名,则需要一个个修改,比较低效。若用typedef,则只需要修改typedef那一行代码。



(2)C++引用、C++ bool

1.对于普通变量指针变量,都要加引用,才能在子函数中改变main函数中变量的值

#include <cstdio>

void reference(int *&p,int *q){ //引用&必须和变量名紧邻
    p = q;
}

int main(){
    int *p = NULL;
    int i = 1;
    int * q = &i;
    printf("%x %x\n",p,q);

    reference(p,q);
    printf("%x %x\n",p,q);
    return 0;
}

普通变量的引用,等价于(一级)指针
指针变量的引用,等价于二级指针
在这里插入图片描述

2.C++中才开始使用布尔类型,也可以进行加减乘除等数值运算

bool a = true;  //值为1
bool b = false; //值为0



  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员爱德华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值