C语言入门 —— 非科班大一学生的C语言自学笔记

初入前端的北京某211大一非科班生(没错上学期还是日语生)的C语言自学笔记
本文约8k字,将介绍:编程基础 数据类型 表达式 语句与控制流 函数 数组,指针 结构体等会随着学习进度推进持续更新~

学习C语言的动机:大一上学期学完 html5 css3 javascript 后,程序对我要求掌握一定的数据结构与算法,而数据结构教材完全是以C语言讲解的,恰好本学期专业课有开展C程序设计语言,顺水推舟地就产生了这篇文章了。当然,绝大部分内容是基于我在csdn上阅读前人的经验所学习的,因此本文档可能更是一篇学习总结。

感谢你的阅读,让我们开始C程序设计语言入门的学习

请添加图片描述


编程

让计算机为解决某个问题 而使用某种 程序设计语言编写程序代码 并最终得到结果 的过程

计算机程序

计算机所执行的一系列的 指令集合

计算机语言

机器语言、汇编语言、高级语言

机器语言: 计算机最终执行的语言 由0和1组成的二进制数 二进制是计算机语言的基础
编程语言: 控制计算机的指令 有固定格式和词汇
分为汇编语言和编程语言

汇编语言: 和机器语言实质相同 都是直接对硬件操作 指令采用英文缩写的标识符

高级语言: C C++ Java C# Python PHP JavaScript Go语言 Objective-C Swift

编程语言和标记语言(html)区别

编程语言 很强的逻辑性和行为能力 诸如if else, for, while等具有逻辑性和行为能力的指令 主动

标记语言(html) 不用于向计算机发出指令 常用于格式化和链接 标记语言的存在是用来被读取 被动

翻译器

高级语言所编制的程序不能直接被计算机识别 必须经过转换才能被执行
翻译器:源代码——>机器语言 这一步也被称为 二进制化

数据类型

数据类型规定如何解读数据

  • int 整型 字节4 -215 ~ 215-1

  • char 字符型 字节1

  • short 短整型 字节2 -215 ~ 215-1

  • long 长整型 字节8 -231 ~ 231-1

  • unsigned int 无符号整型 字节4 0 ~ 216-1

  • float 单精度型 字节4 -3.4x10-38 ~ 3.4x1038

  • double 双精度型 字节8 -1.7x10-308 ~ 1.7x10308

  • long double 长双精度型 字节16 -1.2x10-4932 ~ 1.2x104932

  • void* 空类型 字节8

​ // c语言中不存在字符串变量 字符串只能存在于数组中

请添加图片描述

表达式

表达式是由**操作数(操作对象)运算符(操作符)**组成的式子,表达式是指具有完整意义的计算机指令

表达式的分类:一元 二元 三元

基础运算

四则运算

比较运算(关系运算)

< > <= >= == !=

float\double 无法精确判等 存在精度问题

c语言中没有布尔型 真:非0 假:0

位(bit)运算

<< >> 位移运算(快于四则运算) 溢出则丢弃 左移后空缺补0

位运算不支持float\double

逻辑运算(&& || !)按位运算(& | !) 不一样 逻辑运算有真假(非0和0)

  • 按位与(&): 十进制转换为二进制,上下比较,有0则0,两个都是1才1

  • 按位或(~): 上下比较有1则1,两个都是0才0

  • 按位异或(^): 上下比较,相同为0,不同为1

  • 按位左移(<<): 二进制向左移一位,溢出丢弃,右边补零

  • 按位右移(>>): 二进制向右移一位,右边丢弃,左边补原符号位

赋值表达式 += -= *= &= %=

三元表达式 a?b:c if a the b else c

部分单目操作符

&: 每次使用scanf函数时,总要在变量名前加上&,&变量a的意思是取变量a的地址,这是&的作用之一

~ : 对一个数的二进制按位取反

*: 间接访问操作符(解引用操作符)

(类型): 强制类型转换

格式化输出语句 (占位输出)

​ 将各种类型的数据按照格式化后的类型及指定位置从计算机上显示

printf("输出格式符", 输出项);

