Skip to content

C语言前言篇

双鱼林程序设计网:http://www.shuangyulin.com/

如何运行编写好的程序

  • 方式1:

  • 点击小榔头将"源代码"编译成"可执行文件"

  • 找到编译后的源代码, 打开终端(CMD)运行可执行文件

  • 方式2

  • 直接点击Qt开发工具运行按钮


main函数注意点及其它写法

  • C语言中,每条完整的语句后面都必须以分号结尾
c
int main(){
    printf("hello world\n") // 如果没有分号编译时会报错
    return 0;
}
c
int main(){
    // 如果没有分号,多条语句合并到一行时, 系统不知道从什么地方到什么地方是一条完整语句
    printf("hello world\n") return 0;
}
  • C语言中除了注释和双引号引起来的地方以外都不能出现中文
c
int main(){
    printf("hello world\n"); // 这里的分号如果是中文的分号就会报错
    return 0;
}
  • 一个C语言程序只能有一个main函数
c
int main(){
    return 0;
}
int main(){ // 编译时会报错, 重复定义
    return 0;
}
  • 一个C语言程序不能没有main函数
c
int call(){ // 编译时报错, 因为只有call函数, 没有main函数
    return 0;
}
c
int mian(){ // 编译时报错, 因为main函数的名称写错了,还是相当于没有main函数
    return 0;
}
  • main函数前面的int可以不写或者换成void
c
#include <stdio.h>
main(){ // 不会报错
    printf("hello world\n");
    return 0;
}
c
#include <stdio.h>
void main(){  // 不会报错
    printf("hello world\n");
    return 0;
}
  • main函数中的return 0可以不写
c
int main(){ // 不会报错
    printf("hello world\n");
}
  • 多种写法不报错的原因
    • C语言最早的时候只是一种规范和标准(例如C89, C11等)
    • 标准的推行需要各大厂商的支持和实施
    • 而在支持的实施的时候由于各大厂商利益、理解等问题,导致了实施的标准不同,发生了变化
      • Turbo C
      • Visual C(VC)
      • GNU C(GCC)
    • 所以大家才会看到不同的书上书写的格式有所不同, 有的返回int,有的返回void,有的甚至没有返回值
    • 所以大家只需要记住最标准的写法即可, no zuo no die
c
#include <stdio.h>
int main(){
    printf("hello world\n");
    return 0;
}

Tips: 语法错误:编译器会直接报错 逻辑错误:没有语法错误,只不过运行结果不正确

C语言与其他热门编程语言对比

  • C语言
c
#include<stdio.h>
int main() {
    printf("南哥带你装B带你飞");
    return 0;
}
  • C++语言
c
#include<iostream>
using namespace std;
int main() {
    cout << "南哥带你装B带你飞" << endl;
    return 0;
}
  • OC语言
c
#import <Foundation/Foundation.h>
int main() {
    NSLog(@"南哥带你装B带你飞");
    return 0;
}
  • Java语言
java
class Test
{
    public static viod main()
    {
        system.out.println("南哥带你装B带你飞");
    }
}
  • Go语言
go
package main
import  "fmt" //引入fmt库
func main() {
    fmt.Println("南哥带你装B带你飞")
}

什么是C语言

C语言是一种用于和计算机交流的高级语言, 它既具有高级语言的特点,又具有汇编语言的特点

  • 非常接近自然语言
  • 程序的执行效率非常高

C语言是所有编程语言中的经典,很多高级语言都是从C语言中衍生出来的,例如:C++、C#、Object-C、Java、Go等等

C语言是所有编程语言中的经典,很多著名的系统软件也是C语言编写的

  • 几乎所有的操作系统都是用C语言编写的
  • 几乎所有的计算机底层软件都是用C语言编写的
  • 几乎所有的编辑器都是C语言编写的

C语言发展历史

C语言历史

  • 最早的高级语言:FORTRAN-->ALGOL-->CPL-->BCPL-->C-->C++等

“初,世间无语言,仅电路与连线。及大牛出,天地开,始有 FORTRAN、 LISP、ALGOL 随之, 乃有万种语”

  • 1963年英国剑桥大学推出了CPL(Combined Programming Langurage)语言。 CPL语言在ALGOL 60的基础上接近硬件一些,但规模比较大,难以实现
  • 1967年英国剑桥大学的 Matin Richards(理查兹)对CPL语言做了简化,推出了 BCPL (Base Combined Programming Langurage)语言
  • 1970年美国贝尔实验室的 Ken Thompson(肯·汤普逊) 以 BCPL 语言为基础,又作了进一步的简化,设计出了很简单的而且很接近硬件的 B 语言(取BCPL的第一个字母),并用B语言写出了第一个 UNIX 操作系统。但B语言过于简单,功能有限
  • 1972年至1973年间,贝尔实验室的 Dennis.Ritchie(丹尼斯·里奇) 在 B语言的基础上设计出了C语言(取BCPL的第二个字母)。C语言即保持 BCPL 语言和B语言的优点(精练、接近硬件),又克服了他们的缺点(过于简单,数据无类型等)

C语言现状

为什么要学习C语言

为什么要学习C语言?

  • 40多年经久不衰
  • 了解操作系统、编译原理、数据结构与算法等知识的最佳语言
  • 了解其它语言底层实现原理必备语言
  • 基础语法与其它高级语言类似,学会C语言之后再学习其它语言事半功倍,且知根知底

当你想了解底层原理时,你才会发现后悔当初没有学习C语言 当你想学习一门新的语言时, 你才会发现后悔当初没有学习C语言 当你使用一些高级框架、甚至系统框架时发现提供的 API 都是C语言编写的, 你才发现后悔当初没有学习 C 语言 学好数理化,走遍天下都不拍 学好C语言,再多语言都不怕

如何学好C语言

如何学好C语言

学习本套开源项目之前学习本套开源项目中学习本套开源项目之后
  • 如何达到这样的效果

  • 请往下看

  • 如何达到这样的效果,跟着南哥干就好了

C语言标准

C语言标准

  • 1983年美国国家标准局(American National Standards Institute,简称ANSI)成立了一个委员会,开始制定C语言标准的工作
  • 1989年C语言标准被批准,这个版本的C语言标准通常被称为ANSI C(C89)
  • 1999年,国际标准化组织ISO又对C语言标准进行修订,在基本保留原C语言特征的基础上,针对应该的需要,增加了一些功能,命名为C99
  • 2011年12月,ANSI采纳了ISO/IEC 9899:2011标准。这个标准通常即C11,它是C程序语言的现行标准

C语言基础

流程控制

流程控制基本概念

  • 默认情况下程序运行后,系统会按书写顺序从上至下依次执行程序中的每一行代码。但是这并不能满足我们所有的开发需求, 为了方便我们控制程序的运行流程,C语言提供3种流程控制结构,不同的流程控制结构可以实现不同的运行流程。
  • 这3种流程结构分别是顺序结构、选择结构、循环结构
  • 顺序结构:
  • 按书写顺序从上至下依次执行
  • 选择结构
  • 对给定的条件进行判断,再根据判断结果来决定执行代码
  • 循环结构
  • 在给定条件成立的情况下,反复执行某一段代码

选择结构

什么是计算机程序?

  • 计算机程序是为了告诉计算机"做某件事或解决某个问题"而用"计算机语言编写的命令集合(语句)

  • 只要让计算机执行这个程序,计算机就会自动地、有条不紊地进行工作,计算机的一切操作都是由程序控制的,离开程序,计算机将一事无成

  • 现实生活中你如何告诉别人如何做某件事或者解决某个问题?

    • 通过人能听懂的语言: 张三你去楼下帮我买一包烟, 然后顺便到快递箱把我的快递也带上来
    • 其实我们通过人能听懂的语言告诉别人做某件事就是在发送一条条的指令
    • 计算机中也一样, 我们可以通过计算机语言告诉计算机我们想做什么, 每做一件事情就是一条指令, 一条或多条指令的集合我们就称之为一个计算机程序

循环结构

循环结构

  • C语言中提供了三大循环结构, 分别是while、dowhile和for
  • 循环结构是程序中一种很重要的结构。
    • 其特点是,在给定条件成立时,反复执行某程序段, 直到条件不成立为止。
    • 给定的条件称为"循环条件",反复执行的程序段称为"循环体"

循环结构while

  • 格式:
c
while (  循环控制条件 ) {
    循环体中的语句;
    能够让循环结束的语句;
    ....
}
  • 构成循环结构的几个条件

    • 循环控制条件
      • 循环退出的主要依据,来控制循环到底什么时候退出
    • 循环体
      • 循环的过程中重复执行的代码段
    • 能够让循环结束的语句(递增、递减、真、假等)
      • 能够让循环条件为假的依据,否则退出循环
  • 示例:

c
int count = 0;
while (count < 3) { // 循环控制条件
    printf("发射子弹~哔哔哔哔\n"); // 需要反复执行的语句
    count++; // 能够让循环结束的语句
}
  • while循环执行流程
    • 首先会判定"循环控制条件"是否为真, 如果为假直接跳到循环语句后面
    • 如果"循环控制条件"为真, 执行一次循环体, 然后再次判断"循环控制条件"是否为真, 为真继续执行循环体,为假跳出循环
    • 重复以上操作, 直到"循环控制条件"为假为止
c
#include <stdio.h>
int main(){
    int count = 4;
    // 1.判断循环控制条件是否为真,此时为假所以跳过循环语句
    while (count < 3) { 
        printf("发射子弹~哔哔哔哔\n"); 
        count++; 
    }
    // 2.执行循环语句后面的代码, 打印"循环执行完毕"
    printf("循环执行完毕\n");
}
c
#include <stdio.h>
int main(){
    int count = 0;
    // 1.判断循环控制条件是否为真,此时0 < 3为真
    // 4.再次判断循环控制条件是否为真,此时1 < 3为真
    // 7.再次判断循环控制条件是否为真,此时2 < 3为真
    // 10.再次判断循环控制条件是否为真,此时3 < 3为假, 跳过循环语句
    while (count < 3) { 
        // 2.执行循环体中的代码, 打印"发子弹"
        // 5.执行循环体中的代码, 打印"发子弹"
        // 8.执行循环体中的代码, 打印"发子弹"
        printf("发射子弹~哔哔哔哔\n"); 
        // 3.执行"能够让循环结束的语句" count = 1
        // 6.执行"能够让循环结束的语句" count = 2
        // 9.执行"能够让循环结束的语句" count = 3
        count++; 
    }
    // 11.执行循环语句后面的代码, 打印"循环执行完毕"
    printf("循环执行完毕\n");
}

  • while循环注意点
    • 任何数值都有真假性
c
#include <stdio.h>
int main(){
    while (1) { // 死循环
         printf("发射子弹~哔哔哔哔\n");
         // 没有能够让循环结束的语句
    }
}
  • 当while后面只有一条语句时,while后面的大括号可以省略
