手把手教你0基础C语言速通

Posted MangataTS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手教你0基础C语言速通相关的知识,希望对你有一定的参考价值。

0基础速通C语言

关于C语言

C 语言是一种通用的高级语言,最初是由丹尼斯·里奇在贝尔实验室为开发 UNIX 操作系统而设计的。C 语言最开始是于 1972 年在 DEC PDP-11 计算机上被首次实现。

在 1978 年,布莱恩·柯林汉(Brian Kernighan)和丹尼斯·里奇(Dennis Ritchie)制作了 C 的第一个公开可用的描述,现在被称为 K&R 标准。

UNIX 操作系统,C编译器,和几乎所有的 UNIX 应用程序都是用 C 语言编写的。由于各种原因,C 语言现在已经成为一种广泛使用的专业语言。

优点

  • 易于学习。
  • 结构化语言。
  • 它产生高效率的程序。
  • 它可以处理底层的活动。
  • 它可以在多种计算机平台上编译。

缺点

  • 因为C语言给编写者太大权限,你可能把握不住

为什么要学习C语言

C 语言最初是用于系统开发工作,特别是组成操作系统的程序。由于 C 语言所产生的代码运行速度与汇编语言编写的代码运行速度几乎一样,所以采用 C 语言作为系统开发语言。并且C语言便于理解,也是很多其他语言的母语言,通过学习C语言,我们能够快速的对编程进行一个了解,并且后续在学习其他语言的时候(语法)会更加容易,所以C语言的学习是有必要的

C语言的IDE

关于IDE:

IDE就是一系列开发工具的组合套装.这就好比台式机,一个台式机的核心部件是主机,有了主机就能独立工作了,但是我们在购买台式机时,往往还要附带上显示器、键盘、鼠标、U盘、摄像头等外围设备,因为只有主机太不方便了,必须有外设才能玩的爽。

集成开发环境也是这个道理,只有编译器不方便,所以还要增加其他的辅助工具。在实际开发中,我一般也是使用集成开发环境,而不是单独地使用编译器。

通俗的称呼

有时候为了称呼方便,或者初学者没有严格区分概念,也会将C语言集成开发环境称作“C语言编译器”或者“C语言编程软件”。这里大家不要认为是一种错误,就把它当做“乡间俗语”吧。

(本教程建立在Windows平台)

初学者的话使用IDE我推荐 Devcpp,这个IDE非常轻便好用

如果你想有一个好看的编辑界面,那么我建议你用VScode 或者Visual Studio

当然我还是推荐使用CLion

程序结构

一个C程序包含以下部分

  • 预处理指令
  • 函数
  • 变量
  • 逻辑代码
  • 注释

举个栗子:

#include<stdio.h>
//上面是头文件,下面是函数
int main()
{
	printf("Hello Mangata!");
    return 0;
}

我们先来剖析一下这个C程序

第一行就是一个预处理指令,也就是我们后面讲的头文件

第二行就是一个行注释,当然我们也可以通过/* */ 来达到区间块注释的效果

第三行开始到第七行就是一个标准的函数,也就是我们后面讲的主函数,要记住一个C程序一定有且只有一个main函数

第五行是一个输出语句,调用了printf库函数

基本语法

头文件

为什么要用头文件呢

因为头文件里面有我们需要的一些基本函数,比如输入的scanf输出的printf,这些函数前人们已经帮我们写好了,不必我们从头开始,所以我们需要引入头文件来使用库函数,可以方便我们的程序编写

怎么引入呢

我们通过#include的一种“指令” ,头文件的引入是预处理语句

#include叫做文件包含命令,用来引入对应的头文件(.h文件)。#include 也是C语言预处理命令的一种。

include格式

#include <xxx.h>
#include "xxx.h"

这就是C语言的两种引入头文件的格式,中间的空格不是必须的(😀),xxx就是你想用的库

常用的头文件