`// %d 带符号十进制整数
int a = 10;
printf("%d", a);
// %c 单个字符
char x = 'a';
printf("%c", x);
// %s 字符串
printf("%s", "string");
// %f 6位小数
float a = 1.23;
printf("%f", a);

格式符的个数要与变量、常量或者表达式个数一一对应

int a = 10;
float b = 7.56;
char x = 'c';
printf("%d, %f, %c", a, b, x);

类型:

d 有符号10进制整型

i 有符号10进制整型

u 无符号10进制整型

o 无符号8进制整型

x 无符号8进制整型

X 无符号16进制整型

f 单、双精度浮点数(默认保留6位小数)

e/E 以指数形式输出单、双精度浮点数

g/G 以最短输出宽度,输出单、双精度浮点数

c 字符

s 字符串

p 地址

标志和精度:

- 左对齐(默认是右对齐)

+ 当输出值为正数时,在输出值前面加一个 + (默认不显示)

0 右对齐时,用0填充宽度(默认用空格填充)

空格 输出值为正数时,在输出值前面加上空格,为负数时加负号

# 对c/s/d/u类型无影响 对o类型在输出时加前缀o,对x类型输出时加前缀0x

printf("%.2f", a); 保留2位小数

printf("%.*f", n, a); 动态指定保留小数位数n

int a = 1;
int b = 10;
printf("a = |%5d|\n", a);    //  |    1|
printf("a = |%-5d|\n", a);   //  |1    |
printf("a = |%+d|\n", a);    //  |+1|
printf("a = |%05d|\n", a);   //  |00001|
printf("a = |% d|\n", a);    //  | 1|
printf("b = %#x\n", b);      //  0xa

double c = 3.1415;
printf("c = %.2f\n", c);     //  3.14
printf("c = %.*f\n", 3, c);  //  3.142 
Scanf函数

scanf函数用于接收键入的内容,是阻塞式函数(程序会停在scanf出现的地方,知道接收到数据才会执行后面的代码)

scanf运行原理:系统会将用户输入的内容先放入输入缓冲区,scanf方式会从输入缓冲区中逐个取出内容赋值给变量,如果输入缓冲区内容不为空,scanf会一直从缓冲区中获取,而不要求再次输入

//  scanf("格式控制字符串", 地址列表);
int number;
scanf("%d", &number);  // 接收一个整数
printf("number = %d\n", number);  // 键入2就输出2,不键入输出0

int str;
scanf("%c", &str);  // 接收一个字符
printf("str = %c\n", str);

int num2, str2;
scanf("%d,%c", &num2, &str2); // 键入 44,d
printf("%d,%c", num2, str2);  // 打印 44,d
  • 接收非字符和字符串类型时,空格、tab、回车会被忽略
  • \n是scanf函数的结束符号,所以scanf函数中格式化字符串中不能出现\n
不可改变常量

常量: 在程序执行过程中,值不发生改变的量

c语言的常量分为 直接常量和符号常量

  1. 直接常量(字面量): 可以直接拿来使用无需说明的量
  • 整型常量: 13、0
  • 实型常量: 13.33
  • 字符常量: ‘a’
  • 字符串常量: “Hello World!”
  1. **符号常量: 用一个标识符来表示一个常量。符号常量使用前必须先定义。**且不可被改变

    由于预先对标识符进行了定义,在程序的预处理阶段, 编译器会将所有定义的标识符替换成相应的内容并生成 .i 文件

//  #define 标识符 常量值
//  #define N 100
//  编译器在预处理程序时,会将程序中所有的N用100来替换。换言之,不同于const常量,define本质上是对文本内容的替换

// #define MAX 100         // 将 MAX 这个标识符和 100 这个数字关联起来
// #define REG register     // 为 register 这个关键字,创建一个简短的名字REG
// #define STR "test_string"  // 用 STR 这样一个名字来代替 test_string 这样一个字符串


#include <stdio.h>
#define PI 3.14 
#define S(r) PI*r*r  // 定义在主函数前 不要加分号
int main(void)
{
	printf("area = %f", S(1+2));  // 3.14*1 + 2*1 + 2 = 7.14 直接把r替换成1+2不用加括号
    // 这实际上,宏就是替换,并且是相当直接的替换
    return 0;
}

​ #define定义标识 符的本质就是替换

​ define 定义表达式时要注意**“边缘效应”**

#define N 1+2
float a = N/2.0;
/*
按照常规做法,可能会认为结果是3/2 = 1.5
但是实际上,结果应该为 1+2/2.0 = 2.0

若想要实现3/2,则#define N (1+2)
即为避免边缘效应,一定要加!括!号!
*/

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏 (define macro)

#define 定义宏可分为两种

  1. 一种是不带参数的宏定义,这也就是第1小节提到的使用#define定义标识符
  2. 第二种是带参数的宏定义,其定义格式如下
//  #定义  宏名(参数表)  内容
#define name( parament-list ) stuff

其中,parament-list是一个由逗号隔开的符号表,它们可能出现在stuff中

#define ADD(a, b) (a + b) // 宏的参数有两个a和b 这个宏所要实现的功能是将a和b相加

宏的使用和C语言中的函数类似,都需要进行传参。在main函数中两者的实现方式几乎是一样的,但具体的实现方式却很不一样

int ADD(int x, int y)
{
    return x + y;
}
// #define ADD(a, b) a + b

int main()
{
    // ...
}

宏定义的程序中,预处理器会将ADD(a, b)替换成(a + b)。宏定义是将a+b这样一个求和操作重新命名并置于main函数中,而ADD函数是通过函数调用来实现求和,使用了新的函数栈帧

#define替换规则:

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被它们的值所替换
  3. 最后再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号,如果是,就重复上述过程

注意:

  • 参数列表的左括号必须与name紧邻。如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
  • 宏参数和#define定义中 可以出现其他#define定义的符号
  • 对于宏,不能出现递归
  • 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

宏和函数的对比请添加图片描述

  • 宏名全部大写,函数名不要全部大写
  • 宏比函数在程序的规模和速度方面更甚一筹
  • 函数的参数必须声明为特定的类型,所以函数只能在类型合适的表达式上使用。宏是类型无关的,不够严谨
  • 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度
  • 宏无法调试
自动类型转换

自动转换发生在不同数据类型运算时,在编译的时候自动完成

自动转换

char转换为int遵循ASCII码中的对应值

  • 字节小的可以向字节大的自动转换,但字节大的不能向字节小的自动转换
  • char -> int int -> double char -> double
强制类型转换

​ 通过定义类型转换运算来实现 一般形式为:

// (数据类型) (表达式)

其作用是把表达式的运算结果强制转换成类型说明符所表示的类型

注意:

  • 数据类型和表达式都必须加括号。如把(int)(x/2+y)写成(int)x/2+y则成了把x转换成int型之后再除2再与y相加了
  • 转换后不会改变原数据的类型及变量值,只在本次运算中临时性转换
  • 强制转换后的运算结果不遵循四舍五入的原则
运算符号

c语言中的运算符: 算术、赋值、关系、逻辑、三目运算符

算术运算符:+ - * / % ++ - -

注意:

  • 除法中 两个数有一个为小数则结果为小数 如 9.0/2 = 4.500000
  • 取余中 运算后的符号取决于被模数的符号 如(-10)%3 = -1,10%(-3) = 1

关系运算符: > >= == !=

逻辑运算符: &&逻辑与 ||逻辑或 !逻辑非

逻辑运算的值也是有两种分别为真和假,C语言中用整型的1和0来表示

循环结构之for循环
for(表达式1; 表达式2; 表达式3)
{
    // 执行代码块
}

注意:

  • 表达式123均可不写为空 但分号不能省
  • 省略表达式2(循环条件) 或 表达式3(循环变量增减量) 死循环
如何获得一个数的百位十位个位
  • 百位数: num/100 因为int整数型 765/100=7
  • 十位数: num%100/10 765%100 = 65 65/10 = 6
  • 个位数: num%10 765%10 = 5
break与continue
  • 在没有循环结构的情况下,break不能用在单独的ifelse语句中
  • 在多层循环中,一个break只跳出当前循环
switch
switch(表达式){
    case 常量表达式1: 执行代码块1 break;
      ……
    case 常量表达式n: 执行代码块n break;
    default:  执行代码块n+1break;
}
  • switch后面的表达式语句只能是整型或字符类型
  • case后面允许有多个语句,可以不用{}括起来
  • 各case和default子句的先后顺序可以变动,不会影响程序执行结果
  • default子句可以省略不用
函数

定义: 函数是子程序,子程序是一个大型程序中的某部分代码,它相较于其他代码具备相对的独立性。一般会由输入参数并由返回值,提供对过程的封装和细节的隐藏

C语言提供了大量库函数,比如stdio.h提供输出函数

自定义函数的一般形式:

[数据类型说明] 函数名称 ([参数])
{
    执行代码块;
    return (表达式);
}

int main(){...}
  • [] 包含的内容(数据类型说明)可以省略 省略了默认是int类型函数
  • 参数省略表示该函数是无参函数,参数不省略表示该函数是有参函数
  • 自定义函数尽量放在main函数前。如果要放在main函数后面的话,需要在main函数之前先声明自定义函数,声明格式为[数据类型说明] 函数名称 ([参数])
函数调用
函数名 ([参数]);
  • 对无参函数的调用可省略[ ]中的内容
  • []中可以是常数,变量或其他构造类型数据及表达式,多个参数之间用逗号分隔
有参与无参
// 无参函数
[数据类型说明] 函数名称 ()
{
    // ...
    return 表达式;
}

// 有参函数
[数据类型说明] 函数名称 (参数列表)  // 有参无参唯一区别在于()中多了 参数列表
{
    // ... 
    return 表达式;
}

有参函数相对灵活,输出内容可以随着n的变化随意变动,只要在main函数中传递一个参数就可以了。而无参函数中输出的相对固定,当需要改动的时候还需要到自定义方法内改变循环变量的值

形参和实参

形参:目的是用来接收调用该函数时传入的参数。只有在被调用时才分配内存单元,在调用结束时即刻释放所分配的内存单元。因此,形参只有在函数内部有效

实参:调用时传递该函数的参数。实参可以是常量、变量、表达式、函数等。应预先用赋值等办法使实参获得确定值。

在参数传递,实参和形参在数量上、类型上、顺序上应严格一致,否则会发生类型不匹配的错误

#include <stdio.h>
int Function(int x) {  // x是形参
   	return x*2;
}
int main() {
    int x = 10;  // 定义变量x
    // 下面的x是实参 它其实是main函数中定义的变量
    printf("x=%d\n", Function(x));
    return 0;
}
函数的返回值

返回值:函数被调用后,执行函数体中的程序段所取得的 并返回给主调函数的值

  • 函数的值只能通过return语句返回主调函数
  • 函数值的类型和函数定义中函数的类型应保持一致,若不一致则以函数返回类型为准
  • 没有返回值的函数,返回类型为void
  • void函数中可以有执行代码块,但不能有返回值。void函数中如果有return语句,该语句只能起到结束函数运行的功能,其格式为return;
递归函数

​ 递归就是一个函数在它的函数体内调用自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层。递归函数必须有结束条件

请添加图片描述

递归函数特点:

  • 每一级函数调用时都有自己的变量,但是函数代码并不会得到复制。如计算5的阶乘时每递推一次变量都不同
  • 每次调用时都会有一次返回,如计算5的阶乘时每递推一次都返回进行下一次
  • 递归函数中,位于递归调用前的语句和各级被调用函数具有相同的执行顺序
  • 递归函数中,位于递归调用后的语句的执行顺序和各个被调用函数的顺序相反
  • 递归函数中必须有终止语句

因此,递归函数是 自我调用且有完成状态

// 递归demo  有5个人坐在一起,问第5个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第3个人,又说比第2人大两岁。问第2个人,说比第1个人大两岁。最后 问第1个人,他说是10岁。请问第5个人多大?
// 程序分析:利用递归的方法,递归分为回推和递推两个阶段。要想知道第5个人岁数,需知道第4人的岁数,依次类推,推到第1人(10岁),再往回推。

#include <stdio.h> 
int dfs(int n) {
    return n == 1 ? 10 : dfs(n - 1) + 2;
}
int main() 
{ 
	printf("第5个人的年龄是%d岁", dfs(5)); 
	return 0;
} 

局部与全局

变量按作用域范围可分为局部变量和全局变量

  • 局部变量(内部变量)作用域仅限于函数内,离开该函数后再使用这种变量是非法的
  • 全局变量(外部变量)定义在函数外部的变量,它不属于任何函数,它属于一个源程序文件,其作用域是整个源程序

在所有函数之外声明的函数叫做全局变量

变量存储类别

C语言根据变量的生存周期来划分,可以分为静态存储方式和动态存储方式

  • 静态存储方式:在程序运行期间分配固定的存储空间。静态存储区中存放了在整个程序执行过程中都存在的变量,如全局变量。静态存储是只读的,存放的是指令
  • 动态存储方式:在程序运行期间根据需要进行动态分配存储空间。动态存储区中存放的变量是根据程序运行的需要而建立和释放的,通常包括:函数形式参数、自动变量、函数调用时的现场保护和返回地址等

C语言中存储类别又分为四类:

  • 自动(auto)
  • 静态(static)
  • 寄存器的(register)
  • 外部的(extern)
  1. 用关键字auto定义的变量为自动变量,auto可省略,auto不写则隐含定为“自动存储类别",属于动态存储方式

int fn(int a) {   // 定义fn函数, a为参数
    auto int b, c;  // 定义b,c为 自动变量
}
  1. 用static修饰的为静态变量,如果定义在函数内部称为静态局部变量,定义在函数外部称为静态外部变量

    #include <stdio.h>  
    void fn()
    {
        static int i = 0;   // static修饰局部变量
        i++;
        printf("%d\n", i);
    }
    int main()
    {
        int i;
        for(i=0; i<10; i++)
        {
            fn();
        }
        return 0;
    }
    // 输出: 1 2 3 ... 10
    // 函数内的局部变量并没有每次调用都是为变量i开辟内存空间,而是将变量i的值保存了下来
    

    由此可见:static修饰局部变量改变了变量的生命周期,让静态局部变量超出了作用域后依然存在,直到程序结束时,生命周期才结束

    注意:静态局部变量属于静态存储类别,在静态存储区内分配存储单元在程序整个运行期间都不释放;静态局部变量在编译时赋初值,即只赋初值一次;如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)

    另外,static修饰全局变量和修饰函数的时候,这个全局变量/函数只能在本源文件中使用,不能在其他源文件内使用

  2. 为了提高效率,C语言允许将局部变量的值存放在CPU的寄存器内,这种变量叫做“寄存器变量(register)”

    void fn() {
        register int i;  // 定义i为寄存器类型变量
    }
    

    只有局部自动变量和形式参数可以作为寄存器变量;不能定义任意多个寄存器变量;局部静态变量不能定义为寄存器变量

  3. 外部变量用extern声明,外部变量的意义是某函数可以调用在该函数之后定义的变量

    #include <stdio.h>
    int main()
    {
        extern int x;  // 这里声明使用的是外部全局变量
        printf("extern x=%d\n", x);
        return 0;
    }
    int x = 100;
    
内部函数与外部函数
  • 在C语言中不能被其他源文件调用的函数称为内部函数,内部函数由static关键字定义,又被称为静态函数,static[数据类型]函数名([参数])
  • static是对函数的作用范围的一个限定,限定该函数只能在其所处的源文件中使用,因此在不同文件中出现相同的函数名称的内部函数是没有问题的
  • 在C语言中能被其他源文件调用的函数称为外部函数,外部函数由extern定义,形式同上
  • 在没有指定函数的作用范围时,系统默认是外部函数,因此当需要定义外部函数时extern也可以省略
// 外部函数demo
// main.c
#include <stdio.h>
void main() {
    extern void test();  // 调用在其他函数中定义的三个函数
    ...
}

    
// test.c
#include <stdio.h>
void test() {
    // 定义外部函数test
}
数组
数组初始化

数组在程序中是一块连续的,大小固定并且里面的数据类型一致的内存空间数组初始化的语法:

int arr[3] = {1, 2, 3};
int arr2[] = {1, 2, 3};
// 获取数组元素:arr[0];
  • 数组初始化时,数组内元素个数 <= 声明的数组长度,并且多余的数组元素初始化为0
  • 在声明数组后没有进行初始化的时候,静态(static)和外部(extern)类型的数组元素初始化元素为0,自动(auto)类型的数组的元素初始化值不确定
数组的遍历
int arr(3) = {1, 2, 3};
int i;
for (i=0; i<=2; i++) 
{
    printf("%d\n", arr[i]);
}
return 0;
  • 循环变量不要超出数组的长度
  • C语言的数组长度一经声明就是固定,无法改变
  • C语言不提供计算数组长度的方法
// 获取数组长度
int length = sizeof(arr)/sizeof(arr[0]);
数组作为函数参数

数组可以由整个数组当作函数的参数,也可以由数组中的某个元素当作函数的参数

// 整个数组当作函数参数 即**把数组名称传入函数**中
#include <stdio.h>
void temp(int arr[]) {   // 函数没有返回值或函数无参数 声明为void h
    int i;
    for (i=0; i<=4; i++) {
        printf("%d\n", arr[i]);
    }
}
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    temp(arr);  // 传数组
    return 0;
}
// 数组中的元素当作函数参数,即把数组中参数传入函数中
#include <stdio.h>
void temp(int arrValue) {
    printf("%d\n", arrValue);
}
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    temp(arr[3]);  // 传数组中的参数
    return 0;
}
  • 数组名作为函数实参传递时,函数定义处 作为接收参数的数组类型形参 既可以指定长度也可以不指定长度
  • 数组元素作为函数实参传递时,数组元素类型必须与形参数据类型一致

例1:冒泡排序

// 冒泡排序
#include<stdio.h>

void Function(int arr[]) {
    int i, j, length;
    length = sizeof(arr);
    printf("arr's length is %d", length);
    printf("\nbefore: ");
    for(i=0; i<length; i++) {
        if(i != length-1) {
            printf("%d, ", arr[i]);
        } else {
            printf("%d", arr[i]);
        }
    }
    for(i=0; i<length-1; i++) {
        for(j=0; j<=i; j++) {
            if(arr[j]<arr[j+1]) {
                int temp;
                 temp = arr[j];
                 arr[j] = arr[j+1];
                 arr[j+1] = temp;
            }
        }
    }
    printf("\nafter: ");
    for(i=0; i<length; i++) {
        if(i != length-1) {
            printf("%d, ", arr[i]);
        } else {
            printf("%d", arr[i]);
        }
    }
}

int main()
{
    int arrTest[] = {0, 5, 6, 2, 9, 4, 3, 1};
    Function(arrTest);
	return 0;
}

例2:数组查找

#include <stdio.h>
int getIndex(int arr[5], int value) {  // value是要查询的元素
    int i, index;  // index将是查询元素的下标 元素不存在则赋值-1
    for(i=0; i<5; i++) {
        if(arr[i]==value) {  // 某个元素==查询的元素
            index = i;  // 获得查询元素的下标
            break;
        }
        index = -1;
    }
    return index;
}

int main() {
    int arr[5]={3,14,64,2,8};  // 实参1
    int value = 8;  // 实参2
    int index = getIndex(arr, value);  //  调用函数并传参:声明返回值=函数(实参)
    if(index!=-1) 
        printf("%d存在, 下标为%d\n", value, index);
    else
        printf("%d不存在\n", value);
    return 0;
}
字符串与数组

C语言中无法直接定义字符串数据类型,但是我们可以使用数组定义我们想要的字符串

// 定义
char stringName[length] = "字符串值";
char stringName[length] = {'字符1', '字符2', '字符n', '\0'};
// 输出
printf("%s", stringName);
puts(stringName);
  • []中的length可以省略
  • 采用第二种方式时最后一个元素必须是’\0’,'\0’表示字符串的结束标志
  • 采用第二种方式时数组种不能写中文
字符常量和字符串常量

C语言中用单引号表示字符常量,双引号表示字符串常量

字符常量: 用一对单引号括起来的一个字符,如’a’, ‘9’, ‘!’ ,字符常量中的单引号只起定界作用,并不表示字符本身。一个字符常量是一个整数

字符串常量: 字符串常量是用双引号括起来的多个字符的序列,字符串本质上是多个字符组成的字符数组。在每个字符串常量的结尾,系统都会自动加一个字符’\0’作为该字符串的结束标识符,系统据此判断字符串是否结束

字符串函数
  1. strlen(s) 获取字符串长度
// 注意: 字符串中 汉字和字母的长度不一样
char str1[] = "栖夜";
char str2[] = {'i', 'j', 'k', '\0'};
printf("两者长度分别为%d\t%d\n", strlen(str1), strlen(str2));  // 6  3
  1. strcmp(s1, s2) 把字符串转换成ASCII码后比较

    返回值0 表示s1和s2的ASCII码相等

    返回值1 表示s1比s2的ASCII码大

    返回值-1 表示s1比s2的ASCII码小

char str1[] = "a";
char str2[] = "b";
printf("结果是%d\n", strcmp(str1, str2));   // -1
  1. strcpy(s1, s2) 字符串拷贝 拷贝后会覆盖原字符串且不能对字符串常量进行拷贝
char str1[] = "js";
printf("%s\n", strcpy(str1, "vue"));  // vue

char str2[] = "C语言";
strcpy(str2, "C++");
printf("%s\n", str2);  // c++
  1. strcat(s1, s2) 把字符串s2拼接到s1后。strcat在使用时s1与s2指的内存空间不能重叠,且s1要有足够的空间来容纳要复制的字符串
char str1[] = "hello ";
char str2[] = "world";
strcat(str1, str2);
printf("%s", str1);  // hello world
多维数组

定义一个二维数组 其中第一个[3]表示第一维下标的长度 第二个[3]表示第二维下标的长度

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

请添加图片描述

多维数组初始化必须指定列的维数,因为系统会根据数组中元素的总数来分配空间,当知道元素总数以及列的维数时,会自动计算出行的维数

多维数组遍历(循环嵌套)

综合练习

#include <stdio.h>
#define N 10
// 打印分数
void printScore(int score[])  // void函数中不需要有返回值
{
    int i;
    printf("\n");
    for(i=0; i<N; i++)
    {
        printf("%d ", score[i]);
    }
    printf("\n");
}
// 计算总分
int getTotalScore(int score[])
{
    int sum = 0;
    int i;
    for(i=0; i<N; i++)
    {
        sum += score[i];
    }
    return sum;
}
// 计算平均分
int getAvgScore(int score[])
{
    return getTotalScore(score)/N;
}
// 计算最高分
int getMax(int score[])
{
    int max = -1;
    int i;
    for(i=0; i<N; i++)
    {
        if(score[i]>max)
        {
            max = score[i];
        }
    }
    return max;
}
// 最低分
int getMin(int score[])
{
    int min = 100;
    int i;
    for(i=0; i<N; i++)
    {
        if(score[i]<min)
        {
            min = score[i];
        }
    }
    return min;
}
// 分数降序排序
void sort(int score[])
{
    int i, j;
    for(i=0; i<N-2; i++)
    {
        for(j=0; j<=i; j++)
        {
            if(score[j]<score[j+1])
                {
                    int temp;
                    temp = score[j];
                    score[j] = score[j+1];
                    score[j+1] = temp;
                }
        }
    }
    printScore(score);
}


int main()
{
    int score[N]={67,98,75,63,82,79,81,91,66,84};
    int sum, avg, max, min;
    sum = getTotalScore(score);
    avg = getAvgScore(score);
    max = getMax(score);
    min = getMin(score);
    printf("总分是: %d\n", sum);
    printf("平均分是: %d\n", avg);
    printf("最高分是: %d\n", max);
    printf("最低分是: %d\n", min);
    printf("------------成绩排名------------\n");
    sort(score);
    return 0;
}

更新于2023.3.14

(未完待续…下一章:指针)

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LeonardoSya

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

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

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

打赏作者

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

抵扣说明:

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

余额充值