c
#include <stdio.h>
int main(){
    while (1)  // 死循环
         printf("发射子弹~哔哔哔哔\n");
         // 没有能够让循环结束的语句
}
  • 如果while省略了大括号, 那么后面不能定义变量
c
#include <stdio.h>
int main(){
    while (1)  // 死循环
         int num = 10; // 报错
         // 没有能够让循环结束的语句
}
  • C语言中分号(;)也是一条语句, 称之为空语句
c
#include <stdio.h>
int main(){
    int count = 0;
    while (count < 3);{ // 死循环
       printf("发射子弹~哔哔哔哔\n"); 
       count++; 
    }
}
  • 最简单的死循环
c
// 死循环一般在操作系统级别的应用程序会比较多, 日常开发中很少用
while (1);

  • while练习
    • 计算1 + 2 + 3 + ...n的和
    • 获取1~100之间 7的倍数的个数

循环结构do while

  • 格式:
c
do {
    循环体中的语句;
    能够让循环结束的语句;
    ....
} while (循环控制条件 );
  • 示例
c
int count = 0;
do {
   printf("发射子弹~哔哔哔哔\n");
   count++;
}while(count < 10);
  • do-while循环执行流程

    • 首先不管while中的条件是否成立, 都会执行一次"循环体"
    • 执行完一次循环体,接着再次判断while中的条件是否为真, 为真继续执行循环体,为假跳出循环
    • 重复以上操作, 直到"循环控制条件"为假为止
  • 应用场景

    • 口令校验
c
#include<stdio.h>
int main()
{
    int num = -1;
    do{
        printf("请输入密码,验证您的身份\n");
        scanf("%d", &num);
    }while(123456 != num);
    printf("主人,您终于回来了\n");
}
  • while和dowhile应用场景
    • 绝大多数情况下while和dowhile可以互换, 所以能用while就用while
    • 无论如何都需要先执行一次循环体的情况, 才使用dowhile
    • do while 曾一度提议废除,但是他在输入性检查方面还是有点用的

循环结构for

  • 格式:
c
for(初始化表达式;循环条件表达式;循环后的操作表达式) {
    循环体中的语句;
}
  • 示例
c
for(int i = 0; i < 10; i++){
    printf("发射子弹~哔哔哔哔\n");
}
  • for循环执行流程

    • 首先执行"初始化表达式",而且在整个循环过程中,只会执行一次初始化表达式
    • 接着判断"循环条件表达式"是否为真,为真执行循环体中的语句
    • 循环体执行完毕后,接下来会执行"循环后的操作表达式",然后再次判断条件是否为真,为真继续执行循环体,为假跳出循环
    • 重复上述过程,直到条件不成立就结束for循环
  • for循环注意点:

    • 和while一模一样
    • 最简单的死循环for(;;);
  • for和while应用场景

    • while能做的for都能做, 所以企业开发中能用for就用for, 因为for更为灵活
    • 而且对比while来说for更节约内存空间
c
int count = 0; // 初始化表达式
while (count < 10) { // 条件表达式
      printf("发射子弹~哔哔哔哔 %i\n", count);
      count++; // 循环后增量表达式
}
// 如果初始化表达式的值, 需要在循环之后使用, 那么就用while
printf("count = %i\n", count);
c
// 注意: 在for循环初始化表达式中定义的变量, 只能在for循环后面的{}中访问
// 所以: 如果初始化表达式的值, 不需要在循环之后使用, 那么就用for
// 因为如果初始化表达式的值, 在循环之后就不需要使用了 , 那么用while会导致性能问题
for (int count = 0; count < 10; count++) {
     printf("发射子弹~哔哔哔哔 %i\n", count);
}
//     printf("count = %i\n", count);
c
// 如果需要使用初始化表达式的值, 也可以将初始化表达式写到外面
int count = 0;
for (; count < 10; count++) {
     printf("发射子弹~哔哔哔哔\n", count);
}
printf("count = %i\n", count);

最后,如果你有任何疑问,请关注微信公众号 代码情缘 加我微信。

四大跳转

  • C语言中提供了四大跳转语句, 分别是return、break、continue、goto
  • break:
  • 立即跳出switch语句或循环
  • 应用场景:
  • switch
  • 循环结构
  • break注意点:
    • break离开应用范围,存在是没有意义的
c
if(1) {
  break; // 会报错
}
  • 在多层循环中,一个break语句只向外跳一层
c
while(1) {
  while(2) {
    break;// 只对while2有效, 不会影响while1
  }
  printf("while1循环体\n");
}
  • break下面不可以有语句,因为执行不到
c
while(2){
  break;
  printf("打我啊!");// 执行不到
}

  • continue
  • 结束本轮循环,进入下一轮循环
  • 应用场景:
  • 循环结构
  • continue注意点:
  • continue离开应用范围,存在是没有意义的
c
if(1) {
  continue; // 会报错
}

  • goto
  • 这是一个不太值得探讨的话题,goto 会破坏结构化程序设计流程,它将使程序层次不清,且不易读,所以慎用
  • goto 语句,仅能在本函数内实现跳转,不能实现跨函数跳转(短跳转)。但是他在跳出多重循环的时候效率还是蛮高的
c
#include <stdio.h>
int main(){
    int num = 0;
// loop:是定义的标记
loop:if(num < 10){
        printf("num = %d\n", num);
        num++;
        // goto loop代表跳转到标记的位置
        goto loop;
    }
}
c
#include <stdio.h>
int main(){
    while (1) {
        while(2){
            goto lnj;
        }
    }
    lnj:printf("跳过了所有循环");
}

  • return

    • 结束当前函数,将结果返回给调用者
    • 不着急, 放一放,学到函数我们再回头来看它

最后,如果有任何疑问,欢迎加微信 leader_fengy 拉你进学习交流群。

循环的嵌套

循环的嵌套

  • 循环结构的循环体中存在其他的循环结构,我们称之为循环嵌套
    • 注意: 一般循环嵌套不超过三层
    • 外循环执行的次数 * 内循环执行的次数就是内循环总共执行的次数
  • 格式:
c
while(条件表达式) {
    while循环结构 or dowhile循环结构 or for循环结构
}
for(初始化表达式;循环条件表达式;循环后的操作表达式) {
    while循环结构 or dowhile循环结构 or for循环结构
}
do {
     while循环结构 or dowhile循环结构 or for循环结构
} while (循环控制条件 );
  • 循环优化
    • 在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数
c
for (row=0; row<100; row++) {
  // 低效率:长循环在最外层
  for ( col=0; col<5; col++ ) {
    sum = sum + a[row][col];
  }
}
for (col=0; col<5; col++ ) {
  // 高效率:长循环在最内层
  for (row=0; row<100; row++) {
    sum = sum + a[row][col];
  }
}
  • 练习
    • 打印好友列表
c
好友列表1
    好友1
    好友2
好友列表2
    好友1
    好友2
好友列表3
    好友1
    好友2
for (int i = 0; i < 4; i++) {
    printf("好友列表%d\n", i+1);
    for (int j = 0; j < 4; j++) {
        printf("    角色%d\n", j);
    }
}

C语言程序组成

  • 手机有很多功能, "开机","关机","打电话","发短信","拍照"等等

  • 手机中的每一个功能就相当于C语言程序中的一个程序段(函数)

  • 众多功能中总有一个会被先执行,不可能多个功能一起执行

  • 想使用手机必须先执行手机的开机功能

  • 所以C语言程序也一样,由众多功能、众多程序段组成, 众多C语言程序段中总有一个会被先执行, 这个先执行的程序段我们称之为"主函数"

  • 一个C语言程序由多个"函数"构成,每个函数有自己的功能

  • 一个程序有且只有一个主函数

  • 如果一个程序没有主函数,则这个程序不具备运行能力

  • 程序运行时系统会自动调用主函数,而其它函数需要开发者手动调用

  • 主函数有固定书写的格式和范写

函数

函数基本概念

  • C源程序是由函数组成的
  • 例如: 我们前面学习的课程当中,通过main函数+scanf函数+printf函数+逻辑代码就可以组成一个C语言程序
  • C语言不仅提供了极为丰富的库函数, 还允许用户建立自己定义的函数。用户可把自己的算法编写成一个个相对独立的函数,然后再需要的时候调用它
  • 例如:你用C语言编写了一个MP3播放器程序,那么它的程序结构如下图所示
  • 可以说C程序的全部工作都是由各式各样的函数完成的,所以也把C语言称为函数式语言

函数的分类

  • 在C语言中可从不同的角度对函数分类
  • 从函数定义的角度看,函数可分为库函数和用户定义函数两种
  • 库函数: 由C语言系统提供,用户无须定义,也不必在程序中作类型说明,只需在程序前包含有该函数原型的头文件即可在程序中直接调用。在前面各章的例题中反复用到printf、scanf、getchar、putchar等函数均属此类
  • ***用户定义函数:***由用户按需编写的函数。对于用户自定义函数,不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用
  • 从函数执行结果的角度来看, 函数可分为有返回值函数和无返回值函数两种
  • 有返回值函数: 此类函数被调用执行完后将向调用者返回一个执行结果,称为函数返回值。(必须指定返回值类型和使用return关键字返回对应数据)
  • 无返回值函数: 此类函数用于完成某项特定的处理任务,执行完成后不向调用者返回函数值。(返回值类型为void, 不用使用return关键字返回对应数据)
  • 从主调函数和被调函数之间数据传送的角度看,又可分为无参函数和有参函数两种
  • 无参函数: 在函数定义及函数说明及函数调用中均不带参数。主调函数和被调函数之间不进行参数传送。
  • 有参函数: 在函数定义及函数说明时都有参数,称为形式参数(简称为形参)。在函数调用时也必须给出参数,称为实际参数(简称为实参)

函数的定义

  • 定义函数的目的
  • 将一个常用的功能封装起来,方便以后调用
  • 自定义函数的书写格式
c
返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,…) {
    函数体;
    返回值;
}
  • 示例