头文件描述
#include<stdio.h>这是一个标准输入输出的头文件
#include<math.h>这是数学函数的头文件
#include<string.h>这是字符串函数的头文件
#include<time.h>这是时间函数的头文件
#include<stdlib.h>实用工具函数的头文件

这些大概就是平常使用较多的头文件了,关于头文件具体的哪些函数,我后面会讲一些,但是也只是常用的,所以更多的东西还需要同学们自己去网上拓展

我这里放一个这些头文件的常用函数连接:https://blog.csdn.net/acm_Mercyking/article/details/50119289感兴趣的同学可以去自己拓展一下噢

标准输入输出函数

scanf

这个是标准输入函数,因为输入对格式要求很严,不过速度是非常快的,下面是scanf的声明

int scanf(const char *format, ...)

scanf前面那个int是返回类型,可以不用管,如果你真想知道的话,其实也很简单,就是成功输入赋值的个数

关于括号里面,自然就是传的参数咯,我们一一来看括号里面的东西,里面的结构是长下面这样的:

scanf("<格式化字符串>", <参量表>);
scanf("%d",&a);

这里的例子我们用的是一个%d,这是什么意思呢?这就表示我们输入的数据的类型是一个int整形的,除了%d我们还有%c%lf%lld等等一系列的标准化输入的格式,具体哪种类型使用哪种符号,那就要根据使用情况看啦,也就是下面我们提到的数据类型。

printf

标准输出其实道理和标准输入是同理的,我们先来看看这个标注输出声明

int printf(const char *format, ...)

同理函数前面有一个int的返回值,如果函数成功执行,那么就会返回输出的字符数,否则就会返回一个负数

我们再来看这个printf的一个结构

printf("<格式化字符串>", <参量表>);
printf("%d\\n",a);

其实和scanf标准输入的结构是大同小异的," " 中间放的就是格式字符形式,参量表就是你想输出的变量,可以是一个也可以是多个,也可以没有,我们直接输出字符类型的东西就不需要变量,但是请注意存在转义的情况,比如'\\',感兴趣的同学可以去百度一下

关于’格式化’

为什么我要单独写一个关于格式化的单点呢,因为这才是格式化输入输出的一个特点或者说是小技巧

我们来看scanf这个标准输入,在括号里面的双引号里面的是我们格式化输入的东西,我举个栗子:

如果有一个题目是这样的,要求你输入一个 Mangata+(数字),然后把数字输出出来,如果学过字符串处理的同学,应该就会觉得那直接一个正则,或者把Mangata去掉,然后输出

这样都可以,甚至来说第二种更加好,但是如果这个数字不大,并且要求你对这个数字做一些计算上的操作呢?这里我们就能用到scanf的格式化输入了:

#include<stdio.h>
int main() {
    int a;
	scanf("Mangata%d",&a);
    printf("%d\\n",a);
    return 0;
}

再比如说如果我们需要输出一个时间例如这样08:01,有这种前置零,通过字符串的输出要麻烦得多,我们可以直接通过格式化输出:

#include<stdio.h>
int main() {
    printf("%02d:%02d",8,1);
    return 0;
}

由于本书只是速通教程,所以我不在做过多的列举,更多的东西还是要靠同学们自己学习啦

数据类型

整数类型

类型存储大小值的范围
char(默认就是signed)1字节-128~127或者0 ~ 255
unsigned char1 字节0 ~ 255
signed char1 字节-128~127
int2 或 4 字节-32768 到 32767 或 -2147483648 到 2147483647
unsigned int2 或 4 字节0 ~ 65535 或 0 ~ 4294967295
short2 字节-32768 ~ 32767
unsigned short2 字节0 ~ 65535
long4 字节-2147483648 ~ 2147483647
unsigned long4 字节0 ~ 4294967295
long long8字节-9223372036854775808~9223372036854775807

浮点类型

类型存储大小值范围精度
float4 字节1.2E-38 ~ 3.4E+386 位有效位
double8 字节2.3E-308 ~ 1.7E+30815 位有效位
long double16 字节3.4E-4932 ~ 1.1E+493219 位有效位

