【C语言】基本语法知识&&C语言函数&&操作符详解
【C语言】基本语法知识&&C语言函数&&操作符详解
主页:醋溜马桶圈-CSDN博客
目录
7.1.1.3 多分支if-else if-...-else
1.基本语法
首先介绍一下我使用的工具:VS2019——集成了很多的功能:编辑、编译、链接、运行、调试等
了解:写C语言代码其实写出来的是一个.c的文本文件,本身是不能运行的,是需要经过编译、链接、运行等一系列操作,最终生成一个.exe的可执行文件。一般.c称为源文件,.exe称为可执行文件。
在VS上写代码:
1.创建项目:如下
2.新建源文件 .c文件
3.写代码
该行代码表示:在控制台上打印The First C
源文件可以用记事本,用VS,各种查看软件打开,但要想看到运行效果,就必须得编译、链接、运行,VS的快捷键是ctrl+F5,或者点击下图中按键
效果如下
1.1 代码解释
#define _CRT_SECURE_NO_WARNINGS 1
//scanf函数接受键盘的输入
#include "stdio.h"
//main()主函数
int main()
{
printf("The First C");
return 0;
}
1.1.1 main()主函数
所有的代码都是从main()函数开始执行的,main()函数是程序的入口,C语言成千上万行的代码都是从main()函数的第一行开始执行的,每一个程序代码都必须有main()函数,且一个程序中只能有一个main()函数,有且仅有一个
1.1.2 int
整型的意思,表示main()函数执行完之后,返回一个整型(int)
1.1.3 { }
大括号内表示函数体,main()函数也是函数,是有函数体的,在大括号内写代码
1.1.4 printf()库函数
printf()是库函数,是C语言标准库中提供的一个现成的函数,可以直接使用,printf()函数的功能是在屏幕上打印信息,在” “内部写需要打印的信息
1.1.5 stdio.h头文件
库函数的使用是需要包含头文件的,printf()函数需要的头文件是stdio.h
用#include"----.h"或#include<----.h>的方式包含一个头文件
注意:C语言中每一行代码都是英文的,包括符号
1.2 C语言的数据类型
为什么要写程序呢?
其实写程序是为了用程序解决生活中的一些问题
首先要描述这个问题,例如网上商城:描述商品:名字、定价、优惠
C语言要能描述这些信息,就得有数据类型
C语言中提供的基本数据类型有 :
char //字符数据类型
short //短整型
int //整型
long //长整型
long long //更长的整型
float //单精度浮点型(小数)
double //双精度浮点型(小数)
为什么叫做浮点数呢,举个例子:123.45是一个小数,使用科学记数法可以表示为12.345*10^1或1.2345*10^2,小数点是可以浮动的
计算数据类型的长度 sizeof()操作符:计算()里面的大小
sizeof()计算的单位是字节
计算机中常见的单位:
bit(比特位),byte(字节),KB,MB,GB,TB,PB......
1byte=8bit,1KB=1024byte,1MB=1024KB......
#define _CRT_SECURE_NO_WARNINGS 1
#include "stdio.h"
int main()
{
//%d以十进制的形式打印整数
//'\n'表示换行
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
return 0;
}
2.变量、常量
2.1 变量的概念
类型是用来创建变量的;
例如:int num=0;
char ch='b';
不变的值——常量;变化的值——变量
2.1.1 变量的定义方法
变量的定义方法:类型 变量名
举个例子
int age=150;
float weight=45.5f;
//如果直接写45.5,VS会直接认为是double类型
//要是float类型,就要加上f
char ch='w'
2.1.2 变量的分类
变量有局部变量跟全局变量
- 局部变量 :"在函数内定义的变量", 即在一个函数内部定义的变量,只在本函数范围内有效。
- 全局变量 :"在函数外定义的变量", 即从定义变量的位置到本源文件结束都有效。
简单说,在{}内定义的就是局部变量,在{}外定义的就是全局变量
当局部变量和全局变量在一个地方都可以使用的时候,局部优先
#define _CRT_SECURE_NO_WARNINGS 1
#include "stdio.h"
int a = 100;//定义全局变量
int main()
{
int a = 50;//定义局部变量
printf("%d\n", a);
return 0;
}
2.2 常量的概念
常量,顾名思义就是不变的量,跟变量刚好相反
C语言中常量有以下几种
- 字面常量
- const修饰的常变量
- #define定义的标识符变量
- 枚举常量
2.2.1 字面常量
直接写出的值就是字面常量;例如:100、20.0、3.14、'a' 等等
2.2.2 const修饰的常变量
这里的常,表示的是不变的意思。const是常属性,可以用来修饰变量
通过这段代码可以看出,经过const修饰的变量,就不能再被修改了
这里,我们称b就是const修饰的常变量,具有了常属性;b本质上还是一个变量,只不过它被const在语法层面上加了一层限制,具有了常属性
cons是在语法层面加上的一层限制,就是告诉我们,该变量的值不能再被修改了
2.2.3 #define定义的标识符常量
运行结果如下
通过代码,我们能很明显地感受到#define定义的是一个常量
2.2.4 枚举常量
枚举的意思就是一一列举
生活中有一部分数据是可以一一列举出来的:例如三原色、性别、星期、月份等等。
C语言中就给出一个枚举类型,可以把这些数据一一列举出来,比如下面这段代码:
enum Color//自定义的一个枚举类型
{
RED,
GREEN,
BLUE//枚举常量
//即该枚举类型的取值只有{}内的三种
};
这就是一个枚举类型,而枚举常量的取值是从0开始依次往下排,通过下面这段代码的执行效果就可以感受到
#define _CRT_SECURE_NO_WARNINGS 1
#include "stdio.h"
enum Color//自定义的一个枚举类型
{
RED,
GREEN,
BLUE//枚举常量
//即该枚举类型的取值只有{}内的三种
};
int main()
{
enum Color a = RED;
enum Color b = GREEN;
enum Color c = BLUE;
printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
return 0;
}
所以,枚举常量其实就是枚举类型可能的取值,{ }内一一列举出来的
3.生命周期、作用域
3.1 作用域
作用域:限定该变量可以使用的范围
- 局部变量的作用域是变量所在的局部范围
- 全局变量的作用域是整个工程
#define _CRT_SECURE_NO_WARNINGS 1
#include "stdio.h"
int main() {
{
int a = 10;
printf("%d\n", a);//a在{}内部创建,可以在{}内部使用
}
return 0;
}
运行结果是
如果将代码改成下面这样,结果会怎么样呢
#define _CRT_SECURE_NO_WARNINGS 1
#include "stdio.h"
int main() {
{
int a = 10;
}
printf("%d\n", a);//a在{}内部创建,不可以在{}外部使用
return 0;
}
该段代码运行结果是
原因是printf()函数使用时,超出了a的作用域,a的作用域在定义int a 的大括号内部
这是局部变量作用域的展示
接下来是全局变量的作用域
#define _CRT_SECURE_NO_WARNINGS 1
#include "stdio.h"
int a = 100;
void test()
{
printf("test()--->%d\n", a);//定义test()函数,打印出“”内的内容
}
int main() {
printf("%d\n", a);//定义a为全局变量,可以在整个工程中都使用
test();
return 0;
}
该段代码的效果如下
可见,全局变量的作用域是整个工程
3.2 生命周期
变量的生命周期是指:变量的创建到变量的销毁之间的一个时间段
1.局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束
2.全局变量的生命周期:整个程序的生命周期
#define _CRT_SECURE_NO_WARNINGS 1
#include "stdio.h"
int a = 100;
int main() {
printf("%d\n", a);
printf("%d\n", a);
return 0;
}
该段代码的效果如下
上述代码就是全局变量a的生命周期,是整个程序的生命周期
#define _CRT_SECURE_NO_WARNINGS 1
#include "stdio.h"
int main() {
int a = 10;
printf("%d\n", a);
a = 20;
printf("%d\n", a);
return 0;
}
上述代码是局部变量的生命周期,第一个a在第四行被定义,在第五行被执行并销毁,这就是他的生命周期;同理,第二个a在第六行被定义,在第七行被执行并销毁,这是第二个a的生命周期。
下面是该段代码的执行效果
4.字符、字符串
C语言中有字符(char)类型,但是没有字符串类型;那么在C语言在如何表示字符串呢
4.1 字符串的表示
"hello world\n"
像这样,用" "引起来的一串字符称为字符串;字符串的结束标志是一个'\0'的转义字符。
字符串其实就是一串字符,字符串是可以存放到字符数组里边的。
#include 'stdio.h'
int main(){
char arr1[] = "abc";//直接用" "引起来
char arr2[] = { 'a','b','c' };//字符用{}括起来,中间用逗号隔开
char arr3[] = { 'a','b','c','\0' };
return 0;
}
4.2 字符串的结束标志
在每个字符串的末尾都隐藏了一个'\0',利用VS2019中的监视(调试---窗口---监视)功能可以看出
当一个字符串真正遇到'\0'的时候,才认为这个字符串结束了;如下段代码的显示效果
#define _CRT_SECURE_NO_WARNINGS 1
#include "stdio.h"
int main()
{
char arr1[] = "abc";//直接用" "引起来
char arr2[] = { 'a','b','c' };//字符用{}括起来,中间用逗号隔开
char arr3[] = { 'a','b','c','\0' };
printf("%s\n", arr1);//打印字符串用%s
printf("%s\n", arr2);
printf("%s\n", arr3);
return 0;
}
4.3 字符串的长度
注意:在计算字符串长度的时候'\0'是结束标志,不算作字符串的内容。
strlen( )函数是一个库函数,用来求字符串长度,统计的是字符串中'\0'之前的字符个数
我们可以利用strlen( )函数计算字符串的长度;如下段代码效果
'\0'之前的字符个数就是该字符串的长度,而arr2[ ]没有'\0',所以他的长度是随机的
注意:使用strlen( )函数的时候,需要头文件 #include "string.h"
4.4 转义字符
4.4.1 常用转义字符
这里我们解释一下'\\',他的效果是在屏幕上输出一个 "\";如下图代码效果
这里我们很明显能感受到,需要打印出"\",就得使用'\\'转义字符
4.4.2 特殊转义字符
\xhh:
\x后面跟两位十六进制数,该两位十六进制数的值即为对应字符的十六进制ASCII码值。
\ddd:
斜杠后面跟三位八进制数,该三位八进制数的值即为对应的八进制ASCII码值。
4.4.3 转义字符的大小
一个转义字符就算作一个字符,因此一个转义字符的长度为1
5.注释
5.1 什么是注释
为了方便看懂代码,给代码的一段说明性文字;在编写C语言源代码时,应该多使用注释,这样有助于对代码的理解。在C语言中有两种注释方式:
- 一种是以
/*
开始、以*/
结束的块注释(block comment); - 另一种是以
//
开始、以换行符结束的单行注释(line comment)。
5.2 注释原则
- 项目开发中,尽量保持代码注释规范和统一。
- 注释方便了代码的阅读和维护。
- 边写代码边注释,修改代码时要相应修改注释,保证注释和代码的一致性。
- 注释要简洁明确,不要出现形容词。
- 通过注释可以快速知道所写函数的功能,返回值,参数的使用。
6.ASCII表
6.1 ASCII值
6.1.1产生原因
在计算机中,所有的数据在存储和运算时都要使用二进制数表示(因为计算机用高电平和低电平分别表示1和0),例如,像a、b、c、d这样的52个字母(包括大写)以及0、1等数字还有一些常用的符号(例如*、#、@等)在计算机中存储时也要使用二进制数来表示,而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则,于是美国有关的标准化组织就出台了ASCII编码
6.1.2表达方式
ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符
- 0~31及127(共33个)是控制字符或通信专用字符(即不可打印字符)(其余为可显示字符)
- 32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字
- 65~90为26个大写英文字母
- 97~122号为26个小写英文字母
- 其余为一些标点符号、运算符号等
- 后128个称为扩展ASCII码。许多基于x86的系统都支持使用扩展(或“高”)ASCII。扩展ASCII 码允许将每个字符的第8 位用于确定附加的128 个特殊符号字符、外来语字母和图形符号
有人将 ASCII 编码分成两部分:
- 前 128 个字符称为基本 ASCII,包含常见字符;
- 后 128 个字符称为扩展 ASCII,包含一些特殊字符。
6.2 特殊的ASCII值
ASCII表中可以记下部分特殊的值(字母从A到Z,从a到z,ASCII值依次递增)
- ‘A’:65
- ‘a’:97
- ‘0’(字符0):48
- 0(数字0):0
6.3 大小写转换小妙招
由于ASCII表中的大小写字母对应的ASCII值相差32,所以我们在编写大小写转换程序的时候,就非常便捷
- 大写转小写:ASCII值+32
- 小写转大写:ASCII值-32
7.语句&&结构
C语言是一种结构化的程序设计语言,它有三种结构:顺序结构,选择结构,循环结构
- 顺序结构:简单说就是从头到尾,一条路走到头
- 选择结构:简单说就是到一个路口进行多个选择
- 循环结构:简单说就是循环执行一段语句
生活中的任何一件事都可以抽象成这三种结构中的一种或者组合,所以用C语言来写代码,用这三种结构,基本就可以描述生活中的每一个场景了
7.1 选择结构
C语言中提供了两种选择语句
- if语句
- switch语句
7.1.1 if语句
7.1.1.1 if-else语句
if (表达式)
语句1;
else
语句2;
如果满足表达式,即表达式为真,则执行语句1;
若不满足表达式,即表达式为假,则执行语句2。
那何为真假呢?
0表示假,非0表示真
7.1.1.2 简单的代码示例
if就是是否、如果的意思,表示判断,比如下面这段代码,他的意思是
判断a的值是1还是0
- 如果a等于1,则printf("提升自己\n");
- 否则(如果a等于0),则printf("无法就业\n");
应该是很容易理解的
#define _CRT_SECURE_NO_WARNINGS 1
#include "stdio.h"
int main()
{
int a;
printf("恭喜进入大学\n");
printf("你打算好好学习吗?\n");
printf("1.好好学习 0.不好好学习\n");
scanf("%d", &a);
if (a==1)//判断a的值
{
printf("提升自己\n");
}
else
{
printf("无法就业\n");
}
return 0;
}
7.1.1.3 多分支if-else if-...-else
//多分支
if (表达式1)
语句1;
else if (表达式2)
语句2;
else
语句3;
这是if-else语句的升级版,多了几个分支,同样如果表达式1为真,则执行语句1;表达式2为真,则执行语句2;否则,执行语句3
当然,依照这样的代码,根据选择条件,我们可以有若干个分支
7.1.1.4 单条语句与多条语句
当我们要写多条语句时,要用到{},将代码括起来。如下
if (a==1)
{
printf("提升自己\n");
printf("成为大佬\n");
}
else
{
printf("自暴自弃");
printf("自暴自弃");
}
注意:else的匹配:else是和它离的最近的if匹配的
7.1.1.5 注意
多分支语句的执行顺序
- 从上到下依次判断,若符合 if 条件,就执行if下面的语句;若不符合 if ,就看是否满足下一条 else if 的条件;直到找出符合条件的为止,若所有条件都不符合,就执行 else 的语句。
- if的表达式为判断是否相等,判断是否相等的符号为"=="而不是"="
if (a=1)
这样一来就不是判断了,而是将1赋值给a!!!
因此我们要写成下面这样
if (a==1)
7.1.2 switch语句
switch语句从字面上讲,可以称为开关语句,是一种多分支选择结构,一般与case、break、default配合使用,对流程进行控制。
7.1.2.1 switch-case语句
switch(表达式){
case 常量表达式1: 语句1;
case 常量表达式2: 语句2;
……
case 常量表达式n: 语句n;
default: 语句n+1;
}
其中switch、case、break、default都是关键词。
switch作为一个开关
- 当变量表达式的值对应case中的值时,执行case后面的语句后跳出switch语句
- 如果都不符合则执行default后面的语句后跳出switch语句
7.1.2.2 简单的代码示例
switch
语句常常用于多分支的情况。
这种多分支,一般指的是很多很多分支,用if-else来书写非常麻烦,且判定条件主要以整型为主:
如:输入数字,输出相应的星期几
#include <stdio.h>
int main()
{
int day = 0;
printf("请输入:");
scanf("%d", &day);
switch (day) {
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五\n");
break;
case 6:
printf("星期六\n");
break;
case 7:
printf("星期天\n");
break;
default:
printf("Error\n");
}
return 0;
}
7.1.2.3 switch语句规则
switch语句非常有用,但在使用时必须谨慎。所写的任何switch语句都必须遵循以下规则:
- 只能针对基本数据类型中的整型类型使用switch,这些类型包括int、char等。对于其他类型,则不能使用switch语句。
- switch()的参数类型不能为实型 。因为switch后面只能带自动转换为整形(包括整形)的类型,比如字符型char,unsigned int等,实数型不能自动转换为整形。
- case标签必须是常量表达式(constantExpression),如62或者’8’等。
- case标签必须是唯一性的表达式;也就是说,不允许两个case具有相同的值。
- 在case后,允许有多个语句,可以不用{}括起来。
- 各case和default句的先后顺序可以变动,而不会影响程序执行结果。
- default子句可以省略不用。
7.2 循环结构
C语言中提供了三种循环语句
- while循环
- for循环
- do...while循环
7.2.1 while循环
while语句可以在条件表达式为真的情况下,循环执行指定的一段代码,直到表达式不为真的时结束。
7.2.1.1 while语句
while(表达式)
{
语句块
}
- 先执行while(表达式),条件为真,则执行语句块
- 执行完语句块,回到while(表达式)继续执行
- 直到表达式为假,才退出循环,执行while后面的代码
7.2.2.1 简单代码示例
#include <stdio.h>
int main (void)
{
int i=0; //初始条件i=0;
while(i<10) //while 循环
//while(表达式) 如果为真执行{ }里面语句块。
{
printf("i的值为:%d\n",i); //输出i的值
i++; //自增
}
return 0;
}
这段代码是输出0-9的数
7.2.2 for循环
for循环是一种常用的shell编程语句,用于重复执行一段代码块,直到满足某个条件为止。
for循环通常用于遍历数组、文件列表等场景
7.2.2.1 for循环语句
for (表达式 1 ;表达式 2 ;表达式 3) {
若干语句;
}
- 初次循环先执行表达式1,再执行表达式2,在表达式2为true的前提下执行“若干语句”,再执行表达式3
- 再执行表达式2,在表达式2为true的前提下执行“若干语句”,再执行表达式3
- ……
- 依次的执行下去,直到表达式3的值不能使表达式2为true时,for循环结束
7.2.2.2 简单的代码示例
日常代码输出
#include <stdio.h>
int main( ){
printf("爱惜粮食,从一粒米开始\n");
printf("爱惜粮食,从一粒米开始\n");
printf("爱惜粮食,从一粒米开始\n");
printf("爱惜粮食,从一粒米开始\n");
printf("爱惜粮食,从一粒米开始\n");
printf("爱惜粮食,从一粒米开始\n");
printf("爱惜粮食,从一粒米开始\n");
printf("爱惜粮食,从一粒米开始\n");
printf("爱惜粮食,从一粒米开始\n");
printf("爱惜粮食,从一粒米开始\n");
}
用for循环输出
#include <stdio.h>
int main(){
int i ;
for (i=1;i<=10;i++)
printf("爱惜粮食,从一粒米开始\n");
}
7.2.3 do...while循环
do while循环语句是一种循环控制语句
在执行循环体之前先判断循环条件。即无论循环条件是否成立,先执行循环体中的语句,然后再判断循环条件是否成立,如果成立则继续执行循环体,直到循环条件不成立时结束循环。
与while循环不同的是,do while循环保证循环体至少执行一次。
7.2.3.1 do...while循环语句
do
{
循环体;
}
while (循环条件);
7.2.3.2 简单的代码示例
用do-while输出1到10的值
#include <stdio.h>
int main (void)
{
int i = 1; // 设置初始值,i为1。
do
{
printf("数值为:%d\n",i);
i++;
}
while(i<=10); //先执行语句,在判断结果。
return 0;
}
8.函数
函数的概念第一次出现在我们的数学中,例如:f(x)
但是,在C语言中,函数可不同于数学中的函数;C语言中函数就是具有某项功能的代码段,它是C语言管理代码的最小单位(英言是function),早期被翻译成函数,就一直沿用了,现在新的编程语言都翻译成方法。
函数把具有某些功能的若干行代码封装在函数中方便管理代码且方便重复调用
8.1 子程序
维基百科中,对函数的定义:子程序
- 在计算机科学中,子程序是一个大型程序中的某部分代码,由一个或多个语句块组成,负责完成某项特定任务,而且相较于其他代码,具有相对的独立性
- 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏,这些代码通常被集成为软件库
8.2 库函数
- 在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上,这个时候我们会频繁地使用一个功能:将信息按照一定的格式打印到屏幕上(printf)
- 在编程的过程中,我们也会频繁地做一些字符串的拷贝工作(strcpy)
- 在编程的过程中,我们也会计算类如n的k次方这样的运算(pow)
像上面描述的这些基础功能,它们并不是业务性的代码。我们在开发的过程中每个程序员都可能用得到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行程序开发
8.2.1 认识库函数
库函数不是C语言提供的
C语言标准中预定好,由编译器的厂商提供实现
比如strlen函数,C语言标准规定:
- 函数的功能:求字符串的长度
- 函数名:strlen
- 参数:const char* str
- 返回类型:size_t
当标准规定好后,这时候A厂商和B厂商写出了这个函数,功能一样,但是由于不同厂商的程序员编写方法不同,内部的实现细节可能不同
size_t strlen(const char*str)这就是strlen函数的原型
所有的库函数都在标准库中
8.2.2 如何学习库函数
那么我们怎么学习库函数呢?
这里我推荐大家可以通过一个网站来学习:
8.2.3 C语言中常用的库函数
C语言中,常用的库函数都有:
- I/O函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
例如 stdio.h 头文件包含标准输入输出函数
例如 math.h 头文件包含数学相关的函数
8.2.4 学习简单的库函数
8.2.4.1 pow
double pow ( double base , double exponent ) ;
他的意思是求base的exponent次方
pow库函数包含在math.h头文件中
https://cplusplus.com/reference/cmath/pow/
他的使用方法是这样的
#include "math.h"
#include "stdio.h"
int main(){
pow(x,y);//求x的y次方
return 0;
}
这里我们知道他的结果是一个整数,可以强制转换成int型
cplusplus.com/reference/cmath/pow/
8.2.4.2 strcpy
char * strcpy ( char * destination , const char * source ) ;
把source指向的字符串拷贝到destination的数组空间里面去
strcpy库函数包含在string.h头文件中
举个例子
https://cplusplus.com/reference/cstring/strcpy/
8.2.4.3 memset
void * memset ( void * ptr , int value , size_t num ) ;
把ptr指向的内存块中前num个字节的内容设置成value值
memset库函数包含在string.h头文件中
https://cplusplus.com/reference/cstring/memset/
8.3 自定义函数
如果库函数能够干所有的事情,那还要程序员干什么?
所以更加重要的是自定义函数
自定义函数和库函数一样,都有函数名,返回值类型和函数参数
但是不一样的是这些都是我们自己来设计,这给程序员一个很大的发挥空间
8.3.1 函数的组成
ret_type fun_name ( para 1 , * )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para 1 函数参数
函数参数可以没有,可以是一个,可以是多个
总结一下就是函数有以下四个部分
- 函数名
- 参数
- 返回类型
- 函数体
这四个部分都体现出来,这个函数基本就完成了
8.4 函数参数
8.4.1 实参和形参
函数的参数分为实参和形参
即实际参数 形式参数
8.4.1.1 实参
真实传给函数的参数,叫实参
实参可以是:常量、变量、表达式、函数等
无论实参是何种类型的量,在进行函数调用的时候,都必须有确定的值,以便把这些值传送给形参
8.4.1.2 形参
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数
形式参数当函数调用完成之后就自动销毁了,因此形式参数只在函数中才有效
8.4.1.3 实参形参的关系
- 函数调用的时候,将实参传递给形参
- 形参其实是实参的一份临时拷贝
因此,对形参的修改,不会改变实参
8.5 函数调用
8.5.1 传值调用
- 函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参
8.5.2 传址调用
- 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式
- 这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量
8.6 函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合,也就是互相调用的
8.6.1 嵌套调用
嵌套调用就是某个函数调用另外一个函数
8.6.2 链式访问
把一个函数的返回值作为另一个函数的参数
8.7 函数的声明和定义
8.7.1 函数声明
- 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了
- 函数的声明一般出现在函数的使用之前,要满足先声明后使用
- 函数的声明一般要放在头文件中
在使用函数之前,先告诉编译器有这个函数
在未来的工程中,代码是比较多的
函数一般是放在.h文件中声明,在.c文件中实现的
8.7.2 函数定义
- 函数的定义是指函数的具体实现,交代函数的功能实现
test.h 放置函数的声明
test.c 放置函数的实现
9.关于输入输出与结束符“\0”
9.1 结束符'\0'
结束符\0 ASCII码为0,即为空字符NULL,占用一个字符位。
所有的字符串操作函数,都会遇0而止
仅有字符数组(即char型数组)
字符数组的长度要比实际存储字符串的长度至少多1
(int型等其他数组的末尾不需要加\0)
9.2 输入函数
- 使用 scanf函数的%s格式 / gets()函数 输入字符串 ,识别空格/换行符作为输入结束,不需要在输入的字符串末尾加入'\0'
- 而scanf函数的%c格式用来输入单个字符,能够识别空格跟换行符并将其输入,并不同于其他输入方式(其他函数或其他格式)通过空格或换行来识别输入的结束,不需要在输入的字符串末尾加入'\0'为只识别单个字符)
- 使用getchar()输入字符串则需要在字符串末尾加入“\0”,否则printf和puts函数会因无法识别字符串末尾而输出一大堆乱码。
注意:
由于gets识别换行符\n作为输入结束,因此若scanf完一个整数后,如需使用gets函数,
需要先用getchar接收整数后的换行符,或应在scanf末尾加入\n
9.3 输出函数
puts函数和printf在输出字符串的时候遇到'\0'和'\n'分别是怎么处理的
- 如果有多个\0的话,这两个函数均是输出到第一个\0就终止了,
‘\0’作为一个字符串结束的标志 - 连续多个\n就是多次换行
就输出字符串上的功能而言,这两个函数的作用,效果完全一致
9.3.1 printf和puts的区别
c语言中,puts和printf函数有啥区别?
puts和printf函数的区别如下:
- 不同的换行符
puts()函数会自动在字符串末尾添加一个换行符,这意味着它会自动换行,而printf()函数没有此功能 - 输出内容不同
puts()函数只能输出字符串,不能对输出和转换指令进行标准化,而printf()函数可以
9.3.2 printf和puts的调用格式
9.3.2.1 printf
格式字符串包含三种类型的对象:
(1)字符串常量
(2)格式控制字符串
(3)转义字符
- 字符串常量按原样输出并在显示中用作提示。输出项在输出表列中给出,格式控制字符串和每个输出项在数量和类型上应一一对应。
- 格式控制字符串是以%开头的字符串,后跟各种格式控制字符,以指示输出数据的类型、宽度、精度等。
9.3.2.2 puts
(1)puts()函数只能输出字符串,不能输出值或执行格式转换。
(2)字符串可以直接写入puts()函数。例如:如:puts("Hello,world!")。
(3)puts与printf相同,puts()函数的作用与printf语句相同。注意:puts将在输出字符串之后自动输出回车
9.3.3 输出进制转换
写出一段代码将输入的十进制数分别用八进制和十六进制进行输出
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main() {
int a = 0;
scanf("%d", &a);
printf("%d的八进制为%#o\n", a,a);//%o
printf("%d的十六进制为%#X\n",a,a);//%X
//printf可以使用使用格式控制串“%o”、“%X”分别输出八进制整数和十六进制整数,并使用修饰符“#”控制前导显示
return 0;
}
%d整型输出,%ld长整型输出
%o以八进制数形式输出整数
%x以十六进制数形式输出整数
%u以十进制数输出unsigned型数据(无符号数)
%c用来输出一个字符
%s用来输出一个字符串
%f用来输出实数,以小数形式输出
%e以指数形式输出实数
%g根据大小自动选f格式或e格式,且不输出无意义的零
9.4 参考资料
参考资料来源:puts ()_百度百科
参考资料来源:printf(格式化输出函数)_百度百科
10.操作符
- 算数操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
10.1 算数操作符
+ - * / %
- 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数
- 对于 / 操作符,如果两个操作数都为整数,执行整数除法。而只要有浮点数,执行的就是浮点数除法
- % 操作符的两个操作数必须为整数,返回的是整除后的余数
我们可以在VS中运算一下
10.2 移位操作符
<< 左移操作符
>> 右移操作符
注意:移位操作符的操作数只能是整数
可以这样写代码
int x = 7 >> 1;
移位操作符移动的是二进制位
10.2.1 二进制序列
对于一个整数是4个字节,一个字节是8个bit位,那么一个整数就是32个bit位
一个整数写出二进制序列的时候,就是32个bit位
- 对于有符号整数来说,最高位是符号位:符号位是1表示负数,符号位是0表示正数
- 对于无符号整数来说,所有位都是有效位,没有符号位
所以,负数写成二进制序列的话,最高位一定是1;正数写成二进制序列,最高位一定是0;
10.2.2 原码、反码、补码
整数的二进制表示形式有三种:原码、反码、补码
对于正整数来说,原码、反码、补码相同
对于负数来说
原码:按照数值的正负,直接写出的二进制序列
反码:原码的符号位不变,其他位按位取反(0变成1,1变成0)
补码:反码的二进制+1
0当作无符号数看待
整数在内存中存的都是补码
举个例子:
10:
原码:00000000 00000000 00000000 00001010
反码:00000000 00000000 00000000 00001010
补码:00000000 00000000 00000000 00001010
-10:
原码:10000000 00000000 00000000 00001010
反码: 11111111 11111111 11111111 1111 0101
补码: 11111111 11111111 11111111 1111 0110
10.2.3 左移操作符
我们用图来解释
左移一位即是,二进制序列向左移动一位,在末尾补一个0,形成新的二进制序列
移位的时候移动的是补码
左移n位效果相当于,乘上2的n次方
m只参与运算,m的值不变,这里m移位的结果其实是n的值
所以我们总结下来,向左移位的规则就是:左边丢弃,右边补0
负数同样的道理
10.2.4 右移操作符
移位规则:
首先右移运算分为两种:
- 逻辑移位:左边用0填充,右边丢弃
- 算数移位:左边用原该值的符号位填充,右边丢弃
逻辑右移还是算数右移,取决于编译器 ;绝大部分编译器采取的是算数右移
右移n位的效果相当于,除以n的k次方
注意:不管是左移还是右移,不要移动负数位,比如:>>-1,<<-1
10.3 位操作符
位操作符有:
& //按位与
| //按位或
^ //按位异或
注意:操作数必须是整数
这里我们所说的按位,都是按二进制位
都是按照补码进行运算的
10.3.1 按位与
举个例子
所以,按位与 & 的运算规则是
- 只要有0则为0
- 两个都是1才为1
- 结果是补码,需要还原成原码到十进制数
按位与的使用,可以得到想得到的位:先移位,再按位与
比如,我想得到3的最低位,那么我就按位与1
如果想得到第n位,那么可以把第n位移到最低位,再按位与1
10.3.2 按位或
举个例子
所以,按位或 | 的运算规则是
- 有1则为1
- 全0才为0
- 结果是补码,需要还原成原码到十进制数
10.3.3 按位异或
举个例子
异或运算的特点:
- 相同为0,相异为1
- 结果是补码,需要还原成原码到十进制数
按位异或的使用,根据相同为0,相异为1;比如
a^a=0
0^a=a
我们可以利用异或运算的特点,用不创建临时变量的方法交换两个数的值;看代码
int main()
{
int a, b;
scanf("%d %d", &a, &b);
a = a ^ b;
b = a ^ b;//即 a ^ b ^ b = a ^ 0 = a;
a = a ^ b;//即 a ^ b ^ a = 0 ^ b = b;
printf("%d %d", a, b);
return 0;
}
10.4 赋值操作符
10.4.1 赋值操作符
赋值操作符是一个非常棒的操作符,他可以让你得到一个你之前不满意的值,也就是可以重新赋值
int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y + 1;//连续赋值
这样的写法更加清晰而且易于调试
10.4.2 复合赋值符
+= -= *= /= %= >>= <<= &= |= ^=
这些运算符都可以写成复合的效果,比如:
int x = 10;
x = x + 10;
x += 10;//复合赋值
同样的道理,这样写更加简洁
10.5 单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置 --
++ 前置、后置 ++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
10.5.1 逻辑反操作符 !
想要把假变成真,把真变成假的时候
如果 a 为真,则!a为假
10.5.2 取地址符 &
int a = 10;
&a;//a变量的地址
int arr[10];
&arr;//这是数组的地址
int*p = &a;//可以用指针变量接收地址
10.5.3 解引用操作符 *
解引用操作符 * 和取地址符 & 通常是搭配使用的
*p;//对p进行解引用操作,*p是通过p中存放的地址找到p指向的对象
10.5.4 sizeof 操作符
sizeof 是操作符而不是函数
- sizeof 是在计算类型创建变量或者变量的大小,单位是字节
- sizeof 计算的结果是size_t 类型的
- size_t 是无符号整型
- 对 size_t 类型的数据进行打印,可以使用 %zd
10.5.5 按位取反操作符 ~
~的作用是全部取反,无论是符号位还是其他位,比如:
10.5.6 自增自减操作符 -- ++
++是一种自增1的操作
自增分为:
前置++:即++a,先+1,后使用
后置++:即a++,先使用,后+1
--是一种自减1的操作
自减分为:
前置 --:即--a,先-1,后使用
后置 --:即a--,先使用,后-1
10.6 关系操作符
> 大于
>= 大于等于
< 小于
<= 小于等于
!= 不等于 用于测试“不相等”
== 等于(表判断) 用于测试“相等”
在编程的过程中,注意不要把 == 和 = 写错了
- ==表判断
- =表赋值
10.7 逻辑操作符
&& 逻辑与
|| 逻辑或
区分逻辑与和按位与
区分逻辑或和按位或
1&2 ------>0
1&&2 ------>1 //逻辑与
1|2 ------>3
1||2 ------>1 //逻辑或
10.7.1 逻辑与 &&
逻辑与&&表示并且的意思:同时为真才为真
举个例子:a&&b&&d++
//这个时候如果a为假,则&&后面不再执行;如果a为真,则判断b,b如果为假,则&&后面的d++不再执行
10.7.2 逻辑或 ||
逻辑或 || 表示或者的意思:一个为真就为真
举个例子:a||b||d++
//这个时候如果a为假,则判断b,b如果为假,则||后面的d++不再执行;如果a为真,则||后面不再执行
10.8 条件操作符
条件操作符也称为三目操作符
exp1 ? exp2 : exp3
条件操作符的作用是:
- 如果exp1为真,则执行exp2
- 如果exp1为假,则执行exp3
10.9 逗号表达式
exp1 , exp2 , exp3 , ... expN
逗号表达式,就是用逗号隔开的多个表达式
逗号表达式,从左向右依次执行,整个表达式的结果是最后一个表达式的结果
int main() {
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("%d", c);
return 0;
}
a = get_val();
count_val(a);
while (a > 0) {
//业务处理
a = get_val();
count_val(a);
}
如果用逗号表达式改写:
while (a = get_val(), count_val(a), a > 0) {
//业务处理
}
10.10下标引用、函数调用和结构成员
10.10.1 下标引用操作符
"[ ]"就是下标引用操作符,用在数组中
操作数:一个数组名+一个索引值
int arr[10];//创建数组
arr[9] = 10;//使用下标引用操作符
[ ]的两个操作数是arr和9
[ ]的操作数有两个
10.10.2 函数调用操作符
"( )"就是函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩下的操作数是传递给函数的参数
void test(int a, int b) {
return a + b;
}
int main() {
int a, b;
test(a, b);
return 0;
}
函数名和参数都是()的操作数
10.10.3 访问结构成员
. 结构体.成员名
-> 结构体指针->成员名
结构体.成员名
struct Book {
char name[20];
int price;
};
int main() {
struct Book b = { "C语言指南",55 };
printf("%s %d", b.name, b.price);
return 0;
}
结构体指针->成员名
struct Book {
char name[20];
int price;
};
void Print(struct Book* pb)
{
printf("%s %d", pb->name, pb->price);
}
int main() {
struct Book b = { "C语言指南",55 };
Print(&b);
return 0;
}
这两句代码是等价的
printf("%s %d", (*pb).name, (*pb).price);
printf("%s %d", pb->name, pb->price);
10.11表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型
10.11.1 隐式类型转换
C的整型算数运算总是至少以缺省型类型的精度来进行的
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升
整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换位CPU内整型操作数的标准长度
通用CPU是难以直接实现两个8bit字节直接相加运算,所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算
整型提升是按照变量的数据类型的符号位来提升的
整型提升针对的是自身大小 小于整型大小的操作数
10.11.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中以一个操作数转换为另一个操作数的类型,否则操作就无法进行
下面的层次体系称为寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型再上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算
比如,int 和 float 运算,会把 int 转换成 float(向上转换)
但是算术转换要合理,要不然会有一些潜在的问题
10.12 操作符的属性
复杂表达式的求值有三个影响因素:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执行哪一个?取决于他们的优先级,如果两者的优先级相同,取决于他们的结合性
10.12.1 操作符优先级和结合性
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | ----- |
() | 圆括号 | (表达式)/函数名(形参表) | ----- | ||
. | 成员选择(对象) | 对象.成员名 | ----- | ||
-> | 成员选择(指针) | 对象指针->成员名 | ----- | ||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
(类型) | 强制类型转换 | (数据类型)表达式 | ----- | ||
++ | 前置自增运算符 | ++变量名 | 单目运算符 | ||
++ | 后置自增运算符 | 变量名++ | 单目运算符 | ||
-- | 前置自减运算符 | --变量名 | 单目运算符 | ||
-- | 后置自减运算符 | 变量名-- | |||
* | 取值运算符 | *指针变量 | 单目运算符 | ||
& | 取地址运算符 | &变量名 | 单目运算符 | ||
! | 逻辑非运算符 | !表达式 | 单目运算符 | ||
~ | 按位取反运算符 | ~表达式 | 单目运算符 | ||
sizeof | 长度运算符 | sizeof(表达式) | ----- | ||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | 双目运算符 | ||
% | 余数(取模) | 整型表达式/整型表达式 | 双目运算符 | ||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | 双目运算符 | ||
5 | << | 左移 | 变量 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | 双目运算符 | ||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | 双目运算符 | ||
< | 小于 | 表达式 | 双目运算符 | ||
<= | 小于等于 | 表达式 | 双目运算符 | ||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | 双目运算符 | ||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | ----- |
/= | 除后赋值 | 变量/=表达式 | ----- | ||
*= | 乘后赋值 | 变量*=表达式 | ----- | ||
%= | 取模后赋值 | 变量%=表达式 | ----- | ||
+= | 加后赋值 | 变量+=表达式 | ----- | ||
-= | 减后赋值 | 变量-=表达式 | ----- | ||
<<= | 左移后赋值 | 变量 | ----- | ||
>>= | 右移后赋值 | 变量>>=表达式 | ----- | ||
&= | 按位与后赋值 | 变量&=表达式 | ----- | ||
^= | 按位异或后赋值 | 变量^=表达式 | ----- | ||
|= | 按位或后赋值 | 变量|=表达式 | ----- | ||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | 从左向右顺序运算 |
- 优先级与求值顺序无关。如a+b && b*c,虽然*优先级最高,但这个表达式求值顺序是从左到右
- 优先级从上到下依次递减,最上面具有最高的优先级,逗号操作符具有最低的优先级
- 相同优先级中,按结合性进行结合。大多数运算符结合性是从左到右,只有三个优先级是从右至左结合的,它们是单目运算符、、条件运算符、赋值运算符
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)