一、文章释义
红色加粗字体:注释
黑色加粗字体:重点
黑色下划线字体:了解须知
二、认识C语言
1、什么是语言?
人和人交流:汉语、英语、日语
人和计算机交流的语言:C/C++、JAVA、python、Go
2、什么是C语言?
C语言是一门通用计算机编程语言,广泛应用于底层开发。
C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。
二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言制定了一套完整的美国国家标准语法,称为ANSLC,作为C语言最初的标准。[1]目前2011年12月8日,国际标准化组织(ISO)和国际电工委员会(IEC)发布的C11标准是C语言的第三个官方标准,也是C语言的最新标准,该标准更好的支持了汉字函数名和汉字标识符,一定程度上实现了汉字编程。C语言也有自己的国际标准,最为主要的是C89、C90,次要的位C99、C11
※注释:
底层开发:
应用开发(如QQ、百度网盘) 应用层
---------------------------------------------------------------------------------------------------------------------------------
操作系统(如Windows、Linux) 底层系统
设备驱动程序
根文件系统
BootLoader
---------------------------------------------------------------------------------------------------------------------------------
硬件(PCB) 硬件层
---------------------------------------------------------------------------------------------------------------------------------
三、如何写C语言
1、工具
编译器:Clang、GCC、WIN-TC、SUBLIME、MSVC、TurboC等,(常用的为Clang、GCC、MSVC)
编程:VS2013/VS2019-集成开发环境-集成了MSVC这样的编译器-可以编写+编译C语言的代码
2、步骤
- 创建项目
- 创建源文件
- 写代码
- 编译代码
①、新建项目
vs2013:
vs2019:
②、新建源文件
(xxx.c,后缀为.c的项目为源文件 xxx.h,后缀为.h的项目为头文件 )
VS2013
VS2019
③、写程序
//1.写出主函数(main):
#include <stdio.h>
int main()
{
printf("比特"\n);
return 0;
}
※注释:
- ():1、表示函数,比如main(),printf()
2、改变默认运算顺序,比如(5-3)*2
3、语句结构,比如if(),for(),while() - int:函数的返回类型,整数(整型)的意思
- main:函数名
- {xxxx}:函数体
- printf:为库函数,意思为:在屏幕上打印信息
- include:包含
- <stdio.h>:包含的文件
- \n:换行
- ;:语句结束,或者表示一个空语句,或者在for循环中分割三个表达式。
printf的使用:引用头文件stdio.h
//2.如何执行函数:(C语言是从主函数的第一行开始执行的)
mian函数=程序入口
④、编译程序
1、编译+链接+运行代码
(快捷键:ctrl+f5/ctrl +fn+f5)
(3.菜单中:【调试】->【开始执行不调试】)
2、当编译后,结果一闪而过的处理方法
※注意:
有且仅有一个main函数
1.一个工程中可以有多个.c文件
2.但是多个.c文件中只能有一个main函数
3、如何引用别人的代码、头文件和静态库
只使用或者购买使用权
加入A程序员减法函数不会写,然后买B程序员的,但是B程序员又不想把源码给他,操作步骤:
1.右键项目,点击属性
点击常规-> 选择配置类型-> 选择静态库-> 点击应用 -> 点击确定
3.如debug文件里什么都没有,回到VS2019编译一下
4.点击确定
5.此时出现了lib文件
6.把lib文件与.h文件放到B的项目里
7.添加.h文件
8.导入静态库
四、数据类型
1、类型
C语言表达类型 | 释义 | 字节 |
char | //字符数据类型 | 1byte |
short | //短整型 | 2byte |
int | //整型 | 4byte |
long | //长整型 | 4byte |
long long | //更长的整型 | 8byte |
float | //单精度浮点数 | 4byte |
double | //双精度浮点数 | 8byte |
//%d表示整数的意思
//sizeof-关键字-操作符-计算类型或者变量所占空间的大小
int main()
{
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))
}
2、原码、补码、反码
整数在内存中存储的是补码
一个整数的二进制表示有3种:(以-1为例)
负数计算:
原码:1000000000000000000000000001
反码:11111111111111111111111111111110(符号不变,原来位1变0,0变1)
补码:11111111111111111111111111111111(反码+1为补码)
正数计算(原码、反码:补码相同)
3、ASCII码表
4、计算机中数据可以存储到哪里?
① 网盘 免费2TB
② 硬盘 付费500G 造价越低,CPU读取速度越慢,空间越大
③ 内存 8G/16G ↕
④ 高速缓存 几十MB 造价越高,CPU读取速度越快,空间越小
⑤ 寄存器 更小
※注意:
计算机中的单位
byte-字节= 8bit
kb -1024byte
mb -1024 kb
gb -1024 mb
tb -1024gb
pb -1024tb
sizeof的单位是 --字节(byte)
C语言标准: sizeof(long)>=sizeof(int)
五、数据的存储
五、变量常量
1、定义
变量:不能改变的量
常量:能被改变的量
2、分类
① 变量的分类
- 局部变量:{int a=100},大括号内的变量为局部变量
- 全局变量:int a=100
{xxxx},大括号内的变量为全局变量
当局部变量和全局变量名字冲突的情况下,局部优先
不建议把全局变量和局部变量的名字写成一样的
② 常量的分类
字面常量
int main()
{
3.1415926;
return 0;
}
3.1415926为字面常量。
const修饰的常变量
int main()
{
const int sum = 10;
sum = 20;
return 0;
}
int为变量,+const时,sum为常变量,本质上市变量,但是被const修饰为常量。
无const时sum=20,有const时sum不可用,编译会出错。
#define 定义的标识符常量
#define MAX 1000
int main()
{
int n = MAX;
printf("n = %d\n",n)
return 0;
}
枚举常量
定义:可以一一列举的常量
enum sex
{
//变量的未来可能取值范围,选项
男 =5,//赋初值
女
保密
}
int main()
{
enum sex s = 男;
printf("%d\n",男);
printf("%d\n",女);
printf("%d\n",保密);
return 0;
}
枚举常量是常量,默认从0开始
3、编程案例
#define CRT_SECURE_NO_WARNINGS 1
int main()
{
int a=0;
int b=0;
int sum=0;
scanf(“%d %d”,&a,&h);
sum=a+b;
printf("sum=%d\n",sum);
return 0;
}
#define CRT_SECURE_NO_WARNINGS 1:当报错时,第一行插入这行程序可不报错
scanf:输入函数,表示从外部输入到程序里
%d %d:表示输出格式类型
&a,&b:&表示取地址符,意思为取址a,b
printf:输出函数,表示从程序输出到程外部里
,:逗号运算符,将两个表达式连接起来
六、变量的作用域和生命周期
1、作用域
①局部变量的作用域:
定义:变量所在的局部范围{}
int main()
{
int a = 0;
printf("%d\n",a);
{
int b = 0;
printf("%d\n",b);
}
printf("%d\n",b);
return 0;
}
如上所示:printf("%d\n",a),可以使用
printf("%d\n",b),只可在{}中使用,{}外不可使用
②全局变量的作用域:
定义:整个工程
不同源文件的全局变量,只能在本源文件使用,跨源文件不可使用;
extern int g_val;
int main()
{
printf("%d\n",g_val);
return 0;
}
extern:声明变量,作用是使全局变量“int g_val”可以在不同源文件中使用
2、生命周期
变量的生命周期:变量的创建和销毁之间的时间段
1.局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
2.全局变量的生命周期是:整个程序的生命周期。
七、字符串+转义字符+注释
1、字符串
这种由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),或者简称字
符串。
int main()
{
"adsadsah";
"我最帅";
return 0;
}
双引号(“”)括起来的都是字符串
2、字符数组
注:字符串的结束标志是一个\0 的转义字符。在计算字符串长度的时候 \0 是结束标志,不算作字符串内容。
int main()
{
char arr[] = "hello";
return 0;
}
char:表示字符型变量
这段代码也就表示:arr[]为变量,“hello”为5个字符,但是字符串的结束标志为\0,所以“hello”+\0为6个字符,arr[]则为6。
3、字符数组的不同写法
int main()
{
char arr1[] = "abc";
char arr2[] = {'a','d','c'};
return 0;
}
arr1[] = "abc"有结束标志符\0
arr2[] = {'a','d','c'}无结束标识符\0,会出现乱码。如想正常显示,则需要修改为arr2[] = {'a','d','c','\0'}
4、字符串长度
int main()
{
char arr1[] = "abc";
char arr2[] = {'a','d','c'};
printf(%d\0,strlen(arr1));
printf(%d\0,strlen(arr2));
return 0;
}
strlen:表示计算字符串长度包含\0;字符串函数
arr1[] = "abc"有结束标志符\0,结果会显示具体数值
arr2[] = {'a','d','c'}无结束标识符\0,会出现随机值。如想正常显示,则需要修改为arr2[] = {'a','d','c','\0'}
5、字符串比较
#intclude <string.h> //字符串函数strcmp、strlen必须引用头文件
int main()
{
scanf("s%", password);
if(strcmp(password, "123456")== 0); //strcmp(变量,“字符”)== 0
//== 0的意思为返回值为0
//strcmp为字符串函数
//> 0);
//< 0);
{
printf("密码成功");
}
return 0;
}
八、转义字符(转变意思)
转义字符 | 释义 |
\? | 在书写连续多个问号时使用,防止他们被解析成三字母词 |
\' | 用于表示字符常量 |
\" | 用于表示一个字符串内部的双引号 |
\\ | 用于表示一个反斜杠,防止它被解释为一个转义序列符。 |
\a | 警告字符,蜂鸣 |
\b | 退格符 |
\f | 进纸符 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | ddd表示1~3个八进制的数字。如:1130X |
\xdd | dd表示2个十六进制数字。如:\x300 |
九、注释
//inta=10;//C++注释风格
/*
int b =0;
*/
//C语言的注释风格
十、选择语句
int main()
{
printf("好好学习(1/0)?>;");
scanf("%d\n",&input);
if (input == 1)
printf("拿高薪");
else
printf("卖红薯");
return 0;
}
十二、函数
1、什么是函数?
- 在计算机科学中,子程序:Subroutine,procedure,function,routine,method,subprogram,callable unit),是一个大型程序中的某部分代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
- 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
简易函数如下
int add(int x,int y)
int z = 0;
z = x + y
return 0;
int main()
{
int a = 0;
int b = 0;
scanf("%d%d,&a,&b")
int c = add(a,b);
print("%d\n,c");
return 0;
}
2、函数分类:库函数 && 自定义函数
1.库函数
① 为什么会有库函数
- 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕上(printf)。
- 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)
- 在编程是我们也计算”总是会计算n的k次方这样的运算(pow)。
像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。
② 学习库函数的网站
- www.cplusplus.com
- MSDN
- http://en.cppreference.com(c/c++官网)//英文版 || http://zh.cppreference.com(c/c++官网)//中文版
③库函数分类
IO函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
④如何学习使用库函数?
- strcpy(以cplusplus为例子)
※注意:
使用库函数必须引用头文件
2.自定义函数
① 定义
自定义函数和库函数一样,有函数名,返回值类型和函数参数。 但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。如:
char * strcpy(char *strDestination, const char *strSource);
// char——函数的返回类型
// strcoy——函数名
// char *strDestination, const char *strSource——函数参数
② 函数的组成
ret_type fun_name(para1, *);
{
statement; //语句项——函数体
}
ret_type //返回类型
fun_name //函数名(函数名就是地址)
para1 //函数参数
③ 如何定义使用自定义函数?
-- 常规
int get_max(int x, int y); //自定义get_max函数
{
int z = 0;
if(x > y);
int z = x
else
int z = y
return z;
}
int main()
{
int a = 10;
int b = 30;
int max = get_max(a, b); //函数的调用
printf("%d\n", max);
return 0;
}
--指针取地址(两个值调换)
void swap(int* pa, int * pb)
{
int z = 0;
z = *pa;
*pa = *pb;
*pb = z;
}
//为什么取地址互换
//如果不取地址,则当调用swap函数,pa = a,只是两个不同的地址拥有一个相同的数值,调用后,a的地址值不会发生改变。,所以a,b还是原来的值,需要改变地址值
int main()
{
int a = 10;
int b = 20;
printf("a = %d b= %d",a,b)
swap(a,b)
printf("a = %d b= %d",a,b)
return 0;
}
3、函数参数
1.实际参数
真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
2.形式参数
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
void swap(int* pa, int * pb) //形式参数
{
int z = 0;
z = *pa;
*pa = *pb;
*pb = z;
}
int main()
{
int a = 10;
int b = 20;
printf("a = %d b= %d",a,b)
swap(a,b); //实际参数
printf("a = %d b= %d",a,b)
return 0;
}
这里可以看到Swap1函数在调用的时候,x,y拥有自己的空间,同时拥有了和实参一模一样的内容。所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。
4、函数调用
1.传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参
2.传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数式。 这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操作函数外部的变量。
应首选传址调用
原因:函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈)的系统开销比较大,所以会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址。
int get_max(int x, int y);
{
int z = 0;
if(x > y);
int z = x
else
int z = y
return z;
}
void swap(int* pa, int * pb)
{
int z = 0;
z = *pa;
*pa = *pb;
*pb = z;
}
int main()
{
int a = 10;
int b = 20;
get_max(a, b); //传值调用
printf("a = %d b= %d",a,b)
swap(&a, &b); //传址调用
printf("a = %d b= %d",a,b)
return 0;
}
5、函数的嵌套调用和链式访问
1.嵌套调用
void test3()
{
printf("hehe\n");
}
void test2()
{
test3();
return 0;
}
int main()
{
test2();
return 0;
}
2.链式访问
把一个函数的返回值作为另外一个函数的参数。
include<string.h>
int main()
{
char arr[20] = {0};
char arr[] = {"abc"};
printf("%s\n", strcpy(arr1, arr2)); //链式访问
return 0;
}
6、函数的声明和定义
1.函数声明
(1)告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,无关紧要。
(2)函数的声明一般出现在函数的使用之前。要满足先声明后使用,
(3)函数的声明一般要放在头文件中的;定义放在源文件中(注:文件名要一致,add.h和add.c):
intclud"sub.h" //==int sub(int a, int b);
2.函数定义
函数的定义是指函数的具体实现,交待函数的功能实现。
3.函数声明和函数定义的关系
定义在函数调用 前,不声明;
定义在函数调用后,要声明
4.代码释义
int main()
{
int a = 10;
int b = 20;
int add(int, int); //函数声明,;有这么一个函数
//函数返回类型是int,函数名是add,函数个数2个,函数类型int
int c = add(a,b);
return 0;
}
int add(int x, int y); //函数定义
{
return x + y;
}
7、递归函数
十四、操作符与占位符
Ⅰ 操作符
1、算数操作符
算数操作符 | 释义 |
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取模 |
※注意:“/”,除下来是商,转化为小数型,则需要在除数和被除数中放入小数点,如2.0 3.0
“%”,%操作符的两个操作数必须为整数,取模下来的是余数
除了%操作符之外,其他几个操作符可以作用于整数和浮点数
2、移位操作符
移位操作符 | 释义 |
<< | 左移 |
>> | 右移 |
int main()
{
int a = 1;
int b = a << 1;
printf("%d\n", b);
return 0;
}
※注意:移位操作符移的是二进制位。
算术移位和逻辑移位
算数移位:右边丢弃,左边补原符号位,移位的是算数移位
逻辑移位:右移丢弃,左边补0
3、位操作符
位操作符 | 释义 |
&& | 按(二进制)位与 |
^ | 按位或 |
|| | 按位与或 |
&按位与
int main()
{
int a = 3;
int b = 5;
int c = a & b;
//int a = 3 用二进制表示00000000000000000000000000000011
//int b = 5 用二进制表示00000000000000000000000000000101
//int a & int 用二进制表示00000000000000000000000000000001;&有0全为0,全1都为1
|按位或
int main()
{
int a = 3;
int b = 5;
int c = a | b;
//int a = 3 用二进制表示00000000000000000000000000000011
//int b = 5 用二进制表示00000000000000000000000000000101
//int a | int 用二进制表示00000000000000000000000000000111;|全0为0,有1都为1
^按位异或
int main()
{
int a = 3;
int b = 5;
int c = a ^ b;
//int a = 3 用二进制表示00000000000000000000000000000011
//int b = 5 用二进制表示00000000000000000000000000000101
//int a ^ int 用二进制表示00000000000000000000000000000111;^相同为0,相异为1
4、赋值操作符
赋值操作符 | 释义 |
= | 赋值 |
== | 判断 |
+= | 例:a+=100 -> a=a+100(下同) |
-= | |
*= | |
/= | |
&= | |
^= | |
|= | |
>>= | |
<<= |
int a = 10;
int b = 20
int c = 30
a = b = c+1;连续赋值,从右到左
5、单目操作符
单目操作符 | 释义 |
! | 逻辑反操作,无!为真,有!为假 |
- | 负值 |
+ | 正值 |
& | 取地址 |
sizeof | 操作数的类型长度(以字节为长度) |
~ | 对一个数的二进制按位取反,补码取反 |
-- | 前置、后置 |
++ | 前置、后置自增符运算 |
* | 间接访问操作符(解引用操作符) || 指针引用,"地址赋值" |
(类型) | 强制类型转换 |
int main()
{
//!为假 无!为真
int a = 5;
print("&d\n", !a);
int b = 0;
print("&d\n", !b);
//sizeof
//int为四个字节,所以显示为4,以字节为长度
int c = 8;
printf("%d\n", sizeof c);
printf("%d\n", sizeof (int));
printf("%d\n", sizeof (c)); //语法
char arr[10] = {0};
printf("%d\n", sizeof (arr));
printf("%d\n", sizeof (int [10])); //语法
int main()
{
short s = 5;
int a = 8;
printf("%d\n",sizeof(s = a + 5));
printf("%d\n", s)
return 0;
//显示结果为:2,5
//原理:sizeof括号中放的表达式是不参与运算的
}
//++/--:如i++的意思是:i=i+1
int e = 5;
int r = ++e;
int g = 6;
int f = g++;
printf("%d\n",e);
printf("%d\n",r);//++e先++,后使用
printf("%d\n",g);
printf("%d\n",f);//g++先使用,后++
//* 指针变量大小为4/8,32位4,64位8,指针大小不论类型
int main()
{
int a = 10;
printf("%p\n",&a);
int *pa = &a; //这里的*表示指针变量
*pa = 20; //解引用操作符;间接操作符
printf("%p\n",a);
return 0;
}
//(类型)
int h = (int)3.14;强制数值转换
printf("%d\n",h);
return 0;
}
~(按位取反)
按位取反所得数快速计算(以0为例):
①转化为为4字节的2进制
②2进制相反
③减一
注意:函数的括号不可以省略,sizeof不是函数,是操作符不能省略。
6、关系操作符
关系操作符 | 释义 |
> | |
< | |
= | |
>= | |
<= | |
== | 用于测试相等 |
!= | 用于测试不相等 |
7、逻辑操作符
逻辑操作符 | 释义 |
&& | 逻辑与 |
|| | 逻辑或 |
&&遇假停算,||遇真停算
int mian
{
//&&
//当有两个都为真则为1,当有一个为假或者两个都为0
int a = 5;
int b = 6;
int c = a && b;
print("%d/n", c);//结果显示为1
int e = 5;
int f = 0;
int g = e && f;
print("%d/n", g);//结果显示为0
//||
//当两个为真或者1个为真都为1,当两个为假则为0
int a = 5;
int b = 6;
int c = a || b;
print("%d/n", c);//结果显示为1
int e = 5;
int f = 0;
int g = e || f;
print("%d/n", g);//结果显示为1
return 0;
}
8、条件操作符
条件操作符 | 释义 |
exp1 ?exp2 :exp3 | exp1 成立,exp2计算,整个表达式的结构是:exp2的结果; exp1不成立,exp3计算,整个表达式的结构是:exp3的结果。 |
※释义:
exp1:第一个表达式
exp2:第二个表达式
exp3:第三个表示
?:是否成立
::选择
int main
{
int a = 1;
int b = 6;
int max = 0;
max = a>b ? a : b;exp1不成立,exp3计算,整个表达式的结构是:6
print("%d\n",max);
return 0;
}
9、逗号表达式
逗号表达式 | 释义 |
exp1,exp2,exp3,.expN | 从左向右依次计算 |
※注意:一般要用()括起来
从左往右依次计算
但整个表达式结果为最后一个表达式的结果
int main()
{
int a = 2;
int b = 3;
int c = 34;
int d = (a = b - 2, c = a + 9);
printf("%d\n", d);
return 0;
}
10、下标引用、函数调用和结构成员
下标引用、函数调用和结构成员 | 释义 |
[] | 下标引用操作符 |
() | 函数调用操作符 |
. | “详见结构体” |
-> | 结构体指针 -> 成员变量名 |
int main()
{
//[]
//结果显示为5,表示第几个
int arr[10] = {1,2.3,4,5,6,7,8,9,10};
//下标 = 0,1,2,3,4,5,6,7,8,9
print("%d\n", arr[4]); // arr[4]的[]才是下标引用操作符,arr[10]的[]是引用数组指定数组
大小的一种语法
//[]的操作数为arr和4,一个操作数,一个操作名,两个都不可以删除
//3+5的操作数为3和5
//()
//调用函数的时候,函数名后边的()就是函数调用操作符
//函数调用操作符接收一个或者多个操作数,第一个操作数为函数名,剩下的操作数就是传递给函数的参数如:
add(a,b); //三个操作数为add,a,b
test(); //一个操作数为函数名test
print("哈哈\n");
return 0;
}
Ⅱ 占位符
占位符 | 释义 |
%d || %i | 以十进制有符号整数输出 |
%u | 以十进制无符号整数输出 |
%f | 以小数形式输出单、双精度数,隐含输出6位小数 |
%s | 以字符串形式输出 |
%c | 以字符形式输出,只输出单个字符 |
%p | 输出指针的值 |
%e || %E | 输出指数形式的浮点数 |
%x || %X | 输出无符号以十六进制表示的整数 |
%0 | 输出无符号以八进制表示的整数 |
%g || %G | 输出时自动选择合适的表示法 |
Ⅲ表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
Ⅳ隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
什么时候用整型提升?
自身字节大小达不到整型字节大小就整体提升,自身字节大小比整型大则不用整型提升。
实例2中的,c只要参与表达式运算,就会发生整形提升,表达式 +c,就会发生提升,所以 sizeof(+c)是4个字节,sizeof()不参与运算,但是+c返回值有大小
//实例
char a,b,c;
a = b + c;
b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断(截断:b和c提升为普通整型,为4个字节,a为1个字节,4个字节赋值给1个字节,只保留最低的字节内容,其他扔掉),然后再存储于a中。
int main()
{
char a = 3;
char b = 127;
char c = a + b;
printf("%d\n", c);
return ;
//算术运算前先整型提升
// a由00000011变成00000000000000000000000000000011
// b由01111111变成00000000000000000000000001111111
//c为00000000000000000000000010000001
//保留最后一位c为10000010;
//printf %d先整型提升为:11111111111111111111111110000001
//11111111111111111111111110000001为存在内存的补码
//显示出来的是原码,需要由补码转化为原码,为1000000000000000000000001111101
}
Ⅴ算数转化
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
警告:但是算术转换要合理,要不然会有一些潜在的问题
int a = 5;
long int b = 10;
a + b;
//int会自动转化为long int
Ⅵ操作符属性
复杂表达式的求值有三个影响的因素。
- 操作符的优先级(ps.下列表格从上到下优先级)
// 优先级决定了计算顺序
- 操作符的结合性
// 优先级不起作用,结合性决定了顺序
- 3是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
十五、常见关键字
常见关键字 | 释义 | 场景 |
auto | 自动 | 每个局部变量是auto修饰的,只不过省略掉了 |
break | 跳出循环 | |
case | ||
char | 字符 | |
const | 常变量 | |
continue | 继续 | |
default | 默认 | |
do | do...while..语句 | |
double | 双精度 | |
else | ||
enum | 枚举 | |
extern | 申明外部符号的 | |
float | 单精度浮点型 | |
for | for循环 | |
goto | goto语句 | |
if | ||
int | 整型 | |
long | 长整型 | |
register | 寄存器 | |
return | 返回 | |
short | 短整型 | |
signed | 有符号整型 | |
sizeof | 数据字节大小 | 用于计算该数所属的数据类型所占内存大小 |
static | 静态的 | |
struct | 结构体 | |
switch | ||
typedef | ||
union | 联合体(共用体) | |
unsigned | 无符号整型 | |
void | 无/空 | 自动创建,自动销毁 |
volatile | ||
while |
1、特点:
① C语言提供的关键字
②不能自己创建关键字
③关键字不能做变量名
2、register——寄存器关键字
int main()
{
//大量/频繁被使用的数据,想放在寄存器中,提升效率
//编译器会自己放入寄存器,提高效率,使用意义不大
register int num = 100; //建议num的值放入寄存器中
return 0;
}
3、typedef——类型重定义(重命名)
typedef unsigend int u_int; //u_int为unsigend int的重命名。
int main()
{
u_int hign = 100;
return 0;
}
4、static——静态的
※用法:
①static修饰局部变量,改变了局部变量的生命周期(本质上改变了变量的存储类型)
使得局部变量起了全局变量的作用
void test()
{
static int a = 1;
a++;
printf("%d\n", a);
}
int main()
{
int i = 0;
while(i < 10);
{
test()
i++
}
return 0;
//当有static,显示结果为:2,2,2,2,2,2,2,2,2,2,
//当无static,显示结果为:2,3,4,5,6,7,8,9,10,11,
}
②static修饰全局变量:static修饰全局变量,使得这个全局变量只能在自己所在的源文件 (.c)内部可以使用其他源文件不能使用!
全局变量,在其他源文件内部可以被使用,是因为全局变量具有外 部链接属性,但是被static修饰之后,就变成了内部链接属性,其 他源文件就不能链接到这个静态的全局变量了!
③static修饰函数:static修饰函数,使得函数只能在自己所在的源文件内部使用,不能在其 他源文件内部使用本质上。
static是将函数的外部链接属性变成了内部链接属性!(和static修饰全局 变量一样)
十六、#define定义常量和宏
1、#define定义标识符常量
define MAX 1000
int main()
{
printf("%d\n", MAX);
return 0;
}
2、#define定义宏
define add(a, b) (a+b); //add(a,b)为宏
int main
{
int c = 5 * add(a, b);
printf("%d\n", c);
return 0;
}
十七、指针(内存地址)
1、内存
内存是电脑上特别重要的存储器,计算机中所有程序的运行都是在内存中进行的。
所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
①32位机器和64位机器分别表达什么?
电信号 | ||||
32位 | 32跟地址线 | 物理线 | 通电 | 正极1负极0 |
64位 |
②内存是怎么编号的
电信号转换为数字信息:1和0组成的二进制序列
从00000000000000000000000000000000~11111111111111111111111111111111,
一共2^32个序列,这些序列都可以作为内存编号/内存编号
③一个内存单元是多大空间
一个内存单元是一个字节(byte)
2、取地址代码解析
int main()
{
int a = 20; //a在内存中要分配空间的——4个字节
int* pa = &a; ///pa是用来存放地址的,在c语言中叫是指针变量
//* 说明pa是指针变量
//int* pa = &a;个人理解为设置a的指针为pa,数据类型必须一样
//&a,取的是a的四个字节中第一个字节的地址
//int说明pa执行的对象是int类型的
print("%p\n", pa); //%p是专门用来打印地址的
return 0;
}
3、解引用操作
int main()
{
int a = 20;
int* pc = &a;//这里的*为指针
*pc = 30; //这里的*为 解引用操作
//*pc就是通过pc的里边的地址找到a,给a赋值
print("%d\n", a);
return 0;
}
4、指针大小
int main()
{
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;
//结果显示为:4,4,4,4,4,4,4
}
指针的大小是相同的。
指针是用来存放地址的,指针需要多大空间,取决于地址的存储(32位/64位)需要多大空间。
32位---32bit--4byte
64位---64bit--8byte
5、指针类型的意义
1. 指针类型决定了指针解引用的权限多大
2.指针类型决定了,指针走一步,能走多远(步长)
int main()
{
int a = 8;
int *pa = &a; //指针类型决定了指针解引用的权限多大,如int为4个字节,char为1个字节
printf("%p\n", pa + 1); //指针类型决定了,指针走一步,能走多远(步长):int+1加4个字节,
char则为1个
return 0;
}
6、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
造成野指针的原因:
1.未初始化
int main()
{
//未初始化野指针
int *pa;
*pa = 5;
//规避未初始化
int *p = NULL; //第一种,NULL指的是空指针,注:NULL为无效的,不可使用,只能使用为初始化
int b = 8;
int *ptr = &b; //第二种,初始化
return 0;
}
2.指针越界访问
int main()
{
int arr[10] = { 0 };
char *pa = arr;
for(i = 0, i<=10, i++); //arr只有10个字符,i<=10到最后会加11,超出10,为越界访问
{
*(pa++) = i
return 0;
}
3.指针指向的空间释放
规避空间释放的方法,及时指向空指针
7、指针运算
1.指针+-整数:表示为增减指针地址
int main()
{
int arr[10] = { 0 };
char *pa = arr;
int i = 0
for(i = 0, i<=10, i++);
*(pa++) = 0 //指针+-整数
return 0;
}
2.指针-指针 :表示为得到两个指针之间的元素个数
指针-指针的前提为:两个指针指向同一块空间
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", &arr[9]-&arr[1]);
return 0;
}
3.指针的关系运算
for(vp = &values[N_VALUES-1;VP >=&values[0];vp--); //关系运算,但是一般是>而不是>=
{
*vp == 0;
}
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
8、指针和数组
1、指针互换
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int * pa = arr;
printf("%d\n",2[arr]);
//[]是操作符,操作符两边可以互换,如a+b可以互换b+a
//所以arr[2] == 2[arr];这是系统这么决定的
//arr[2]编译器编译的时候,会转换为*(arr+2) == *(2+arr)
//而*(p+2)表达的意思是数组往后移动两位,*(arr+2)也是,所以*(p+2) == * (arr+2)
return 0;
}
2、指针数组
int* pa[10] //存放指针的数组
//整型指针的数组
9、多级指针
int main()
{
int a = 10;
int* pa = &a;
int* *ppa = &pa; //二级指针
int* **pppa = &ppa; //三级指针
//......
//pa指针取a地址,因a是变量
//ppa指针取pa地址,因pa是指针变量
//以此类推
return 0;
}
十八、指针进阶
1、字符指针
1.代码1
int main()
{
char* ps = "hello pig";
printf("%c\n", ps);
//不是把字符串 he11o pig放到字符指针 ps 里了,本质是把字符串 he11o pig.首字符的地址放到了ps中
return 0;
}
不是把字符串 he11o pig放到字符指针 ps 里了,本质是把字符串 he11o pig.首字符的地址放到了ps中
意思是把一个常量字符串的首字符h的地址存放到指针变量 ps中。
2.代码2
int main()
{
char cha1[] = "hello";
char cha2[] = "hello";
char* cha3 = "hello";
char* cha4 = "hello";
if(cha1 == cha2);
printf("cha1和cha2相同");
else
printf("cha1和cha2不同");
if(cha3 == cha4);
printf("cha3和cha4相同");
else
printf("cha3和cha4不同");
return 0;
//显示结果为:
//cha1和cha2不同
//cha3和cha4相同
}
为什么cha1和cha2不同,cha3和cha4相同?
因为:
①比较指的是指向的内存区域
②这里cha3和cha4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域.当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以cha1和cha2不同,cha3和cha4不同。
2、指针数组
1.定义
存放整型的数组为整型数组
存放浮点型的数组为浮点数组
而存放指针的数组为指针数组(这里的指针也是数据类型)
2.代码释义
int main()
{
int a[] = {3,4,5,6,7};
int b[] = {4,5,6,7,8};
int c[] = {5,6,7,8,9};
int* d[] = {a,b,c};
printf("%d,%d,%d\n",*(d[0]),*(d[1]),*(d[2])); // 显示结果为3,4,5,
int i = 0;
if(i = 0, i < 3, i++);
int j = 0;
for(j = 0, j < 5, j++);
{
printf("%d\n",*(d[i]+j)); //int abc的值都显示
}
return 0;
}
3、数组指针
1.定义
数组指针是指针?还是数组?
-是指针,指向数组的指针
2.代码语法释义
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int (*ps)[10] = &arr;//
//arr[i] == (*ps)[i]
double* ps[5];//指针数组
double* (*pps)[5] = &ps;
return 0;
}
3.数组指针的调用
void print1(int (*ps)[5],int c,int d)
{
int x = 0;
int y = 0;
for(x = 0, x < c, x++)
{
for(y = 0,y < d,y++)
{
printf("%d", *(*(ps+x)+y));
}
printf("\n");
}
}
int main()
{
int a[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
print1(a,3,5);
}
4、数组、数组指针、指针数组区别
int a[10];//数组
int* a[10];//指针数组
int (*pass)[10]//数组指针,该指针指向一个数组,数组10个元素,每个元素的类型是int
int (*pass[10])[5];//pass是一个存储数组指针的数组,表示能够存放10个数组指针,每个指针指向一个数组,数组5个元素,每个元素为int类型
4、数组传参和指针传参
1.一维数组传参
void test(int arr[10])
[]
void test(int arr[])
[]
void test(int* arr)
[]
void test(int* arr[20])
[]
void test(int **arr)
int main()
{
int arr[10] = {0};
int* arr2[20] = {0};
test(arr);
test(arr2);
return 0;
}
2.二维数组传参
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
void test(int (*arr)[5])
{}
int main()
{
int arr[3][5] = {0};
test(arr);
return 0;
}
3、一级指针传参
void print(int* pss, int pz)
{
int i = 0;
for(i = 0, i < 10, i++);
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = arr;
int pz = sizeof(arr)/sizeof(arr[0]);
print(p,pz);
return 0;
}
6、二级指针传参
void test(int** z)
{
**z = 20;
}
int main()
{
int a = 10;
int *x = &a;//x是一级指针
int** y = &x;//y是二级指针
test(y)//二级指针传参
test(&x)
int* arr[10] = {0};
test(arr);传存放一级指针的地址
return 0;
}
5、函数指针
1.定义
指向函数的指针,存放函数地址的指针。
int arr(int x, int y)
{
return x + y;
}
int main()
{
arr();
printf("%p\n", arr);
printf("%p\n", &arr);
//函数名:arr == &arr
//数组名:arr != &arr
int (*pf)(int, int) = &arr;
//int (*pf)(int, int)为函数指针类型
//pf就是函数指针变量
//(int, int)为整型类型的参数
//int为整型的返回类型
//pf为函数指针名
int z = (*pf)(3,5);
//(*pf)(3,5)是对arr的解引用操作,但是这里*没有任何实质意义,就是为了理解
//所以可以省略,也可以增加(注:只能函数指针这样)
int z = *pf(3,5);
//这种写法是错误的,根据操作符优先级,意思为对(3,5)后的8进行解引用
//因为pf == arr,而arr(3,5也是进行函数调用),所以也可以这样写
int z = arr(3,5);
int z = pf(3,5);
//显示结果都为8;
return 0;
}
6、函数指针数组
1.定义
存放函数指针的数组
2.代码释义
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int main()
{
int(*s)(int, int) = &add;
int(*ss)(int,int) = ⊂
int(*sss[2])(int,int) = {add, sub};
return 0;
}
7、指向函数指针数组的指针
8、回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。(可以理解为参数为函数指针)
//qsort自动排序库函数
void qsort(void* base,//base中存放的是待排序中第一个对象的地址
//void相当于数据垃圾桶,什么类型的数据都可以放进去
size_t num,//排序数据的个数
size_t size,//排序数据中一个元素的大小,单位是字节
int (*cmp)(const void*, const void*)//用来比较待排序数据中的2个元素的函数
//返回整型>0,第一个>第二个
//返回整型<0,第一个<第二个
//返回整型=0,第一个=第二个
);
//库函数定义
int cmp_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
//e1 > e2为降序
//e1 < e2为升序
}
//库函数使用
int main()
{
int a[] = {9,8,7,6,5,4,3,2,1,0};
int b = sizeof(a) / sizeof(a[0]);
qsort(a, b,sizeof(a[0]), cmp_int);
}
模仿qsort实现一个冒泡排序的通用算法
int sort_by_age(const void* e1, const void* e2)
{
return((struct stu* e1)->age- (const void* e2)->e2);
}
void swap(char* buf1, char* buf2, int width)
{
int f = 0;
for(f = 0, f < width, f++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp
buf1++;
buf2++;
}
}
void bubble_sort(void* base
int sz
int width
int(*cmp)(const void* e1, const void* e1,)
{
int i = 0;
for(i = 0, i < sz-1, i++)
{
int j = 0;
for(j = 0, j < sz-1-i, j++)
{
if(cmp((char*)base + j * width, (char*)base + (j+1) * width) > 0)
{
swap((char*)base + j * width, (char*)base + (j+1) * width) > 0,width)
}
}
}
}
struct stu
{
char name[20];
int age;
}
int main()
{
int b = 0;
struct stu s[2] = {{'wangwu',26,},{"zhangsan",25}};
int b = sizeof(s) / sizeof(s[0]);
bubble_sort(s,b,sizeof(s[0],sort_by_age)
}
9、指针和数组的解析
sizeof计算大小
//sizeof(数组名) - 数组名表示整个数组的-计算的是整个数组的大小
//&数组名 - 数组名表示整个数组,取出的是整个数组的地址
//除此之外,所有的数组名都是数组首元素的地址
int main()
{
int a[4] = {0,1,2,3};
printf("%d\n",sizeof(a));//结果为16
printf("%d\n",sizeof(a+0));//结果为4/8, - a+0是第一个元素的地址,sizeof(a+0)计算的是地
址的大小
printf("%d\n",sizeof(*a));//结果为4, -*a是数组的第一个元素,sizeof(*a)计算的是第一个元素
的大小
printf("%d\n",sizeof(a+1));//结果为4/8, - a+1是第二个元素的地址,sizeof(a+1)计算的地址的
大小
printf("%d\n",sizeof(a[1]));//结果为4,- 计算的是第二个元素的大小
printf("%d\n",sizeof(&a));//结果为4/8, - &a虽然是数组的地址,但也是地址,sizeof(&a)计算的
是一个地址的大小
printf("%d\n",sizeof(*&a));//结果为16, - 计算的是数组的大小
printf("%d\n",sizeof(&a+1));//结果为4/8, - &a+1是数组后面的空间的地址
printf("%d\n",sizeof(&a[0]));//4/8
printf("%d\n",sizeof(&a[0]+1));//4/8
//****要注意函数所占空间的大小和所占地址的大小,所占空间的大小根据数据类型不一样而不一样,
//所占空间大小是固定的,在32位为4字节,在64位为8字节
char arr[] = "abcdef"
printf("%d\n",sizeof(arr));结果为7,""需要包含\0
printf("%d\n",sizeof(arr+0));结果为4/8,首元素地址
printf("%d\n",sizeof(*arr));结果为1
printf("%d\n",sizeof(arr[1]));结果为1
printf("%d\n",sizeof(&arr));结果为4/8
printf("%d\n",sizeof(&arr+1));结果为4/8
printf("%d\n",sizeof(&arr[0]+1));结果为4/8
printf("%d\n",strlen(arr));结果为6 - strlen遇到\0停下来
printf("%d\n",strlen(arr+0));结果为6
printf("%d\n",strlen(*arr));结果为err - 数值
printf("%d\n",strlen(arr[1]));结果为err - 数值
printf("%d\n",strlen(&arr));结果为6
printf("%d\n",strlen(&arr+1));结果为随机
printf("%d\n",strlen(&arr[0]+1));结果为5
char* arrr = "abcdef"
printf("%d\n",sizeof(arrr));结果为4/8
printf("%d\n",sizeof(arrr+1));结果为4/8
printf("%d\n",sizeof(*arrr));结果为1
printf("%d\n",sizeof(arrr[1]));结果为1
printf("%d\n",sizeof(&arrr));结果为4/8
printf("%d\n",sizeof(&arrr+1));结果为4/8
printf("%d\n",sizeof(&arrr[0]+1));结果为4/8
printf("%d\n",strlen(arr));结果为6 - strlen遇到\0停下来
printf("%d\n",strlen(arr+1));结果为5
printf("%d\n",strlen(*arr));结果为err - 数值
printf("%d\n",strlen(arr[1]));结果为err - 数值
printf("%d\n",strlen(&arrr));结果为随机值,取得是arrr前的地址,取得什么类型不知道,so随机
printf("%d\n",strlen(&arr+1));结果为随机
printf("%d\n",strlen(&arr[0]+1));结果为5
return 0;
}
strlen计算大小
int main()
{
char arr[] = {a,b,c,d,e,f};
printf("%d\n", strlen(arr));//结果为随机值 - strlen计算大小,找\0,但是找不到,所以随机值
printf("%d\n", strlen(arr + 0));//结果为随机值
printf("%d\n", strlen(*arr));//结果为err - *arr对arr解引用,传过去数值,所以err
printf("%d\n", strlen(arr[1]));//结果为err
printf("%d\n", strlen(&arr));//结果为随机值
printf("%d\n", strlen(&arr+1));//结果为随机值
printf("%d\n", strlen(&arr[0]+1));//结果为随机值
return 0;
}
十八、结构体
1、定义
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
2、类型与初始化
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如描述学生,学生包含:名字+年龄+性别+学号 这几项信息。
这里只能使用结构体来描述了。
例如:
//结构体可以让C语言创建新的类型
struct b
{
char name[20];
int age;
char sex[15];
char id[30];
}
struct stu //struce结构体关键字,stu变量(书的类型)
{
//结构体成员名(变量)可以是整型、字符等,也可以是其他结构体
struct b sb //其他结构体
char name[20]; //名字
int age; //年龄
char sex[15]; //性别
char id[30]; //学号
}S1 = {"老刘",30,"男","72"}//S1为全局变量
int main()
{
struct stu s = {{'王五',26,'男','996'}"张三",25,"男","52"};//结构体的创建和初始化,s为结构体局部变量名(书名),{}里的为成员
名
printf("%c %s %d %lf\n",s.sb.name s.name,s.age,s.sex,s.id);//找到结构体成员变量用.结构体变量.成员变量
struct stu * pa = &s;//struct stu *结构体指针
printf(""%s %d %f\n", (*pa).name,(*pa).age,(*pa).sex,(*pa).id);
printf(""%s %d %f\n",pa->name,pa->age,pa->sex,pa->id);
//.操作符格式:结构体成员名.成员名
(结构体指针).成员名
结构体指针 -> 成员名
//打印结果都为:张三 20 85.500000
}
3.结构体传参
应首选传址调用
原因:函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈)的系统开销比较大,所以会导致性能的下降。
结论:结构体传参的时候,要传结构体的地址。
struct b
{
char name[20];
int age;
char sex[15];
char id[30];
}
struct stu
{
struct b sb;
char name[20];
int age;
char sex[15];
char id[30];
}S1 = {"老刘",30,"男","72"}
void print1(struct stu b)
{
printf("%c %s %d %lf\n",b.sb.name,b.name,b.age,b.sex,b.id);
}
void print2(struct stu* ps)
{
printf("%c %s %d %f\n",pa->sb.name,pa->name,pa->age,pa->sex,pa->id);
}
int main()
{
struct stu s = {{'王五',26,'男','996'}"张三",25,"男","52"};
print1(s) //传值调用
print2(&s) //传址调用
return 0;
}
十九、分支语句if else
二十、分支语句switch
1、switch
switch语句也是一种分支语句。常常用于多分支的情况。
比如:
输入1,输出苹果
输入1,输出橘子
输入1,输出西瓜
输入1,输出蜜桃
输入1,输出香蕉
输入1,输出葡萄
编程举例:
int main()
{
int a = 0;
scanf("%d\n", &a);
switch(a) //switch(整型表达式)
{ //{}里面包含的语句项
case 1: //case 整型常量表达式:
printf("苹果\n"); //语句
break; //break语句的实际效果是把语句列表划分为不同的部分。
case 2: //case2表示如果a=2从csae2开始执行switch语句,而不是只执行case
// 不执行是因为有break语句,而不是因为a=2
printf("香蕉\n");
break;
case 3:
printf("橘子\n");
break;
case 4:
printf("西瓜\n");
break;
case 5:
printf("葡萄\n");
break;
case 6:
printf("水蜜桃\n");
break;
defaul: //输入超出case1-6,显示输出错误
printf("输入错误\n");
break;
}
return 0;
}
}
2、default
写在任何一个case标签可以出现的位置。
当 switch表达式的值并不匹配所有case标签的值时,这个default子句后面的语句就会执行。
所以,每个switch语句中只能出现一条default子句。
但是它可以出现在语句列表的任何位置,而且语句流会像贯穿一个case标签一样贯穿default子句。
二十一、循环语句while
1、while循环之break
在while循环中,break用于永久的终止循环。
int main()
{
int a = 0;
while(a < 10 && a == 0); //while(表达式)
{
if(a == 5);
break; //break用于永久的终止循环
printf("%d\n", a);
a++; //循环语句
}
return 0;
}
2、while循环之continue
在while循环中,continue的作用是跳过本次循环continue后边的代码/直接去判断部分,看是否进行下一次循环。
int main()
{
int a = 0;
while(a < 10 && a == 0); //while(表达式)
{ //{}里的是while循环
if(a == 5);
continue; //break用于永久的终止循环
printf("%d\n", a);
a++; //循环语句
}
return 0;
}
3、while循环之getchar、putchar
int main()
{
int ch = 0;
while((ch = getchar())!=EOF)
//EOF - end of file -文件结束标志 - -1
//getchar输入一个字符,返回值为int
//getchar写入的是ASCII的字符
//putchar输出一个字符
putchar(ch);
return 0;
}
4、while循环之scanf与getchar
int main()
{
char password[20] = {0};
printf("请输入密码");
scanf("%s\n", password);
printf("请确认密码(Y/N):");
int a = getchar();
getchar(); // 清理缓冲区,处理"\n"
int b = 0;
while((b=getchar()) != \n));
{
;
}
if(a == 'Y');
{
printf("密码正确\n");
}
else
{
printf("密码错误\n");
}
return 0;
}
getchar/scanf怎么输入?
问题一、密码是123456时,scanf从缓冲区取值123456留下\n,当程序执行到getchar,本应重新输入值,但是缓冲区还有值,所以去了\n,密码不等于Y,也不等于N,显示错误。
解决方法:添加getchar()函数,从缓冲区取了\n。
问题二、密码是123456 abcdef\n时,scanf从缓冲区取空格前的值,即123456,但是getchar只能取一个字符,最终显示失败。
解决方法:加入代码如下,让缓冲区有值时一直循环取值。直到为空缓冲区。
int b = 0;
while((b=getchar()) != \n));
{
;
}
二十二、for循环
1、使用
表达式1表达式1为初始化部分,用于初始化循环变量的。 表达式2 表达式2为条件判断部分,用于判断循环时候终止。 表达式3 表达式3为调整部分,用于循环条件的调整。
int main()
{
int i = 0;
for(i = 0; i < 10; i++); //语法:for(表达式1; 表达式2; 表达式3);
// for(初始化;判断;调整)
{ //{}里的循环语句
if(i == 5);
break; //跳出循环
continue; //当遇到continue,去执行for循环的第一步,下面略过
printf("%d\n, i")
}
return 0;
}
2、建议
1.不可在for 循环体内修改循环变量,防止 for 循环失去控制。
如:for(i = 0; i < 0; i++);,i不可在for循环内改变。
2.建议for语句的循环控制变量的取值采用“前闭后开区间“写法,
int i = 0;
//前闭后开的写法
for(i = 0; i < 10; i++);
{
}
//前闭后闭的写法
for(i = 0; i <= 10; i++);
{
}
3、for循环的变种
变种1:
int main()
{
//判断部分的省略 - 判断部分恒为真
for(;;);
{
printf("hehe\n");
}
}
int main()
{
int a = 0;
int b = 0;
for(a = 0; a < 3; a++);
{
for(b = 0; b < 3; b++);
{
printf("hehe\n");
}
}
return 0;
// 显示结果为9
}
int main()
{
int a = 0;
int b = 0;
for(; a < 3; a++);
{
for(; b < 3; b++);
{
printf("hehe\n");
}
}
return 0;
// 显示结果为3
}
//显示值为9是因为第一个程序第二个for循环,b=0初始化,第二个则没有
变种2:
int x, y;
for(x = 0, y = 0; x < 2 && y < 5; x++, y++);
{
x++;
}
4、基于判断真假的for循环
int main()
{
int a = 0;
int b = 0;
for(a = 0,b = 0; a = 0; a++, b++); //判断部分a=0,a为假,所以不会循环
{
printf("hehe\n");
}
return 0;
}
二十三、do..while循环
1、使用
int main()
{
int a = 1;
do //do{循环语句}
{
printf("%d", i);
i++
}while(i <= 10); //while(循环条件);//当不满足循环条件则跳出循环
return 0;
}
2、do while循环之break
int main()
{
int a = 1;
do //do{循环语句}
{
if(i == 5);
{
printf("%d", i);
break; //break跳出循环
}
i++
}while(i <= 10); //while(循环条件);//当不满足循环条件则跳出循环
return 0;
//最终显示结果为:1,2,3,4
}
3、do while循环之continue
int main()
{
int a = 1;
do //do{循环语句}
{
if(i == 5);
{
printf("%d", i);
continue; //continue跳到循环开始
}
i++
}while(i <= 10); //while(循环条件);//当不满足循环条件则跳出循环
return 0;
//最终显示结果为:1,2,3,4,6,7,8,9,10
}
4、do while语句的特点
① 循环至少执行一次,使用的场景有限,所以不是经常使用。
二十四、数组
数组名:数组首元素(arr[0])的内存地址,但是有两个列外:
- sizeof(数组名)- 数组名表示整个数组 - 计算的是整个数组的大小,单位是字节
- &数组名 - 数组名表示整个数组 - 取出的是整个数组的地址。ps.数组的地址和数组首元素的地址是一样的。
- 数组传参传的是首元素的地址;
-
&arr+1; // 移动40个字节 arr+1; // 移动4个字节
数组:一组相同类型元素的集合
1、一维数组的创建和初始化
int main()
{
int arr[10] = {0}; //存放整型的叫做整型数组
char ch[8] = {0}; //存放字符的叫做字符数组
return 0;
}
- 数组的创建方式
type arr_name [const_n];
//type是指数组的元素类型
//const_n是一个常量表达式,用来指定数组的大小
如:
{
int arr[8];
char ch[5];
}
- 数组的初始化:在创建数组的同时给数组的内容一些合理初始值(初始化)。
int main()
{
int a = 10;
int arr[10] = {1,2,3,4,5,6,7,8,9,10}; //完全初始化
int ch[10] = {1,2,3,4,5}; //不完全初始化
3、数组的长度
int main()
{
char arr1[10] = {1,2,3.4.5.6.7.8.9.10};
char arr2[] = {1,2,3,4,5,}
return 0;
}
4、数组的地址
int main()
{
int arr1[10] = {1,2,3.4.5.6.7.8.9.10};
arr;//数组名首元素的地址
&arr;//数组的地址
int* p1 = arr;
int (*p2)[10] = &arr;
//语法规定arr与&arr的不同
printf("%p\n", p1+1);//移动4个字节,移动一个数组
printf("%p\n", p2+1);//移动40个字节,移动整个数组
return 0;
}
5、数组解引用
int main()
{
int arr1[10] = {1,2,3.4.5.6.7.8.9.10};
int (*pp)[10] = &arr1;
return 0;
}
pp
*pp
//pp与*pp的区别
//pp表示整个数组的地址,从arr1[0] -> arr1[10]
//*pp表示对整个数组进行解引用,解引用后为arr1,而arr1为首元素地址,则为arr[0]/1.
2、数组在内存的存放
- 一堆数组在内存中是连续存放的
- 随着数组下标的增长,地址是由低到高变化的
3、二维数组的创建
二维数组的数组名为首元素地址
二维数组的首元素为:第一行!
int main()
{
int arr[3][4]; //arr为数组名;int为函数类型;3,4表示3行4列
return 0;
}
4、二维数组的初始化
int main()
{
int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
//完全初始化显示结果为:第一行1,2,3,4 第二行5,6,7,8 第三行9,10,11,12
//行数为3,行号为0,1,2
//列数为4,行号为0,1,2,3
int arr1[3][4] = {1,2,3,4,5,6,7};
//不完全初始化显示结果为:第一行1,2,3,4 第二行5,6,7,0 第三行0,0,0,0
int arr2[3][4] = {{1,2},{3,4},{5,6}};
//显示结果为第一行1,2,0,0, 第二行3,4,0,0 第三行5,6,0,0
//{}里的代表几行,行7可以省略,但是列8不可以省略
}
5、二维数组的元素确定
int main()
{
int arr[3][4] = {{1,2},{3,4},{5,6}};
int i = 0;
int j = 0;
for(i = 0, i < 3, i++);
{
for(j = 0, j < 4, j++);
{
printf("%d", arr[i][j]);
printf("arr[%d][%d] = %p\n,i,j,&arr[i][j]"); //取二维数组的内存地址
//&arr[i][j]的意思是取arr[0][0]->arr[3][4]的值,共12个值
}
print("\n");
}
return 0;
}
- 二维数组与一维数组的存放是连续的
一维:从左到右,差四个字节
二维:从左到右,从上到下,隔行从左到右差四个字节
6、数组作为函数参数
vold bubble_sort(int arr,int sz); 定义函数bubble_sort
{
int i = 0; //第几次循环换值
for(i = 0, i < sz-1; i++); //sz次比较循环
{
int j = 0; //第三个桶用于换值
for (j = 0; j < sz-1-i; j++);//没少sz次就少一次比较循环
{
if(arr[j] > arr[j + 1])
{
int tmp = arr[j]; //三色桶思维换值
arr[j] = arr[i];
arr[i] = int tmp;
}
}
}
int main()
{
int arr[] = {0,1,2,3,4,5,6,7,8,9};
int sz = sizeof(arr) / sizeof(arr[0])); //通过计算数组长度计算数组的个数
bubble_sort(arr,sz); //调用函数bubble_sort
return 0;
}