c
int main(){
    printf("hello world\n");
    retrun 0;
}
  • 定义函数的步骤
  • 函数名:函数叫什么名字
  • 函数体:函数是干啥的,里面包含了什么代码
  • 返回值类型: 函数执行完毕返回什么和调用者

  • 无参无返回值函数定义
  • 没有返回值时return可以省略

  • 格式:

    c
    void 函数名() {
        函数体;
    }
  • 示例:

    c
    // 1.没有返回值/没有形参
    // 如果一个函数不需要返回任何数据给调用者, 那么返回值类型就是void
    void printRose() {
        printf(" {@}\n");
        printf("  |\n");
        printf(" \\|/\n"); // 注意: \是一个特殊的符号(转意字符), 想输出\必须写两个斜线
        printf("  |\n");
      // 如果函数不需要返回数据给调用者, 那么函数中的return可以不写
    }

  • 无参有返回值函数定义
  • 格式:

    c
    返回值类型 函数名() {
        函数体;
        return 值;
    }
  • 示例:

    c
    int getMax() {
        printf("请输入两个整数, 以逗号隔开, 以回车结束\n");
        int number1, number2;
        scanf("%i,%i", &number1, &number2);
        int max = number1 > number2 ? number1 : number2;
        return max;
    }

  • 有参无返回值函数定义
  • 形式参数表列表的格式: 类型 变量名,类型 变量2,......

  • 格式:

    c
    void 函数名(参数类型 形式参数1,参数类型 形式参数2,…) {
        函数体;
    }
  • 示例:

    c
    void printMax(int value1, int value2) {
        int max = value1 > value2 ? value1 : value2;
        printf("max = %i\n", max);
    }

  • 有参有返回值函数定义
  • 格式:

    c
    返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,…) {
        函数体;
        return 0;
    }
  • 示例:

    c
     int printMax(int value1, int value2) {
        int max = value1 > value2 ? value1 : value2;
        return max;
    }

  • 函数定义注意

  • 函数名称不能相同

    c
    void test() {
    }
    void test() { // 报错
    }

函数的参数和返回值

  • 形式参数
    • 在***定义函数***时,函数名后面小括号()中定义的变量称为形式参数,简称形参
    • 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。
    • 因此,形参只有在函数内部有效,函数调用结束返回主调函数后则不能再使用该形参变量
c
int max(int number1, int number2) //  形式参数
{
    return number1 > number2 ? number1 : number2;
}

  • 实际参数
    • 在***调用函数***时, 传入的值称为实际参数,简称实参
    • 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参
    • 因此应预先用赋值,输入等办法使实参获得确定值
c
int main() {
    int num = 99;
    // 88, num, 22+44均能得到一个确定的值, 所以都可以作为实参
    max(88, num, 22+44); // 实际参数
    return 0;
}

  • 形参、实参注意点

    • 调用函数时传递的实参个数必须和函数的形参个数必须保持一致
    c
    int max(int number1, int number2) { //  形式参数
        return number1 > number2 ? number1 : number2;
    }
    int main() {
        // 函数需要2个形参, 但是我们只传递了一个实参, 所以报错
        max(88); // 实际参数
        return 0;
    }
    • 形参实参类型不一致, 会自动转换为形参类型
    c
    void change(double number1, double number2) {//  形式参数
       // 输出结果: 10.000000, 20.000000
       // 自动将实参转换为double类型后保存
       printf("number1 = %f, number2 = %f", number1, number2);
    }
    int main() {
        change(10, 20);
        return 0;
    }
    • 当使用基本数据类型(char、int、float等)作为实参时,实参和形参之间只是值传递,修改形参的值并不影响到实参函数可以没有形参
    c
    void change(int number1, int number2) { //  形式参数
        number1 = 250; // 不会影响实参
        number2 = 222;
    }
    int main() {
        int a = 88;
        int b = 99;
        change(a, b);
        printf("a  = %d, b = %d", a, b); // 输出结果: 88, 99
        return 0;
    }

  • 返回值类型注意点

    • 如果没有写返回值类型,默认是int
    c
    max(int number1, int number2) {//  形式参数
        return number1 > number2 ? number1 : number2;
    }
    • 函数返回值的类型和return实际返回的值类型应保持一致。如果两者不一致,则以返回值类型为准,自动进行类型转换
    c
    int height() {
        return 3.14; 
    }
    int main() {
      double temp = height();
      printf("%lf", temp);// 输出结果: 3.000000
    }
    • 一个函数内部可以多次使用return语句,但是return语句后面的代码就不再被执行
    c
    int max(int number1, int number2) {//  形式参数
        return number1 > number2 ? number1 : number2;
        printf("执行不到"); // 执行不到
        return 250; // 执行不到
    }

函数的声明

  • 在C语言中,函数的定义顺序是有讲究的:
  • 默认情况下,只有后面定义的函数才可以调用前面定义过的函数
  • 如果想把函数的定义写在main函数后面,而且main函数能正常调用这些函数,那就必须在main函数的前面进行函数的声明, 否则
  • 系统搞不清楚有没有这个函数
  • 系统搞不清楚这个函数接收几个参数
  • 系统搞不清楚这个函数的返回值类型是什么
  • 所以函数声明,就是在函数调用之前告诉系统, 该函数叫什么名称, 该函数接收几个参数, 该函数的返回值类型是什么
  • 函数的声明格式:
  • 将自定义函数时{}之前的内容拷贝到调用之间即可
  • 例如: int max( int a, int b );
  • 或者: int max( int, int );
c
// 函数声明
void getMax(int v1, int v2);
int main(int argc, const char * argv[]) {
    getMax(10, 20); // 调用函数
    return 0;
}
// 函数实现
void getMax(int v1, int v2) {
    int max = v1 > v2 ? v1 : v2;
    printf("max = %i\n", max);
}
  • 函数的声明与实现的关系
  • 声明仅仅代表着告诉系统一定有这个函数, 和这个函数的参数、返回值是什么
  • 实现代表着告诉系统, 这个函数具体的业务逻辑是怎么运作的
  • 函数声明注意点:
  • 函数的实现不能重复, 而函数的声明可以重复

    c
    // 函数声明
    void getMax(int v1, int v2);
    void getMax(int v1, int v2);
    void getMax(int v1, int v2); // 不会报错
    int main(int argc, const char * argv[]) {
        getMax(10, 20); // 调用函数
        return 0;
    }
    // 函数实现
    void getMax(int v1, int v2) {
        int max = v1 > v2 ? v1 : v2;
        printf("max = %i\n", max);
    }
  • 函数声明可以写在函数外面,也可以写在函数里面, 只要在调用之前被声明即可

    c
    int main(int argc, const char * argv[]) {
        void getMax(int v1, int v2); // 函数声明, 不会报错
        getMax(10, 20); // 调用函数
        return 0;
    }
    // 函数实现
    void getMax(int v1, int v2) {
        int max = v1 > v2 ? v1 : v2;
        printf("max = %i\n", max);
    }
  • 当被调函数的函数定义出现在主调函数之前时,在主调函数中也可以不对被调函数再作声明

    c
    // 函数实现
    void getMax(int v1, int v2) {
        int max = v1 > v2 ? v1 : v2;
        printf("max = %i\n", max);
    }
    int main(int argc, const char * argv[]) {
        getMax(10, 20); // 调用函数
        return 0;
    }
  • 如果被调函数的返回值是整型时,可以不对被调函数作说明,而直接调用

    c
    int main(int argc, const char * argv[]) {
        int res = getMin(5, 3); // 不会报错
        printf("result = %d\n", res );
        return 0;
    }
    int getMin(int num1, int num2) {// 返回int, 不用声明
        return num1 < num2 ? num1 : num2;
    }

最后,如果你有任何疑问 加微信 leader_fengy 拉你进学习交流群,另外开源项目有配套视频教程哦。

main函数分析

  • main的含义:
  • main是函数的名称, 和我们自定义的函数名称一样, 也是一个标识符
  • 只不过main这个名称比较特殊, 程序已启动就会自动调用它
  • return 0;的含义:
  • 告诉系统main函数是否正确的被执行了
  • 如果main函数的执行正常, 那么就返回0
  • 如果main函数执行不正常, 那么就返回一个非0的数
  • 返回值类型:
  • 一个函数return后面写的是什么类型, 函数的返回值类型就必须是什么类型, 所以写int
  • 形参列表的含义
  • int argc :
  • 系统在启动程序时调用main函数时传递给argv的值的个数
  • const char * argv[] :
  • 系统在启动程序时传入的的值, 默认情况下系统只会传入一个值, 这个值就是main函数执行文件的路径
  • 也可以通过命令行或项目设置传入其它参数
  • 函数练习
  • 写一个函数从键盘输入三个整型数字,找出其最大值
  • 写一个函数求三个数的平均值

递归函数

  • 什么是递归函数?
  • 一个函数在它的函数体内调用它自身称为递归调用

    c
    void function(int x){
        function(x);
    }
  • 递归函数构成条件
  • 自己搞自己
  • 存在一个条件能够让递归结束
  • 问题的规模能够缩小
  • 示例:
  • 获取用户输入的数字, 直到用户输入一个正数为止
c
void getNumber(){
    int number = -1;
    while (number < 0) {
        printf("请输入一个正数\n");
        scanf("%d", &number);
    }

    printf("number = %d\n", number);
}
c
void getNumber2(){
    int number = -1;
    printf("请输入一个正数abc\n");
    scanf("%d", &number);
    if (number < 0) {
//        负数
        getNumber2();
    }else{
//        正数
       printf("number = %d\n", number);
    }
}
  • 递归和循环区别
  • 能用循环实现的功能,用递归都可以实现
  • 递归常用于"回溯", "树的遍历","图的搜索"等问题
  • 但代码理解难度大,内存消耗大(易导致栈溢出), 所以考虑到代码理解难度和内存消耗问题, 在企业开发中一般能用循环都不会使用递归
  • 递归练习
  • 有5个人坐在一起,问第5个人多少岁?他说比第4个人大两岁。问 第4个人岁数,他说比第3个人大两岁。问第3个人,又说比第2个 人大两岁。问第2个人,说比第1个人大两岁。最后问第1个人, 他说是10岁。请问第5个人多大?
  • 用递归法求N的阶乘
  • 设计一个函数用来计算B的n次方

函数定义格式

  • 主函数定义的格式:
    • int 代表函数执行之后会返回一个整数类型的值
    • main 代表这个函数的名字叫做main
    • () 代表这是一个函数
    • {} 代表这个程序段的范围
    • return 0; 代表函数执行完之后返回整数0
c
int main() {
    // insert code here...
    return 0;
}
  • 其它函数定义的格式
    • int 代表函数执行之后会返回一个整数类型的值
    • call 代表这个函数的名字叫做call
    • () 代表这是一个函数
    • {} 代表这个程序段的范围
    • return 0; 代表函数执行完之后返回整数0
c
int call() {
    return 0;
}

如何执行定义好的函数

  • 主函数(main)会由系统自动调用, 但其它函数不会, 所以想要执行其它函数就必须在main函数中手动调用
    • call 代表找到名称叫做call的某个东西
    • () 代表要找到的名称叫call的某个东西是一个函数
    • ; 代表调用函数的语句已经编写完成
    • 所以call();代表找到call函数, 并执行call函数
c
int main() {
    call();
    return 0;
}
  • 如何往屏幕上输出内容
    • 输出内容是一个比较复杂的操作, 所以系统提前定义好了一个专门用于输出内容的函数叫做printf函数,我们只需要执行系统定义好的printf函数就可以往屏幕上输出内容
    • 但凡需要执行一个函数, 都是通过函数名称+圆括号的形式来执行
    • 如下代码的含义是: 当程序运行时系统会自动执行main函数, 在系统自动执行main函数时我们手动执行了call函数和printf函数
    • 经过对代码的观察, 我们发现两个问题
      • 并没有告诉printf函数,我们要往屏幕上输出什么内容
      • 找不到printf函数的实现代码