我们可以通过一个关键字sizeof来获得一个类型或者说后面的变量等等的大小

举个栗子:

#include<stdio.h>
int main()
{
	printf("%d\\n",sizeof(char));
	printf("%d\\n",sizeof(int));
	printf("%d\\n",sizeof(long));
	printf("%d\\n",sizeof(double));
	return 0;
}
/*
输出结果
1
4
4
8
*/

这里我们使用了printf库函数和sizeof 关键字 ,前者是打印函数,后者是计算对象内存大小的关键字

我这里列举一下常用的几个格式化符号:

类型输入格式化输出格式化
int%d%d
char%c%c
float%f%f
double%lf%lf 或者%f
long long%lld%lld
字符串%s%s

void 类型

  • void类型可以拿来修饰函数,函数也就不需要一个返回值,我们之后在讲
  • void类型可以拿来修饰指针,这种指针我们可以通过强制转换来实现转换到任意数据类型

我这里放一张C语言包含的数据类型的图,便于大家理解记忆:

关于其他没讲的类型在下面我会一一讲解的,不用担心啦!

变量

命名规则

  1. 变量名的开头必须是字母或下划线,不能是数字。实际编程中最常用的是以字母开头,而以下划线开头的变量名是系统专用的。
  2. 变量名中的字母是区分大小写的。比如 a 和 A 是不同的变量名,num 和 Num 也是不同的变量名。
  3. 变量名绝对不可以是C语言关键字,这一点一定要记住!(关于关键字可以看下面的关键字表格,记住常用的就行了)
  4. 变量名中不能有空格。这个可以这样理解:因为上面我们说过,变量名是字母、数字、下划线的组合,没有空格这一项。

关于编程命名规范您可以参考这个博客:https://www.cnblogs.com/wfwenchao/p/5209197.html 当然你也可以用拼音

举个例子:

int temp;//定义了一个int数据类型的变量,名叫temp
char str;//定义了一个char数据类型的数据变量,名叫str
double num;//定义了一个double数据类型的数据变量,名叫num

变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储。变量定义指定一个数据类型,并包含了该类型的一个或多个变量的列表

比如你要定义四个变量,这四个变量都是int类型的,你就可以将他们写在一起,他们之间用,隔开末尾以;结束

int a,b,c,d;
int e = 10;

上述的例子都是定义,那么什么是声明呢?,我们来看两个例子

  1. 使用关键字修饰

    extern int a;//表示的其他文件以及定义过a这个变量,我们这里直接拿来使用
    
  2. 函数声明

    void f(int a);//没有返回类型的函数声明
    

声明和定义最重要的区别就是:定义创建了对象并为此对象分配了内存,而声明并未分配内存

强制转换

这个东西不难,讲起来也很简单,但是还算比较重要所以单独讲一下:

其实在程序中字节长度较高的变量对字节长度较低的变量有一定的兼容性的,但是低的对高的可不是了,因为很可能造成数据的溢出,或者是不同数据类型之间float和int,我们得到的是一个float值,但是我们想保存一个int值,那么这个时候就需要用到强制转换,我举个栗子:

#include <stdio.h>

int main ()
{
    float a = 1.234;
    int b = (int)a;
    printf("a = %f\\tb = %d\\n",a,b);
    return 0;
}

这个栗子很简单,我们下面到内存控制的时候使用malloc也是需要强制转换的。

左值右值

  1. **左值(lvalue):**指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。

  2. **右值(rvalue):**术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。

    ​ 变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。

举个栗子:

int a = 20;

在这个语句中a就是一个指向内存的变量也就是左值,20就是存储在内存某个地址的数值,有了这个概念就能理解下面的这个语句了

int a = 0;
a = a + 1;
printf("%d\\n",a);