c
int call(){
    return 0;
}

int main(){
    call();
    printf();
    return 0;
}
  • 如何告诉printf函数要输出的内容
    • 将要输出的内容编写到printf函数后面的圆括号中即可
    • 注意: 圆括号中编写的内容必须用双引号引起来
c
printf("hello world\n");
  • 如何找到printf函数的实现代码
    • 由于printf函数是系统实现的函数, 所以想要使用printf函数必须在使用之前告诉系统去哪里可以找到printf函数的实现代码
    • #include <stdio.h> 就是告诉系统可以去stdio这个文件中查找printf函数的声明和实现
c
#include <stdio.h>

int call(){
    return 0;
}

int main(){
    call();
    printf("hello world\n");
    return 0;
}

最后,如果你有任何学习疑问,可以加我微信 leader_fengy 拉你进学习群交流。

数据类型

  • 作为程序员, 我们最关心的是内存中的动态数据,因为我们写的程序就是在内存中运行的
  • 程序在运行过程中会产生各种各样的临时数据,为了方便数据的运算和操作, C语言对这些数据进行了分类, 提供了丰富的数据类型
  • C语言中有4大类数据类型:基本类型、构造类型、指针类型、空类型

变量

什么是变量?

  • "量"表示数据。变量,则表示一些不固定的数据,也就是可以改变的数据
  • 就好比现实生活中人的身高、体重一样, 随着年龄的增长会不断发生改变, 所以身高、体重就是现实生活中变量的一种体现
  • 就好比现实生活中超市的储物格一样, 同一个格子在不同时期不同人使用,格子中存储的物品是可以变化的。张三使用这个格子的时候里面放的可能是尿不湿, 但是李四使用这个格子的时候里面放的可能是面包

如何定义变量

  • 格式1: 变量类型 变量名称 ;
    • 为什么要定义变量?
    • 任何变量在使用之前,必须先进行定义, 只有定义了变量才会分配存储空间, 才有空间存储数据
    • 为什么要限定类型?
    • 用来约束变量所存放数据的类型。一旦给变量指明了类型,那么这个变量就只能存储这种类型的数据
    • 内存空间极其有限,不同类型的变量占用不同大小的存储空间
    • 为什么要指定变量名称?
    • 存储数据的空间对于我们没有任何意义, 我们需要的是空间中存储的值
    • 只有有了名称, 我们才能获取到空间中的值
c
int a;
float b;
char ch;
  • 格式2:变量类型 变量名称,变量名称;
    • 连续定义, 多个变量之间用逗号(,)号隔开
c
int a,b,c;
  • 变量名的命名的规范
    • 变量名属于标识符,所以必须严格遵守标识符的命名原则

如何使用变量?

  • 可以利用=号往变量里面存储数据
    • 在C语言中,利用=号往变量里面存储数据, 我们称之为给变量赋值
c
int value;
value = 998; // 赋值
  • 注意:
    • 这里的=号,并不是数学中的“相等”,而是C语言中的赋值运算符,作用是将右边的整型常量998赋值给左边的整型变量value
    • 赋值的时候,= 号的左侧必须是变量 (10=b,错误)
    • 为了方便阅读代码, 习惯在 = 的两侧 各加上一个 空格

变量的初始化

  • C语言中, 变量的第一次赋值,我们称为“初始化”
  • 初始化的两种形式
    • 先定义,后初始化
    • int value; value = 998; // 初始化
    • 定义时同时初始化
    • int a = 10; int b = 4, c = 2;
    • 其它表现形式(不推荐)
c
int a, b = 10; //部分初始化
int c, d, e;
c = d = e =0;
  • 不初始化里面存储什么?
    • 随机数
    • 上次程序分配的存储空间,存数一些 内容,“垃圾”
    • 系统正在用的一些数据

如何修改变量值?

  • 多次赋值即可
    • 每次赋值都会覆盖原来的值
c
int i = 10;
i = 20; // 修改变量的值

变量之间的值传递

  • 可以将一个变量存储的值赋值给另一个变量
c
 int a = 10;
 int b = a; // 相当于把a中存储的10拷贝了一份给b

如何查看变量的值?

  • 使用printf输出一个或多个变量的值
c
int a = 10, c = 11;
printf("a=%d, c=%d", a, c);
  • 输出其它类型变量的值
c
double height = 1.75;
char blood = 'A';
printf("height=%.2f, 血型是%c", height,  blood);

变量的作用域

  • C语言中所有变量都有自己的作用域
  • 变量定义的位置不同,其作用域也不同
  • 按照作用域的范围可分为两种, 即局部变量和全局变量

  • 局部变量
    • 局部变量也称为内部变量
    • 局部变量是在代码块内定义的, 其作用域仅限于代码块内, 离开该代码块后无法使用
c
int main(){
    int i = 998; // 作用域开始
    return 0;// 作用域结束
}
c
int main(){
    {
        int i = 998; // 作用域开始
    }// 作用域结束
    printf("i = %d\n", i); // 不能使用
    return 0;
}
c
int main(){
    {
        {
            int i = 998;// 作用域开始
        }// 作用域结束
        printf("i = %d\n", i); // 不能使用
    }
    return 0;
}

  • 全局变量
    • 全局变量也称为外部变量,它是在代码块外部定义的变量
c
int i = 666;
int main(){
    printf("i = %d\n", i); // 可以使用
    return 0;
}// 作用域结束
int call(){
    printf("i = %d\n", i); // 可以使用
    return 0;
}

  • 注意点:
    • 同一作用域范围内不能有相同名称的变量
c
int main(){
    int i = 998; // 作用域开始
    int i = 666; // 报错, 重复定义
    return 0;
}// 作用域结束
c
int i = 666; 
int i = 998; // 报错, 重复定义
int main(){
    return 0;
}
  • 不同作用域范围内可以有相同名称的变量
c
int i = 666; 
int main(){
    int i = 998; // 不会报错
    return 0;
}
c
int main(){
    int i = 998; // 不会报错
    return 0;
}
int call(){
    int i = 666;  // 不会报错
    return 0;
}

变量内存分析(简单版)

  • 字节和地址

    • 为了更好地理解变量在内存中的存储细节,先来认识一下内存中的“字节”和“地址”
    • 每一个小格子代表一个字节
    • 每个字节都有自己的内存地址
    • 内存地址是连续的
  • 变量存储占用的空间

  • 一个变量所占用的存储空间,和定义变量时声明的类型以及当前编译环境有关

类型16位编译器32位编译器64位编译器
char111
int244
float444
double888
short222
long448
long long888
void*248
  • 变量存储的过程

    • 根据定义变量时声明的类型和当前编译环境确定需要开辟多大存储空间
    • 在内存中开辟一块存储空间,开辟时从内存地址大的开始开辟(内存寻址从大到小)
    • 将数据保存到已经开辟好的对应内存空间中
    c
    int main(){
      int number;
      int value;
      number = 22;
      value = 666;
    }
    c
    #include <stdio.h>
    int main(){
        int number;
        int value;
        number = 22;
        value = 666;
        printf("&number = %p\n", &number); // 0060FEAC
        printf("&value = %p\n", &value);   // 0060FEA8
    }

先不要着急, 刚开始接触C语言, 我先了解这么多就够了. 后面会再次更深入的讲解存储的各种细节。

最后,如果你有任何疑问,加微信 leader_fengy 拉你进学习交流群

常量

什么是常量?

  • "量"表示数据。常量,则表示一些固定的数据,也就是不能改变的数据
  • 就好比现实生活中生男生女一样, 生下来是男孩永远都是男孩, 生下来是女孩就永远都是女孩, 所以性别就是现实生活中常量的一种体现
  • 不要和江哥吹牛X说你是泰国来的, 如果你真的来自泰国, 我只能说你赢了

常量的类型

整型常量

  • 十进制整数。例如:666,-120, 0
  • 八进制整数,八进制形式的常量都以0开头。例如:0123,也就是十进制的83;-011,也就是十进 制的-9
  • 十六进制整数,十六进制的常量都是以0x开头。例如:0x123,也就是十进制的291
  • 二进制整数,逢二进一 0b开头。例如: 0b0010,也就是十进制的2

实型常量

  • 小数形式
  • 单精度小数:以字母f或字母F结尾。例如:0.0f、1.01f
  • 双精度小数:十进制小数形式。例如:3.14、 6.66
  • 默认就是双精度
  • 可以没有整数位只有小数位。例如: .3、 .6f
  • 指数形式
  • 以幂的形式表示, 以字母e或字母E后跟一个10为底的幂数
  • 上过初中的都应该知道科学计数法吧,指数形式的常量就是科学计数法的另一种表 示,比如123000,用科学计数法表示为1.23×10的5次方
  • 用C语言表示就是1.23e5或1.23E5
  • 字母e或字母E后面的指数必须为整数
  • 字母e或字母E前后必须要有数字
  • 字母e或字母E前后不能有空格

字符常量

  • 字符型常量都是用''(单引号)括起来的。例如:'a'、'b'、'c'
  • 字符常量的单引号中只能有一个字符
  • 特殊情况: 如果是转义字符,单引号中可以有两个字符。例如:'\n'、'\t'

字符串常量

  • 字符型常量都是用""(双引号)括起来的。例如:"a"、"abc"、"lnj"
  • 系统会自动在字符串常量的末尾加一个字符'\0'作为字符串结束标志

自定义常量

  • 后期讲解内容, 此处先不用了解

常量类型练习

1231.1F1.1.3'a'"a""李南江"

数据

什么是数据?

  • 生活中无时无刻都在跟数据打交道

  • 例如:人的体重、身高、收入、性别等数据等

  • 在我们使用计算机的过程中,也会接触到各种各样的数据

  • 例如: 文档数据、图片数据、视频数据等


数据分类

静态的数据

  • 静态数据是指一些永久性的数据,一般存储在硬盘中。硬盘的存储空间一般都比较大,现在普通计算机的硬盘都有500G左右,因此硬盘中可以存放一些比较大的文件
  • 存储的时长:计算机关闭之后再开启,这些数据依旧还在,只要你不主动删掉或者硬盘没坏,这些数据永远都在
  • 哪些是静态数据:静态数据一般是以文件的形式存储在硬盘上,比如文档、照片、视频等。

动态的数据

  • 动态数据指在程序运行过程中,动态产生的临时数据,一般存储在内存中。内存的存储空间一般都比较小,现在普通计算机的内存只有8G左右,因此要谨慎使用内存,不要占用太多的内存空间
  • 存储的时长:计算机关闭之后,这些临时数据就会被清除
  • 哪些是动态数据:当运行某个程序(软件)时,整个程序就会被加载到内存中,在程序运行过程中,会产生各种各样的临时数据,这些临时数据都是存储在内存中的。当程序停止运行或者计算机被强制关闭时,这个程序产生的所有临时数据都会被清除。

既然硬盘的存储空间这么大,为何不把所有的应用程序加载到硬盘中去执行呢?

  • 主要原因就是内存的访问速度比硬盘快N倍

  • 静态数据和动态数据的相互转换

  • 也就是从磁盘加载到内存

  • 动态数据和静态数据的相互转换

  • 也就是从内存保存到磁盘

  • 数据的计量单位

  • 不管是静态还是动态数据,都是0和1组成的

  • 数据越大,包含的0和1就越多

c
1 B(Byte字节) = 8 bit(位)
// 00000000 就是一个字节
// 111111111 也是一个字节
// 10101010 也是一个字节
// 任意8个0和1的组合都是一个字节
1 KB(KByte) = 1024 B
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB

标识符

什么是标识符?

  • 从字面上理解,就是用来标识某些东西的符号,标识的目的就是为了将这些东西区分开来
  • 其实标识符的作用就跟人类的名字差不多,为了区分每个人,就在每个人出生的时候起了个名字
  • C语言是由函数构成的,一个C程序中可能会有多个函数,为了区分这些函数,就给每一个函数都起了个名称, 这个名称就是标识符
  • 综上所述: 程序员在程序中给函数、变量等起名字就是标识符

标识符命名规则

  • 只能由字母(a~z、 A~Z)、数字、下划线组成
  • 不能包含除下划线以外的其它特殊字符串
  • 不能以数字开头
  • 不能是C语言中的关键字
  • 标识符严格区分大小写, test和Test是两个不同的标识符

标识符命名规范

  • 见名知意,能够提高代码的可读性
  • 驼峰命名,能够提高代码的可读性
  • 驼峰命名法就是当变量名或函数名是由多个单词连接在一起,构成标识符时,第一个单词以小写字母开始;第二个单词的首字母大写.
  • 例如: myFirstName、myLastName这样的变量名称看上去就像驼峰一样此起彼伏

练习

  • 下列哪些是合法的标识符
fromNo22from#22my_Booleanmy-Boolean2ndObjGUIlnj
Mike2jack江哥_testtest!32haha(da)ttjack_rosejack&rose

关键字

什么是关键字?

关键字,也叫作保留字。是指一些被C语言赋予了特殊含义的单词

关键字特征:

  • 全部都是小写
  • 在开发工具中会显示特殊颜色

关键字注意点:

  • 因为关键字在C语言中有特殊的含义, 所以不能用作变量名、函数名等
  • C语言中一共有32个关键字
12345678
charshortintlongfloatdoubleifelse
returndowhileforswitchcasebreakcontinue
defaultgotosizeofautoregisterstaticexternunsigned
signedtypedefstructenumunionvoidconstvolatile

这些不用专门去记住,用多了就会了。在编译器里都是有特殊颜色的。 我们用到时候会一个一个讲解这个些关键字怎么用,现在浏览下,有个印象就OK了

注释

什么是注释?

  • 注释是在所有计算机语言中都非常重要的一个概念,从字面上看,就是注解、解释的意思
  • 注释可以用来解释某一段程序或者某一行代码是什么意思,方便程序员之间的交流沟通
  • 注释可以是任何文字,也就是说可以写中文
  • 被注释的内容在开发工具中会有特殊的颜色

使用注释的好处?

注释是一个程序员必须要具备的良好习惯

帮助开发人员整理实现思路

解释说明程序, 提高程序的可读性

  • 初学者编写程序可以养成习惯:先写注释再写代码
  • 将自己的思想通过注释先整理出来,在用代码去体现
  • 因为代码仅仅是思想的一种体现形式而已

为什么要使用注释?

  • 没有编写任何注释的程序
c
void printMap(char map[6][7] , int row, int col);
int main(int argc, const char * argv[])
{
    char map[6][7] = {
        {'#', '#', '#', '#', '#', '#', '#'},
        {'#', ' ', ' ', ' ', '#' ,' ', ' '},
        {'#', 'R', ' ', '#', '#', ' ', '#'},
        {'#', ' ', ' ', ' ', '#', ' ', '#'},
        {'#', '#', ' ', ' ', ' ', ' ', '#'},
        {'#', '#', '#', '#', '#', '#', '#'}
    };
    int row = sizeof(map)/sizeof(map[0]);
    int col = sizeof(map[0])/ sizeof(map[0][0]);
    printMap(map, row, col);
    int pRow = 2;
    int pCol = 1;
    int endRow = 1;
    int endCol = 6;
    while ('R' != map[endRow][endCol]) {
        printf("亲, 请输入相应的操作\n");
        printf("w(向上走) s(向下走) a(向左走) d(向右走)\n");
        char run;
        run = getchar();
        switch (run) {
            case 's':
                if ('#' != map[pRow + 1][pCol]) {
                    map[pRow][pCol] = ' ';
                    pRow++;//3
                    map[pRow][pCol] = 'R';
                }
                break;
            case 'w':
                if ('#' != map[pRow - 1][pCol]) {
                    map[pRow][pCol] = ' ';
                    pRow--;
                    map[pRow][pCol] = 'R';
                }
                break;
            case 'a':
                if ('#' != map[pRow][pCol - 1]) {
                    map[pRow][pCol] = ' ';
                    pCol--;
                    map[pRow][pCol] = 'R';
                }
                break;
            case 'd':
                if ('#' != map[pRow][pCol + 1]) {
                    map[pRow][pCol] = ' ';
                    pCol++;
                    map[pRow][pCol] = 'R';
                }
                break;
        }
        printMap(map, row, col);
    }
    printf("你太牛X了\n");
    printf("想挑战自己,请购买完整版本\n");
    return 0;
}
void printMap(char map[6][7] , int row, int col)
{
    system("cls");
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            printf("%c", map[i][j]);
        }
        printf("\n");
    }
}
  • 编写了注释的程序
c
/*
     R代表一个人
     #代表一堵墙
//   0123456
     ####### // 0
     #   #   // 1
     #R ## # // 2
     #   # # // 3
     ##    # // 4
     ####### // 5

     分析:
     >1.保存地图(二维数组)
     >2.输出地图
     >3.操作R前进(控制小人行走)
      3.1.接收用户输入(scanf/getchar)
      w(向上走) s(向下走) a(向左走) d(向右走)
      3.2.判断用户的输入,控制小人行走
         3.2.1.替换二维数组中保存的数据
             (
                1.判断是否可以修改(如果不是#就可以修改)
                2.修改现有位置为空白
                3.修改下一步为R
             )
      3.3.输出修改后的二维数组
     4.判断用户是否走出出口
*/
// 声明打印地图方法
void printMap(char map[6][7] , int row, int col);
int main(int argc, const char * argv[])
{
    // 1.定义二维数组保存迷宫地图
    char map[6][7] = {
        {'#', '#', '#', '#', '#', '#', '#'},
        {'#', ' ', ' ', ' ', '#' ,' ', ' '},
        {'#', 'R', ' ', '#', '#', ' ', '#'},
        {'#', ' ', ' ', ' ', '#', ' ', '#'},
        {'#', '#', ' ', ' ', ' ', ' ', '#'},
        {'#', '#', '#', '#', '#', '#', '#'}
    };
    // 2.计算地图行数和列数
    int row = sizeof(map)/sizeof(map[0]);
    int col = sizeof(map[0])/ sizeof(map[0][0]);
    // 3.输出地图
    printMap(map, row, col);
    // 4.定义变量记录人物位置
    int pRow = 2;
    int pCol = 1;
    // 5.定义变量记录出口的位置
    int endRow = 1;
    int endCol = 6;
    // 6.控制人物行走
    while ('R' != map[endRow][endCol]) {
        // 6.1提示用户如何控制人物行走
        printf("亲, 请输入相应的操作\n");
        printf("w(向上走) s(向下走) a(向左走) d(向右走)\n");
        char run;
        run = getchar();
        // 6.2根据用户输入控制人物行走
        switch (run) {
            case 's':
                if ('#' != map[pRow + 1][pCol]) {
                    map[pRow][pCol] = ' ';
                    pRow++;//3
                    map[pRow][pCol] = 'R';
                }
                break;
            case 'w':
                if ('#' != map[pRow - 1][pCol]) {
                    map[pRow][pCol] = ' ';
                    pRow--;
                    map[pRow][pCol] = 'R';
                }
                break;
            case 'a':
                if ('#' != map[pRow][pCol - 1]) {
                    map[pRow][pCol] = ' ';
                    pCol--;
                    map[pRow][pCol] = 'R';
                }
                break;
            case 'd':
                if ('#' != map[pRow][pCol + 1]) {
                    map[pRow][pCol] = ' ';
                    pCol++;
                    map[pRow][pCol] = 'R';
                }
                break;
        }
        // 6.3重新输出行走之后的地图
        printMap(map, row, col);
    }
    printf("你太牛X了\n");
    printf("想挑战自己,请购买完整版本\n");
    return 0;
}

/**
 * @brief printMap
 * @param map 需要打印的二维数组
 * @param row 二维数组的行数
 * @param col 二维数组的列数
 */
void printMap(char map[6][7] , int row, int col)
{
    // 为了保证窗口的干净整洁, 每次打印都先清空上一次的打印
    system("cls");
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            printf("%c", map[i][j]);
        }
        printf("\n");
    }
}

注释的分类

单行注释

  • // 被注释内容
  • 使用范围:任何地方都可以写注释:函数外面、里面,每一条语句后面
  • 作用范围: 从第二个斜线到这一行末尾
  • 快捷键:Ctrl+/

多行注释

  • /* 被注释内容 */
  • 使用范围:任何地方都可以写注释:函数外面、里面,每一条语句后面
  • 作用范围: 从第一个/*到最近的一个*/

注释的注意点

单行注释可以嵌套单行注释、多行注释

c
// 南哥 // 公号代码情缘
// /* 江哥 */
// 瓜哥
//公众号代码情缘

多行注释可以嵌套单行注释

c
/*
// 作者:LNJ
// 描述:第一个C语言程序作用:这是一个主函数,C程序的入口点
 */

多行注释不能嵌套多行注释

c
/* 
哈哈哈
     /*嘻嘻嘻*/
 呵呵呵 
*/

注释的应用场景

思路分析

c
/*
     R代表一个人
     #代表一堵墙
//   0123456
     ####### // 0
     #   #   // 1
     #R ## # // 2
     #   # # // 3
     ##    # // 4
     ####### // 5

     分析:
     >1.保存地图(二维数组)
     >2.输出地图
     >3.操作R前进(控制小人行走)
      3.1.接收用户输入(scanf/getchar)
      w(向上走) s(向下走) a(向左走) d(向右走)
      3.2.判断用户的输入,控制小人行走
         3.2.1.替换二维数组中保存的数据
             (
                1.判断是否可以修改(如果不是#就可以修改)
                2.修改现有位置为空白
                3.修改下一步为R
             )
      3.3.输出修改后的二维数组
     4.判断用户是否走出出口
*/