​ 这里我们声明了一个int类型的变量a,然后在声明的时候给它定义了初值为0,然后第二个语句我们让a = a + 1,注意此时左边的a是一个变量,也就是上面我们说的左值,右边是a的值加上1,也就是右边的整体构成了一个右值,其实对于左值右值不必纠结太多,这个语句实现的效果就是a自增1,也就是说如果我们运行上面的代码的话会给我们打印一个1,这个1就是通过运算后a的值

常量

数值常量

​ 注意此数值常量非彼数值,我此处所说的常量是单纯的一个值,没有名字那种,比如 3 他是一个整形常量 0.3 是一个浮点常量再比如说 'c' 它是一个字符常量 "ccc" 它是一个字符串常量,这种就是数值常量了,一般只是用来初始化,而且这种数值常量不易维护,比如说我们在一个程序的很多地方都用了同一个常量那么如果你想修改这个常量的值,稍微笨点的同学可能会一个一个的替换,有的同学可能想到了直接文件全局替换,但是这样做是有风险的,比如我的常量为1,我想把它替换为2,但是我可能有一些其他的常量是1开头的,比如12,那么就会造成数据紊乱,甚至有些变量里面也包含了1那么可能会导致编译出错,再退一步讲,编译没问题,程序的运行结果也不会和预期一样

define常量

说的浅显易懂点,define就是一个文本替换,格式如下

#define xxx yyy //达到的效果就是下面的程序所有用到的xxx都会被替换为yyy
#define x 10//这里就表示x是一个常量,它表示的value是10
  • 当然你也可以通过#define 来实现宏函数eg:
#define max(a,b) (a>b?a:b)
  • #define 是一个预处理指令,所以在书写代码的时候请将#define放在程序的顶部
  • #define后面没有分号 !
  • 请尽量少使用#define,因为#define相当于一种文本替换,在代码行数较少发生的问题较少,但是在代码量较大的时候这种常量就会存在一种潜在的危险->冲突(如果报错还好,没有报错那就难受了,大概率程序的运行结果和预期不同)

const关键字

const是constant的缩写,是一个修饰词用于修饰一些变量,通过const修饰符的变量就变成了一个不可变的变量

eg:

const int a = 10;//这就是定义了一个常量,名叫a,值为10
  • 通过const修饰后不能更改值
  • 我们可以通过指针修改const定义的常量

通过const修改指针

大体上分为三类

  • 一、const修饰指针指向的内容,则内容不可变,但是能通过一个同数据类型的指针来改变值
const int *a = 10;// 这样的内容不可变
  • 二、const修饰指针,那么指针的值不可变
int a = 10;
int* const b = &a;

此时的const修饰的是指针,那么指针指向的地址不可变,也就是此时的b指向的地址不能边变,但是*b可变,请同学们要想明白

  • 三、const修饰指针并且修饰的指针指向的内容,那么指针指向的地址和值都不可变
int a = 10;
const int * const b = &a;

此时的b指向的地址不能更改,地址上的值*b也不能更改

存储类

auto

auto是一种默认的存储类,作用范围是局部,一般不用管,生命周期和作用域都是局部的

static

这是一个静态的存储类,它有点特殊的是他的生命周期是整个程序从开始到结束,但是它的作用域只是局部

举个栗子:

#include<stdio.h>
void f() {
    static int a = 0;
    a++;
    printf("%d\\n",a);
}
int main()
{
    for(int i = 0;i < 5; ++i) {
        f();
    }
  	return 0;
}

你觉得会输出什么呢?对没错,输出

1
2
3
4
5    

换句话说我们在函数里面定义的是一个静态变量,它的声明周期就是从定义它到程序结束,所以这里输出的内容也就不难理解了

register

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。

再来用一个通俗的解释:

此段出自**《C语言深度剖析》**,很棒的一本书,建议有一定基础再看

extern

extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。

当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。

总结一句话extern就是为了在多文件编程的时候直接取另外一个文件的变量

关于extern,其实初学者不必过于关系,后面做项目开发的时候查找查找用法即可,我们这里也就不过多展开了

运算符

算数运算