对变量进行说明

c
// 2.计算地图行数和列数
int row = sizeof(map)/sizeof(map[0]);
int col = sizeof(map[0])/ sizeof(map[0][0]);

对函数进行说明

c
/**
 * @brief printMap
 * @param map 需要打印的二维数组
 * @param row 二维数组的行数
 * @param col 二维数组的列数
 */
void printMap(char map[6][7] , int row, int col)
{
    system("cls");
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            printf("%c", map[i][j]);
        }
        printf("\n");
    }
}

多实现逻辑排序

c
    // 1.定义二维数组保存迷宫地图
    char map[6][7] = {
        {'#', '#', '#', '#', '#', '#', '#'},
        {'#', ' ', ' ', ' ', '#' ,' ', ' '},
        {'#', 'R', ' ', '#', '#', ' ', '#'},
        {'#', ' ', ' ', ' ', '#', ' ', '#'},
        {'#', '#', ' ', ' ', ' ', ' ', '#'},
        {'#', '#', '#', '#', '#', '#', '#'}
    };
    // 2.计算地图行数和列数
    int row = sizeof(map)/sizeof(map[0]);
    int col = sizeof(map[0])/ sizeof(map[0][0]);
    // 3.输出地图
    printMap(map, row, col);
    // 4.定义变量记录人物位置
    int pRow = 2;
    int pCol = 1;
    // 5.定义变量记录出口的位置
    int endRow = 1;
    int endCol = 6;
    // 6.控制人物行走
    while ('R' != map[endRow][endCol]) {
        ... ...
    }

数组和函数

数组的基本概念

数组,从字面上看,就是一组数据的意思,没错,数组就是用来存储一组数据的。在C语言中,数组属于构造数据类型

数组的几个名词

  • 数组:一组相同数据类型数据的有序的集合
  • 数组元素: 构成数组的每一个数据。
  • 数组的下标: 数组元素位置的索引(从0开始)

数组的应用场景

一个int类型的变量能保存一个人的年龄,如果想保存整个班的年龄呢?

  • 第一种方法是定义很多个int类型的变量来存储
  • 第二种方法是只需要定义一个int类型的数组来存储
c
#include <stdio.h>

int main(int argc, const char * argv[]) {
    /*
    // 需求: 保存2个人的分数
    int score1 = 99;
    int score2 = 60;
    
    // 需求: 保存全班同学的分数(130人)
    int score3 = 78;
    int score4 = 68;
    ...
    int score130 = 88;
    */
    // 数组: 如果需要保存`一组``相同类型`的数据, 就可以定义一个数组来保存
    // 只要定义好一个数组, 数组内部会给每一块小的存储空间一个编号, 这个编号我们称之为 索引, 索引从0开始
    // 1.定义一个可以保存3个int类型的数组
    int scores[3];
    
    // 2.通过数组的下标往数组中存放数据
    scores[0] = 998;
    scores[1] = 123;
    scores[2] = 567;
   
    // 3.通过数组的下标从数组中取出存放的数据
    printf("%i\n", scores[0]);
    printf("%i\n", scores[1]);
    printf("%i\n", scores[2]);
    return 0;
}

定义数组

元素类型 数组名[元素个数];

c
// int 元素类型
// ages 数组名称
// [10] 元素个数
int ages[10];

初始化数组

定义的同时初始化

指定元素个数,完全初始化,其中在{ }中的各数据值即为各元素的初值,各值之间用逗号间隔

c
int ages[3] = {4, 6, 9};

不指定元素个数,完全初始化,根据大括号中的元素的个数来确定数组的元素个数

c
int nums[] = {1,2,3,5,6};

指定元素个数,部分初始化,没有显式初始化的元素,那么系统会自动将其初始化为0

c
int nums[10] = {1,2};

指定元素个数,部分初始化

c
int nums[5] = {[4] = 3,[1] = 2};

不指定元素个数,部分初始化

c
int nums[] = {[4] = 3};

先定义后初始化

c
int nums[3];
nums[0] = 1;
nums[1] = 2;
nums[2] = 3;

没有初始化会怎样?

如果定义数组后,没有初始化,数组中是有值的,是随机的垃圾数,所以如果想要正确使用数组应该要进行初始化。

c
int nums[5];
printf("%d\n", nums[0]);
printf("%d\n", nums[1]);
printf("%d\n", nums[2]);
printf("%d\n", nums[3]);
printf("%d\n", nums[4]);
输出结果:
0
0
1606416312
0
1606416414

注意点:

  • 使用数组时不能超出数组的索引范围使用, 索引从0开始, 到元素个数-1结束
  • 使用数组时不要随意使用未初始化的元素, 有可能是一个随机值
  • 对于数组来说, 只能在定义的同时初始化多个值, 不能先定义再初始化多个值
c
int ages[3];
ages = {4, 6, 9}; // 报错

数组的使用

通过下标(索引)访问:

c
// 找到下标为0的元素, 赋值为10
ages[0]=10;
// 取出下标为2的元素保存的值
int a = ages[2];
printf("a = %d", a);

数组的遍历

数组的遍历:遍历的意思就是有序地查看数组的每一个元素

c
int ages[4] = {19, 22, 33, 13};
for (int i = 0; i < 4; i++) {
    printf("ages[%d] = %d\n", i, ages[i]);
}

数组长度计算方法

因为数组在内存中占用的字节数取决于其存储的数据类型和数据的个数:数组所占用存储空间 = 一个元素所占用存储空间 * 元素个数(数组长度)

所以计算数组长度可以使用如下方法

数组的长度 = 数组占用的总字节数 / 数组元素占用的字节数

c
int ages[4] = {19, 22, 33, 13};
int length =  sizeof(ages)/sizeof(int);
printf("length = %d", length);	// 输出结果: 4

正序输出(遍历)数组

c
int ages[4] = {19, 22, 33, 13};
for (int i = 0; i < 4; i++) {
    printf("ages[%d] = %d\n", i, ages[i]);
}

逆序输出(遍历)数组

c
int ages[4] = {19, 22, 33, 13};
for (int i = 3; i >=0; i--) {
    printf("ages[%d] = %d\n", i, ages[i]);
}

从键盘输入数组长度,构建一个数组,然后再通过for循环从键 盘接收数字给数组初始化。并使用for循环输出查看

数组的越界问题

数组越界导致的问题

约错对象

程序崩溃

c
char cs1[2] = {1, 2};
char cs2[3] = {3, 4, 5};
cs2[3] = 88; // 注意:这句访问到了不属于cs1的内存
printf("cs1[0] = %d\n", cs1[0] ); // 输出结果: 88

为什么上述会输出88, 自己按照"数组内部存储细节"画图脑补


数组注意事项

  • 在定义数组的时候[]里面只能写整型常量或者是返回整型常量的表达式
c
 int ages4['A'] = {19, 22, 33};
 printf("ages4[0] = %d\n", ages4[0]);

  int ages5[5 + 5] = {19, 22, 33};
  printf("ages5[0] = %d\n", ages5[0]);

  int ages5['A' + 5] = {19, 22, 33};
  printf("ages5[0] = %d\n", ages5[0]);
  • 错误写法
c
// 没有指定元素个数,错误
int a[];

// []中不能放变量
int number = 10;
int ages[number]; // 老版本的C语言规范不支持
printf("%d\n", ages[4]);

int number = 10;
int ages2[number] = {19, 22, 33} // 直接报错

// 只能在定义数组的时候进行一次性(全部赋值)的初始化
int ages3[5];
ages10 = {19, 22, 33};

// 一个长度为n的数组,最大下标为n-1, 下标范围:0~n-1
int ages4[4] = {19, 22, 33}
ages4[8]; // 数组角标越界
  • 练习
  • 从键盘录入当天出售BTC的价格并计算出售的BTC的总价和平均价(比如说一天出售了10个比特币)

最后,如果有任何疑问,请加微信 leader_fengy 拉你进学习交流群。

开源不易,码字不易,如果觉得有价值,欢迎分享支持。

数组内部存储细节

存储方式:

  1. 内存寻址从大到小, 从高地址开辟一块连续没有被使用的内存给数组
  2. 从分配的连续存储空间中, 地址小的位置开始给每个元素分配空间
  3. 从每个元素分配的存储空间中, 地址最大的位置开始存储数据
  4. 用数组名指向整个存储空间最小的地址

示例

c
#include <stdio.h>
int main()
{
    int num = 9;
    char cs[] = {'l','n','j'};
    printf("cs = %p\n", &cs);       // cs = 0060FEA9
    printf("cs[0] = %p\n", &cs[0]); // cs[0] = 0060FEA9
    printf("cs[1] = %p\n", &cs[1]); // cs[1] = 0060FEAA
    printf("cs[2] = %p\n", &cs[2]); // cs[2] = 0060FEAB

    int nums[] = {2, 6};
    printf("nums = %p\n", &nums);      // nums = 0060FEA0
    printf("nums[0] = %p\n", &nums[0]);// nums[0] = 0060FEA0
    printf("nums[1] = %p\n", &nums[1]);// nums[1] = 0060FEA4
    
    return 0;
}

注意:字符在内存中是以对应ASCII码值的二进制形式存储的,而非上述的形式。

数组和函数

数组可以作为函数的参数使用,数组用作函数参数有两种形式:

  • 一种是把数组元素作为实参使用
  • 一种是把数组名作为函数的形参和实参使用

数组元素作为函数参数

数组的元素作为函数实参,与同类型的简单变量作为实参一样,如果是基本数据类型, 那么形参的改变不影响实参

c
void change(int val)// int val = number
{
    val = 55;
}

int main(int argc, const char * argv[])
{
    int ages[3] = {1, 5, 8};
    printf("ages[0] = %d", ages[0]);// 1
    change(ages[0]);
    printf("ages[0] = %d", ages[0]);// 1
}

用数组元素作函数参数不要求形参也必须是数组元素

数组名作为函数参数

在C语言中,数组名除作为变量的标识符之外,数组名还代表了该数组在内存中的起始地址,因此,当数组名作函数参数时,实参与形参之间不是"值传递",而是"地址传递"

实参数组名将该数组的起始地址传递给形参数组,两个数组共享一段内存单元, 系统不再为形参数组分配存储单元

既然两个数组共享一段内存单元, 所以形参数组修改时,实参数组也同时被修改了

c
void change2(int array[3])// int array = 0ffd1
{
    array[0] = 88;
}

int main(int argc, const char * argv[])
{
    int ages[3] = {1, 5, 8};
    printf("ages[0] = %d", ages[0]);// 1
    change(ages);
    printf("ages[0] = %d", ages[0]);// 88
}