运算符效果例子
+对两个数进行相加a = 1 + 2 => a= 3
-对两个数进行相减a = 2 - 1 => a = 1
*对两个数进行相乘a = 2 * 2 => a = 4
/对两个数进行相除a = 10 / 2 => a = 5
%a对b取模a = 10 % 3 = > a = 1
++变量自增1a = 0; ++a => a = 1
--变量自减1a = 0 ; --a => a = -1

关系运算

运算符描述实例
==检查两个操作数的值是否相等,如果相等则条件为真。(A == B) 为假。
!=检查两个操作数的值是否相等,如果不相等则条件为真。(A != B) 为真。
>检查左操作数的值是否大于右操作数的值,如果是则条件为真。(A > B) 为假。
<检查左操作数的值是否小于右操作数的值,如果是则条件为真。(A < B) 为真。
>=检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。(A >= B) 为假。
<=检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。(A <= B) 为真。

逻辑运算

运算符描述实例
&&称为逻辑与运算符。如果两个操作数都非零,则条件为真。(A && B) 为假。
||称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。(A || B) 为真。
!称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。!(A && B) 为真。

位运算

以下的运算都是将数转化为二进制在进行运算,然后再转化为相应的进制

运算符描述实例
&与运算 全一则一,否则为零3 & 6 = 2
|或运算 有一则一,否则为零3 & 6 = 7
^异或运算 不同为一,相同为零3 ^ 4 = 7
~取反运算 零一颠倒~4 = 3
<<左移运算 效果等价十进制中乘 2 n 2^n 2n5<<1 = 10
>>右移运算 效果等价十进制中除 2 n 2^n 2n5>>1 = 2

其他

运算符描述实例
sizeof()返回变量的大小。sizeof(a)将返回 4,其中 a 是整数。
&返回变量的地址。&a; 将给出变量的实际地址。
*指向一个变量。*a; 将指向一个变量。
? :条件表达式如果条件为真 ? 则值为 X : 否则值为 Y

运算符优先级表格

类别运算符结合性
后缀() [] -> . ++ - -从左到右
一元+ - ! ~ ++ - - (type)* & sizeof从右到左
乘除* / %从左到右
加减+ -从左到右
移位<< >>从左到右
关系< <= > >=从左到右
相等== !=从左到右
位与 AND&从左到右
位异或 XOR^从左到右
位或 OR|从左到右
逻辑与 AND&&从左到右
逻辑或 OR||从左到右
条件?:从右到左
赋值= += -= *= /= %=>>= <<= &= ^= \\|=从右到左
逗号,从左到右

条件判断

if判断

#include<stdio.h>
int main()
{
    if(a > 10) { // 这里就是一个if判断
        printf("YES");
    }
    else  {
        printf("NO");
    }
    return 0;
}

if判断很简单,逻辑值为真那么就执行if下方的语句,(但是注意如果没有大括号的话那么只执行下面一行的内容),我们通过else就可以处理逻辑为假的情况,那么如果我们要判断很多种情况呢,这时我们就可以使用一下结构

if(xxx) {
    
}
else if(xxx) {
    
}
else {
    
}

注意上面的代码,这里我们能发现多了个else if 的判断,聪明的你们应该猜到了,这个else if就是为了在判断的情况不止两种的时候使用的,当然你只用if …… else的结构能不能实现呢?按理来说是能的,不过这样的逻辑结构设计的就会稍稍有一点复杂或者说是繁琐。

三目运算符

action ? ans1:ans2;

大家不要觉得这个很难,其实就是一个简化的

if(action) {
    ans1;
}
else {
    ans2;
}

action表示的是逻辑判断的值,ans1就表示判断为真所做的操作,ans2就表示判断为假所作的操作

循环

在讲下面几个循环的方式之前,我先来说说什么是循环,循环就是多次执行同一个流程,为什么是流程呢,因为可能每次操作得到的值不一样,或者说里面有一些嵌套判断什么的,最终每次的操作可能不太一样,比如说我有一个循环,我外面定义了一个变量他的初始值是1,我在循环里面做一件事情如果这个数是奇数那么我就让它加一,否则,让它不变,我们将这个循环四次,最终这个变量的结果就是2,我们可以先看看这个代码的for格式