数组名作函数参数的注意点

在函数形参表中,允许不给出形参数组的长度

c
void change(int array[])
{
    array[0] = 88;
}

形参数组和实参数组的类型必须一致,否则将引起错误。

c
void prtArray(double array[3]) // 错误写法
{
    for (int i = 0; i < 3; i++) {
        printf("array[%d], %f", i, array[i]);
    }
}
int main(int argc, const char * argv[])
{
    int ages[3] = {1, 5, 8};
    prtArray(ages[0]);
}

当数组名作为函数参数时, 因为自动转换为了指针类型,所以在函数中无法动态计算除数组的元素个数

c
void printArray(int array[])
{
    printf("printArray size = %lu\n", sizeof(array)); // 8
    int length = sizeof(array)/ sizeof(int); // 2
    printf("length = %d", length);
}

练习:

  • 设计一个函数int arrayMax(int a[], int count)找出数组元素的最大值
  • 从键盘输入3个0-9的数字,然后输出0~9中哪些数字没有出现过
  • 要求从键盘输入6个0~9的数字,排序后输出

运算符

运算符基本概念

和数学中的运算符一样, C语言中的运算符是告诉程序执行特定算术或逻辑操作的符号

  • 例如告诉程序, 某两个数相加, 相减,相乘等

什么是表达式

  • 表达式就是利用运算符链接在一起的有意义,有结果的语句;
  • 例如: a + b; 就是一个算数表达式, 它的意义是将两个数相加, 两个数相加的结果就是表达式的结果
  • 注意: 表达式一定要有结果

运算符分类

按照功能划分:算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符

按照参与运算的操作数个数划分:

  • 单目运算:只有一个操作数 如 : i++;
  • 双目运算:有两个操作数 如 : a + b;
  • 三目运算:C语言中唯一的一个,也称为问号表达式 如: a>b ? 1 : 0;

运算符的优先级和结合性

早在小学的数学课本中,我们就学习过"从左往右,先乘除后加减,有括号的先算括号里面的", 这句话就蕴含了优先级和结合性的问题

C语言中,运算符的运算优先级共分为15 级。1 级最高,15 级最低

在C语言表达式中,不同优先级的运算符, 运算次序按照由高到低执行

在C语言表达式中,相同优先级的运算符, 运算次序按照结合性规定的方向执行

算数运算符

优先级名称符号说明
3乘法运算符*双目运算符,具有左结合性
3除法运算符/双目运算符,具有左结合性
3求余运算符 (模运算符)%双目运算符,具有左结合性
4加法运算符+双目运算符,具有左结合性
4减法运算符-双目运算符,具有左结合性

注意事项

  • 如果参与运算的两个操作数皆为整数, 那么结果也为整数
  • 如果参与运算的两个操作数其中一个是浮点数, 那么结果一定是浮点数
  • 求余运算符, 本质上就是数学的商和余"中的余数
  • 求余运算符, 参与运算的两个操作数必须都是整数, 不能包含浮点数
  • 求余运算符, 被除数小于除数, 那么结果就是被除数
  • 求余运算符, 运算结果的正负性取决于被除数,跟除数无关, 被除数是正数结果就是正数,被除数是负数结果就是负数
  • 求余运算符, 被除数为0, 结果为0
  • 求余运算符, 除数为0, 没有意义(不要这样写)
c
#include <stdio.h>
int main(){
    int a = 10;
    int b = 5;
    // 加法
    int result = a + b;
    printf("%i\n", result); // 15
    // 减法
    result = a - b;
    printf("%i\n", result); // 5
    // 乘法
    result = a * b;
    printf("%i\n", result); // 50
    // 除法
    result = a / b;
    printf("%i\n", result); // 2
    
    // 算术运算符的结合性和优先级
    // 结合性: 左结合性, 从左至右
    int c = 50;
    result = a + b + c; // 15 + c;  65;
    printf("%i\n", result);
    
    // 优先级: * / % 大于 + -
    result = a + b * c; // a + 250; 260;
    printf("%i\n", result);
}
c
#include <stdio.h>
int main(){
    // 整数除以整数, 结果还是整数
    printf("%i\n", 10 / 3); // 3

    // 参与运算的任何一个数是小数, 结果就是小数
    printf("%f\n", 10 / 3.0); // 3.333333
}
c
#include <stdio.h>
int main(){
    // 10 / 3 商等于3, 余1
    int result = 10 % 3;
    printf("%i\n", result); // 1

    // 左边小于右边, 那么结果就是左边
    result = 2 % 10;
    printf("%i\n", result); // 2

    // 被除数是正数结果就是正数,被除数是负数结果就是负数
    result = 10 % 3;
    printf("%i\n", result); // 1
    result = -10 % 3;
    printf("%i\n", result); // -1
    result = 10 % -3;
    printf("%i\n", result); // 1
}

赋值运算符

优先级名称符号说明
14赋值运算符=双目运算符,具有右结合性
14除后赋值运算符/=双目运算符,具有右结合性
14乘后赋值运算符 (模运算符)*=双目运算符,具有右结合性
14取模后赋值运算符%=双目运算符,具有右结合性
14加后赋值运算符+=双目运算符,具有右结合性
14减后赋值运算符-=双目运算符,具有右结合性

简单赋值运算符

c
#include <stdio.h>
int main(){
    // 简单的赋值运算符 =
    // 会将=右边的值赋值给左边
    int a = 10;
    printf("a = %i\n", a); // 10
}

复合赋值运算符

c
#include <stdio.h>
int main(){
     // 复合赋值运算符 += -= *= /= %=
     // 将变量中的值取出之后进行对应的操作, 操作完毕之后再重新赋值给变量
     int num1 = 10;
     // num1 = num1 + 1; num1 = 10 + 1; num1 = 11;
     num1 += 1;
     printf("num1 = %i\n", num1); // 11
     int num2 = 10;
     // num2 = num2 - 1; num2 = 10 - 1; num2 = 9;
     num2 -= 1;
     printf("num2 = %i\n", num2); // 9
     int num3 = 10;
     // num3 = num3 * 2; num3 = 10 * 2; num3 = 20;
     num3 *= 2;
     printf("num3 = %i\n", num3); // 20
     int num4 = 10;
     // num4 = num4 / 2; num4 = 10 / 2; num4 = 5;
     num4 /= 2;
     printf("num4 = %i\n", num4); // 5
     int num5 = 10;
     // num5 = num5 % 3; num5 = 10 % 3; num5 = 1;
     num5 %= 3;
     printf("num5 = %i\n", num5); // 1
}

结合性和优先级

c
#include <stdio.h>
int main(){
    int number = 10;
    // 赋值运算符优先级是14, 普通运算符优先级是3和4, 所以先计算普通运算符
    // 普通运算符中乘法优先级是3, 加法是4, 所以先计算乘法
    // number += 1 + 25; number += 26; number = number + 26; number = 36;
    number += 1 + 5 * 5;
    printf("number = %i\n", number); // 36
}

自增自减运算符

在程序设计中,经常遇到“i=i+1”和“i=i-1”这两种极为常用的操作。

C语言为这种操作提供了两个更为简洁的运算符,即++和--

优先级名称符号说明
2自增运算符(在后)i++单目运算符,具有左结合性
2自增运算符(在前)++i单目运算符,具有右结合性
2自减运算符(在后)i--单目运算符,具有左结合性
2自减运算符(在前)--i单目运算符,具有右结合性

自增

  • 如果只有单个变量, 无论++写在前面还是后面都会对变量做+1操作
c
#include <stdio.h>
int main(){
    int number = 10;
    number++;
    printf("number = %i\n", number); // 11
    ++number;
    printf("number = %i\n", number); // 12
}

如果出现在一个表达式中, 那么++写在前面和后面就会有所区别

  • 前缀表达式:++x, --x;其中x表示变量名,先完成变量的自增自减1运算,再用x的值作为表达式的值;即“先变后用”,也就是变量的值先变,再用变量的值参与运算
  • 后缀表达式:x++, x--;先用x的当前值作为表达式的值,再进行自增自减1运算。即“先用后变”,也就是先用变量的值参与运算,变量的值再进行自增自减变化
c
#include <stdio.h>
int main(){
    int number = 10;
    // ++在后, 先参与表达式运算, 再自增
    // 表达式运算时为: 3 + 10;
    int result = 3 + number++;
    printf("result = %i\n", result); // 13
    printf("number = %i\n", number); // 11
}
c
#include <stdio.h>
int main(){
    int number = 10;
    // ++在前, 先自增, 再参与表达式运算
    // 表达式运算时为: 3 + 11;
    int result = 3 + ++number;
    printf("result = %i\n", result); // 14
    printf("number = %i\n", number); // 11
}

自减

c
#include <stdio.h>
int main(){
    int number = 10;
    // --在后, 先参与表达式运算, 再自减
    // 表达式运算时为: 10 + 3;
    int result = number-- + 3;
    printf("result = %i\n", result); // 13
    printf("number = %i\n", number); // 9
}
c
#include <stdio.h>
int main(){
    int number = 10;
    // --在前, 先自减, 再参与表达式运算
    // 表达式运算时为: 9 + 3;
    int result = --number + 3;
    printf("result = %i\n", result); // 12
    printf("number = %i\n", number); // 9
}

注意点:

  • 自增、自减运算只能用于单个变量,只要是标准类型的变量,不管是整型、实型,还是字符型变量等,但不能用于表达式或常量
    • 错误用法: ++(a+b); 5++;
  • 企业开发中尽量让++ -- 单独出现, 尽量不要和其它运算符混合在一起
c
int i = 10;
int b = i++; // 不推荐
或者
int b = ++i; // 不推荐
或者
int a = 10;
int b = ++a + a++;  // 不推荐

请用如下代码替代

c
int i = 10;
int b = i; // 推荐
i++;
或者;
i++;
int b = i; // 推荐
或者
int a = 10;
++a;
int b = a + a; // 推荐
a++;

C语言标准没有明确的规定,同一个表达式中同一个变量自增或自减后如何运算, 不同编译器得到结果也不同, 在企业开发中千万不要这样写

c
int a = 1;
// 下列代码利用Qt运行时6, 利用Xcode运行是5
// 但是无论如何, 最终a的值都是3
// 在C语言中这种代码没有意义, 不用深究也不要这样写
// 特点: 参与运算的是同一个变量, 参与运算时都做了自增自减操作, 并且在同一个表达式中
int b = ++a + ++a;
printf("b = %i\n", b);

sizeof运算符

sizeof可以用来计算一个变量或常量、数据类型所占的内存字节数

  • 标准格式: sizeof(常量 or 变量);