#include<stdio.h>
int main()
{
	int a = 1;
    for(int i = 0;i < 4; ++i) {
        if(a % 2 == 1) a++;
        else a = a; //注意这里的else其实是可以删掉的
    }
    printf("%d\\n",a);
	return 0;
}

for循环

for循环由四部分组成,初始化条件判断迭代更新逻辑操作语句

我们先来看一下for的结构

for(初始化语句;条件判断;迭代更新) {
    逻辑操作语句
}
//eg:
for(int i = 0;i < 10; ++i) {
    //pass
}

注意这里的初始化、迭代更新、逻辑操作都可以省略,直接不写即可,但是条件判断一定要写上,并且两个;不能省略

while循环

while循环看起来就要简单一点(结构上),只有条件判断逻辑操作

我们来看while的结构

while(条件判断) {
	逻辑操作语句
}
//eg:
int i = 0;
while(++i < 10) {
    //pass
}

当条件不满足的时候就会跳出循环,也就是条件判断为真就继续循环,条件判断为假就跳出循环

do……while循环

do……while在结构上也只有条件判断逻辑操作

我们来看do……while结构

do{
    逻辑操作语句
}while(条件判断)

do……whilewhile的区别就在于while第一次判断如果不满足那么就不进入循环,而**do……while会先进行一次操作**,然后再判断是否继续循环

举个栗子:

当你在一个文件中读取数据的时候,什么时候才算读完了呢?一般的话文件会有一个结束符(EOF),那么我们使用do……while结构,先读,读完后判断是否是文件结束符,如果不是的话那么就继续循环。

循环控制

  • 嵌套循环

我们上面的循环都只是一层循环,假如一层循环是遍历一行数据,那么如果我们想遍历一个 n × n n\\times n n×n的矩阵,那么我们就可以通过二层循环来实现,例如

#include<stdio.h>
int main()
{
    int n;
	for(int i = 0;i < n; ++i) {
        for(int j = 0;j < n; ++i) {
            //op
        }
    }
	return 0;
}

这就是一个简单的二层循环

  • 死循环

当我们的条件判断设计不合理或者说在for循环中没有设置判断条件,那么就会导致死循环,有的时候我们需要死循环,有的时候我们不需要,所以是否需要要根据你的需求来看,举个简单的栗子,这个操作系统就可以简单的看成一个死循环,但是在大部分情况下我们应该是要避免出现死循环的,出现了死循环一定要好好检查循环处的代码

  • break 和 continue

两者都是跳过循环,但是break是跳过整个剩下的循环,而continue只是跳过当前这次循环,也就是说后面如果还有循环操作,那么就继续执行

  • goto

goto语句在很多课程或者是一些学习的资料里面都很少,很多程序员也觉得这个是个辣鸡玩意,但是我觉得goto只要用对了地方,那么就是一个非常有用的工具,使用goto的时候请务必不要向上跳,如果你把握不住的话那么你就不要使用这个语句,在我看来goto一般使用在多重循环里面想直接跳出多重循环,而不是写多个break。

eg:

#include<stdio.h>
int main()
{
	for(int i = 0;i < 10; ++i) {
        for(int j = 0;j < 10; ++j) {
            for(int k = 0;k < 10; ++k) {
                if(i + j + k == 12) {
                    goto out以上是关于手把手教你0基础C语言速通的主要内容,如果未能解决你的问题,请参考以下文章

手把手教你7天学会C语言!(第0天二进制数)

零基础手把手教你学Python为什么要学Python?——人生苦短,只用Python

手把手教你c语言队列实现代码,通俗易懂超详细!

一学就会,手把手教你用Go语言调用智能合约

手把手教你C语言五子棋的实现(双玩家对战)

C语言中手把手教你动态内存分配