sizeof的几种形式

  • sizeof( 变量\常量 );
    • sizeof(10);
    • char c = 'a'; sizeof(c);
  • sizeof 变量\常量;
    • sizeof 10;
    • char c = 'a'; sizeof c;
  • sizeof( 数据类型);
    • sizeof(float);
    • 如果是数据类型不能省略括号
  • sizeof面试题:
    • sizeof()和+=、*=一样是一个复合运算符, 由sizeof和()两个部分组成, 但是代表的是一个整体
    • 所以sizeof不是一个函数, 是一个运算符, 该运算符的优先级是2
c
#include <stdio.h>
int main(){
    int a = 10;
    double b = 3.14;
    // 由于sizeof的优先级比+号高, 所以会先计算sizeof(a);
    // a是int类型, 所以占4个字节得到结果4
    // 然后再利用计算结果和b相加, 4 + 3.14 = 7.14
    double res = sizeof a+b;
    printf("res = %lf\n", res); // 7.14
}

逗号运算符

在C语言中逗号“,”也是一种运算符,称为逗号运算符。 其功能是把多个表达式连接起来组成一个表达式,称为逗号表达式

逗号运算符会从左至右依次取出每个表达式的值, 最后整个逗号表达式的值等于最后一个表达式的值

格式: 表达式1,表达式2,… …,表达式n;

例如: int result = a+1,b=3*4;

c
#include <stdio.h>
int main(){
    int a = 10, b = 20, c;
    // ()优先级高于逗号运算符和赋值运算符, 所以先计算()中的内容
    // c = (11, 21);
    // ()中是一个逗号表达式, 结果是最后一个表达式的值, 所以计算结果为21
    // 将逗号表达式的结果赋值给c, 所以c的结果是21
    c = (a + 1, b + 1);
    printf("c = %i\n", c); // 21
}

关系运算符

为什么要学习关系运算符

默认情况下,我们在程序中写的每一句正确代码都会被执行。但很多时候,我们想在某个条件成立的情况下才执行某一段代码

这种情况的话可以使用条件语句来完成,但是学习条件语句之前,我们先来看一些更基础的知识:如何判断一个条件是否成立


  • C语言中的真假性
  • 在C语言中,条件成立称为“真”,条件不成立称为“假”,因此,判断条件是否成立,就是判断条件的“真假”
  • 怎么判断真假呢?C语言规定,任何数值都有真假性,任何非0值都为“真”,只有0才为“假”。也就是说,108、-18、4.5、-10.5等都是“真”,0则是“假”

  • 关系运算符的运算结果只有2种:如果条件成立,结果就为1,也就是“真”;如果条件不成立,结果就为0,也就是“假”
优先级名称符号说明
6大于运算符>双目运算符,具有左结合性
6小于运算符<双目运算符,具有左结合性
6大于等于运算符>=双目运算符,具有左结合性
6小于等于运算符<=双目运算符,具有左结合性
7等于运算符==双目运算符,具有左结合性
7不等于运算符!=双目运算符,具有左结合性
c
#include <stdio.h>
int main(){
    int result = 10 > 5;
    printf("result = %i\n", result); // 1
    result = 5 < 10;
    printf("result = %i\n", result); // 1
    result = 5 > 10;
    printf("result = %i\n", result); // 0
    result = 10 >= 10;
    printf("result = %i\n", result); // 1
    result = 10 <= 10;
    printf("result = %i\n", result); // 1
    result = 10 == 10;
    printf("result = %i\n", result); // 1
    result = 10 != 9;
    printf("result = %i\n", result); // 1
}
  • 优先级和结合性
c
#include <stdio.h>
int main(){
    // == 优先级 小于 >, 所以先计算>
    // result = 10 == 1; result = 0;
    int result = 10 == 5 > 3;
    printf("result = %i\n", result); // 0
}
c
#include <stdio.h>
int main(){
    // == 和 != 优先级一样, 所以按照结合性
    // 关系运算符是左结合性, 所以从左至右计算
    // result = 0 != 3; result = 1;
    int result = 10 == 5 != 3;
    printf("result = %i\n", result); // 1
}
  • 练习: 计算result的结果
c
int result1 = 3 > 4 + 7
int result2 = (3>4) + 7
int result3 = 5 != 4 + 2 * 7 > 3 == 10
  • 注意点:
    • 无论是float还是double都有精度问题, 所以一定要避免利用==判断浮点数是否相等
c
#include <stdio.h>
int main(){
    float a = 0.1;
    float b = a * 10 + 0.00000000001;
    double c = 1.0 + + 0.00000000001;
    printf("b = %f\n", b);
    printf("c = %f\n", c);
    int result = b == c;
    printf("result = %i\n", result); // 0
}

逻辑运算符

优先级名称符号说明
2逻辑非运算符!单目运算符,具有右结合性
11逻辑与运算符&&双目运算符,具有左结合性
12逻辑或运算符||双目运算符,具有左结合性

逻辑非

格式: ! 条件A;

运算结果: 真变假,假变真

运算过程:

  • 先判断条件A是否成立,如果添加A成立, 那么结果就为0,即“假”;
  • 如果条件A不成立,结果就为1,即“真”
  • 使用注意:
  • 可以多次连续使用逻辑非运算符
  • !!!0;相当于(!(!(!0)));最终结果为1
c
#include <stdio.h>
int main(){
    // ()优先级高, 先计算()里面的内容
    // 10==10为真, 所以result = !(1);
    // !代表真变假, 假变真,所以结果是假0
    int result = !(10 == 10);
    printf("result = %i\n", result); // 0
}

逻辑与

  • 格式: 条件A && 条件B;
  • 运算结果:一假则假
  • 运算过程:
  • 总是先判断"条件A"是否成立
  • 如果"条件A"成立,接着再判断"条件B"是否成立, 如果"条件B"也成立,结果就为1,即“真”
  • 如果"条件A"成立,"条件B"不成立,结果就为0,即“假”
  • 如果"条件A"不成立,不会再去判断"条件B"是否成立, 因为逻辑与只要一个不为真结果都不为真
  • 使用注意:
  • "条件A"为假, "条件B"不会被执行
c
#include <stdio.h>
int main(){
    //               真     &&    真
    int result = (10 == 10) && (5 != 1);
    printf("result = %i\n", result); // 1
    //          假     &&    真
    result = (10 == 9) && (5 != 1);
    printf("result = %i\n", result); // 0
    //          真     &&    假
    result = (10 == 10) && (5 != 5);
    printf("result = %i\n", result); // 0
    //          假     &&    假
    result = (10 == 9) && (5 != 5);
    printf("result = %i\n", result); // 0
}
c
#include <stdio.h>
int main(){
    int a = 10;
    int b = 20;
    // 逻辑与, 前面为假, 不会继续执行后面
    int result = (a == 9) && (++b);
    printf("result = %i\n", result); // 1
    printf("b = %i\n", b); // 20
}

逻辑或

  • 格式: 条件A || 条件B;
  • 运算结果:一真则真
  • 运算过程:
  • 总是先判断"条件A"是否成立
  • 如果"条件A"不成立,接着再判断"条件B"是否成立, 如果"条件B"成立,结果就为1,即“真”
  • 如果"条件A"不成立,"条件B"也不成立成立, 结果就为0,即“假”
  • 如果"条件A"成立, 不会再去判断"条件B"是否成立, 因为逻辑或只要一个为真结果都为真
  • 使用注意:
  • "条件A"为真, "条件B"不会被执行
c
#include <stdio.h>
int main(){
    //               真     ||    真
    int result = (10 == 10) || (5 != 1);
    printf("result = %i\n", result); // 1
    //          假     ||    真
    result = (10 == 9) || (5 != 1);
    printf("result = %i\n", result); // 1
    //          真     ||    假
    result = (10 == 10) || (5 != 5);
    printf("result = %i\n", result); // 1
    //          假     ||    假
    result = (10 == 9) || (5 != 5);
    printf("result = %i\n", result); // 0
}
c
#include <stdio.h>
int main(){
    int a = 10;
    int b = 20;
    // 逻辑或, 前面为真, 不会继续执行后面
    int result = (a == 10) || (++b);
    printf("result = %i\n", result); // 1
    printf("b = %i\n", b); // 20
}
  • 练习: 计算result的结果
c
int result = 3>5 || 2<4 && 6<1;

三目运算符

三目运算符,它需要3个数据或表达式构成条件表达式

格式: 表达式1?表达式2(结果A):表达式3(结果B)

示例: 考试及格 ? 及格 : 不及格;

求值规则:

如果"表达式1"为真,三目运算符的运算结果为"表达式2"的值(结果A),否则为"表达式3"的值(结果B)

c
示例:
    int a = 10;
    int b = 20;
    int max = (a > b) ? a : b;
    printf("max = %d", max);
    输出结果: 20
等价于:
    int a = 10;
    int b = 20;
    int max = 0;
    if(a>b){
      max=a;
    }else {
       max=b;
    }
    printf("max = %d", max);
  • 注意点
  • 条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值符
  • 条件运算符?和:是一个整体,不能分开使用
c
#include <stdio.h>
int main(){
    int a = 10;
    int b = 5;
    // 先计算 a > b
    // 然后再根据计算结果判定返回a还是b
    // 相当于int max= (a>b) ? a : b;
    int max= a>b ? a : b;
    printf("max = %i\n", max); // 10
}
c
#include <stdio.h>
int main(){
    int a = 10;
    int b = 5;
    int c = 20;
    int d = 10;
    // 结合性是从右至左, 所以会先计算:后面的内容
    // int res = a>b?a:(c>d?c:d);
    // int res = a>b?a:(20>10?20:10);
    // int res = a>b?a:(20);
    // 然后再计算最终的结果
    // int res = 10>5?10:(20);
    // int res = 10;
    int res = a>b?a:c>d?c:d;
    printf("res = %i\n", res);
}

类型转换

强制类型转换(显示转换)自动类型转换(隐式转换)
(需要转换的类型)(表达式)1.算数转换 2.赋值转换
  • 强制类型转换(显示转换)
c
// 将double转换为int
int a = (int)10.5;

算数转换

  • 系统会自动对占用内存较少的类型做一个“自动类型提升”的操作, 先将其转换为当前算数表达式中占用内存高的类型, 然后再参与运算
c
// 当前表达式用1.0占用8个字节, 2占用4个字节
// 所以会先将整数类型2转换为double类型之后再计算
double b = 1.0 / 2;
  • 赋值转换
c
// 赋值时左边是什么类型,就会自动将右边转换为什么类型再保存
int a = 10.6;
  • 注意点:
  • 参与计算的是什么类型, 结果就是什么类型
c
// 结果为0, 因为参与运算的都是整型
double a = (double)(1 / 2);
// 结果为0.5, 因为1被强制转换为了double类型, 2也会被自动提升为double类型
double b = (double)1 / 2;
  • 类型转换并不会影响到原有变量的值
c
#include <stdio.h>
int main(){
    double d = 3.14;
    int num = (int)d;
    printf("num = %i\n", num); // 3
    printf("d = %lf\n", d); // 3.140000
}

基于 MIT 许可发布