目录
C 基础1⭐
数据类型
计算机中的单位
常量与变量
字符串/转意字符/ 注释
C 基础2⭐
判断语句
函数与数组
C语言操作符
C语言关键字
C语言定义宏与关键字
初始指针 ⭐⭐
指针基础
拓展知识 — 结构体
分支与循环⭐
分支语句
if语句
switch语句
循环语句
while循环
for循环
do while 循环
goto 语句
函数⭐⭐
什么是函数
函数的分类
函数的实参和形参
函数的调用
函数的嵌套调用
递归函数
数组⭐
一维数组的创建和初始化
二维数组的创建和初始化
数组作为函数参数
数组地址表达的区别
课外拓展
操作付讲解⭐
操作付分类
表达式求值
算数转换
操作符属性
初阶指针⭐⭐⭐
指针是什么
指针和指针的类型的意思
野指针
指针的运算
指针和数组
二级指针
指针数组
初始结构体⭐
结构体类型的声明
结构体的定义和初始化
结构体成员的访问
结构体成员的传参
调试技巧⭐⭐
什么是bug?
什么是调试?
Windows环境调试介绍
调试实例
如何写出优秀代码
编程常见错误
声明:本笔记博客的图片和代码,文本根据,鹏哥C语言(B站)/菜鸟教程的C语言分类进行编排,特此鸣谢。
📕本章主题
1️⃣一级标题
2️⃣二级标题
C基础 1📕
1.数据类型 1️⃣
C语言的数据类型如下:
char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整形
float //单精度浮点数
double //双精度浮点数
#include<stdio.h>
//各数据类型大小 (单位:字节)
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;
}
2.计算机中的单位/二进制解析1️⃣
2.1计算机中的单位
计算机中一比特位(又称字节)可以存一个二进制位(存放0或者1),1bit可以存放2^1次方的二进制数据,以此类推。n个bit可以存放2的n次方的二进制数据。数据的最大值是2^n-1,因为要去掉0这个值所以是2^n-1。
1 byte<字节> = 8bit<比特位>
1 kb = 1024 byte <字节>
1 mb = 1024 kbss
1 gb = 1024 mb
1 tb = 1024 gb
unsigned int能表示的最大值是2^32-1。至于原因,看了下面的例子就会明白:假设一个单片机用二位二进制来表示数,很明显就是00,01, 10, 11(这里表示2^2 (2^2 = 4)次方种可能,写成0~2^2-1 = 4)这四种情况,分别表示0,1, 2, 3这四个数。这就对了,原来0也占用了一种状态,总共能表示2^32种状态的16位二进制数就只能表示:0~2^32-1,即0~32767了。
2.2 二进制解析
点击上方蓝色字体进入网页解析
3.常量和变量1️⃣
变量就是能被改变的量,常量就是不能改变的量
3.1变量的分类
变量分为局部变量和全局变量,局部变量 { } 内部定义的变量是局部变量,{ }外部则是全局变量
当局部变量和全局变量名字冲突的情况下,局部优先。不建议:把全局变量和局部变量的名字写成一样
#include(stdio.h)
int a = 100;//全局变量
int mian(){
int a = 10; //局部变量
return 0;
}
3.1.1变量的作用区域与生命周期
作用域( Scope ) , 程序设计概念,通常来说, 一段程序代码中所用到的名字并不总是有效/可用的而限定这个名字的可用性的代码范围就是这个名字的作用域。通俗来说,这个变量那里可以使用,那里就是它的作用域。
1.局部变量的作用域:就是变量所在的局部范围。
2.全局变量的作用域:整个工程。
变量的什么周期就是变量的创建和销毁之间的时间段
1.局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
2.全局变量的生命周期是:整个程序的生命周期。
3.2常量的分类
C语言中的常量分为以下以下几种:
字面常量
const 修饰的常变量
#define 定义的标识符常量
枚举常量
#include<stdio.h>
enum Sex
{
MALE,
FEMALE,
SECRET
};//枚举常量
#define MAX 10000 //标识符常量
int main(){
20 //字面常量
const int = 10; //常变量
return 0;
}
4.字符串 转意字符 注释1️⃣
4.1字符串
这种由双引号( Double Quote )引起来的一串字符称为字符串字面值( String Literal) , 或者简称字符串。注:字符串的结束标志是一个\0的转义字符。在计算字符串长度的时候\0是结束标志,不算作字符串内容。
#include<stdio.h>
int main()
{
char a = "HELLO WORLD" //字符串
printf("%s",a);
return 0;
}
4.2 转意字符 与 注释
转移字符表(下表部分)
字符 | 意义 | ASCII码值(10进制) |
\a | 响铃(BEL) | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
int main()
{
printf("%d\n", strlen("abcde"));//转义字符
printf("%d\n", strlen("c:\test\348\test.c"));
return 0;
}
int main()
{
char arr[] = "halo bit";
printf("%d\n", sizeof(arr));
printf("%d", strlen(arr));
//strlen - 函数 求字符串长度的,找\0之前出现的字符个数
//sizeof - 操作符 - 计算类型/变量所占用内存的大小,单位是字节 转义字符也会占用内存空间
}
int main()
{
// char arr[] = "halo bit"; 单行注释
/*printf("%d\n", sizeof(arr));
printf("%d", strlen(arr));*/ //多行注释
}
C基础 2📕
1.初始判断与循环1️⃣
1.1 判断语句if
if语句的判断标准为 结果为真会执行下方语句,为假则不执行。
#include <stdio.h>
int main ()
{
/* 局部变量定义 */
int a = 10;·
if( a < 20 )
{
/* 如果条件为真,则输出下面的语句 */
printf("a 小于 20\n" );
}
printf("a 的值是 %d\n", a);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main() {
int input = 0;
printf("join us ?\n");
printf("好好学习?(1/0):");
scanf("%d",&input);//接受键盘输入的值然后将值赋给 input
if (input == 1)
{
printf("right\n");
}else
{
printf("XXXXX\n");
}
return 0;
}
1.2 循环语句 while
#include<stdio.h>
int main() {
int line = 0;
//int u = 0;
//printf("a = %d\n", a);
while (line < 30000) {
line++;
printf("敲%d行代码\n", line);
//printf("敲代码第%d行\n", "&line");
}
if (line == 30000) {
printf("好f\n");
}
return 0;
}
3.函数与数组1️⃣
3.1函数
函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。
您可以把代码划分到不同的函数中。如何划分代码到不同的函数中是由您来决定的,但在逻辑上,划分通常是根据每个函数执行一个特定的任务来进行的。函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。C 标准库提供了大量的程序可以调用的内置函数。例如,函数 strcat() 用来连接两个字符串,函数 memcpy() 用来复制内存到另一个位置。函数还有很多叫法,比如方法、子例程或程序,等等。(引用菜鸟教程 — C函数)
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
int add(int x, int y) //add是个函数
{
int z = 0;
z = x + y;
return z;
}
int main() {
int n1 = 0;
int n2 = 0;
scanf("%d%d", &n1, &n2); //接受输入的值,然后将值赋给吧
int sum = add(n1, n2);//将N1/2 的值传到add函数中去
printf("%d", sum);
return 0;
}
3.2数组
C 语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。数组的声明并不是声明一个个单独的变量,比如 runoob0、runoob1、...、runoob99,而是声明一个数组变量,比如 runoob,然后使用 runoob[0]、runoob[1]、...、runoob[99] 来代表一个个单独的变量。—— 菜鸟教程
所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
一组相同类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量,数组名就是地址,所以在scanf函数里不用填写&。
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// 下标 0 1 2 3 4 5 6 7 8 9
char ar[5] = { 'a','b','c' };//不完全初始化,剩余的默认为0
return 0;
}
4.C语言操作符1️⃣
4.1算数操作符
+ — * / % // 加 减 乘 除 取余
int main() {
int a = 9 % 2;
float b = 9 / 2;
printf("%d", a);
return 0;
}
关于取模操作符
求余运算或者模除运算。其运算符为%,a%b,其中a和b都是整数。计算规则为,计算a除以b,得到的余数就是取模的结果。比如100%17 即100 = 17*5+15。 于是100%17 = 15。类似的2÷3 = 0 …… 2。即2 = 3*0+2。于是 2%3=2。这里有一个规律,如果b>a>0,那么a%b=a。
4.2 C语言操作符
4.2.1 位移操作符
二进制左移运算符。将一个运算对象的各二进制位全部左移或右移若干位(左边的二进制位丢弃,右边补0)。
<< >>//移位操作符
int main() {
int a = 2;
int b = a << 2 ;
printf("%d", b);
return 0;
}
4.2.2位操作符
位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:
//按位与 &
//按位或 |
//按位异或 ^
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int a = 1;
int b = 0;
int c = 0;
printf("a = %d b = %d\n", a,b);
c = a | b;
printf("a | b = %d\n", c);
c = a ^ b;
printf("a ^ b = %d\n", c);
c = a & b;
printf("a & b = %d\n", c);
return 0;
}
4.3赋值操作符
下表列出了 C 语言支持的赋值运算符:
#include <stdio.h>
int main()
{
int a = 21;
int c ;
c = a;
printf("Line 1 - = 运算符实例,c 的值 = %d\n", c );
c += a;
printf("Line 2 - += 运算符实例,c 的值 = %d\n", c );
c -= a;
printf("Line 3 - -= 运算符实例,c 的值 = %d\n", c );
c *= a;
printf("Line 4 - *= 运算符实例,c 的值 = %d\n", c );
c /= a;
printf("Line 5 - /= 运算符实例,c 的值 = %d\n", c );
c = 200;
c %= a;
printf("Line 6 - %%= 运算符实例,c 的值 = %d\n", c );
c <<= 2;
printf("Line 7 - <<= 运算符实例,c 的值 = %d\n", c );
c >>= 2;
printf("Line 8 - >>= 运算符实例,c 的值 = %d\n", c );
c &= 2;
printf("Line 9 - &= 运算符实例,c 的值 = %d\n", c );
c ^= 2;
printf("Line 10 - ^= 运算符实例,c 的值 = %d\n", c );
c |= 2;
printf("Line 11 - |= 运算符实例,c 的值 = %d\n", c );
}
Line 1 - = 运算符实例,c 的值 = 21
Line 2 - += 运算符实例,c 的值 = 42
Line 3 - -= 运算符实例,c 的值 = 21
Line 4 - *= 运算符实例,c 的值 = 441
Line 5 - /= 运算符实例,c 的值 = 21
Line 6 - %= 运算符实例,c 的值 = 11
Line 7 - <<= 运算符实例,c 的值 = 44
Line 8 - >>= 运算符实例,c 的值 = 11
Line 9 - &= 运算符实例,c 的值 = 2
Line 10 - ^= 运算符实例,c 的值 = 0
Line 11 - |= 运算符实例,c 的值 = 2
4.5单目操作符
逻辑反操作符、正值与负值
//逻辑 !操作
//检查两个操作数的值是否相等,如果不相等则条件为真。
#include<stdio,h>
int main()
{
int a = 10;
int b = 20;
while(a != b) //词句代码翻译为 判断a是否不等于b,a是20,b是10,20≠10,条件判断为真,输出打印ture
{
printf("ture");
}
return 0;
}
- //负值
a = -5;
+ //正值
a = 5;
取地址符、sizeof、前置与后置++ --
& //取地址操作符
#include<stdio.h>
int main(){
int a = 10;
int * p = NULL;//创建整形指针变量。
p = &a; //将a的地址存储到指针p变量中。
*p = 20; //*p 等于 a,这里a将会被改成20。*间接访问操作符( * 叫解引用操作符)
//sizeof 表达式 定义 sizeof是C/C++中的一个操作符(operator)
//简单的说其作用就是返回一个对象或者类型所占的内存字节数。
printf("%d", sizeof(a));
return 0;
}
//前置与后置++ --
#inclue<stdio.h>
int main(){
int a = 10;
int b = a++;//这里是先把a的值赋给b,再进行++运算,所以这里b还是10。
int c = ++a;
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
printf("a = %d\n", a);
return 0;
}
取反操作符 < ~ >
对一个数的二进制按位取反,把所有的二进制中的数字,1变成0,0变成1。整数在内存中存储的是补码,一个整数的二进制表示有3钟,分别是原码 反码 补码。正整数的原码、补码、反码相同。
负整数源码、反码,补码的计算方式
-1的原码的最高位是1,1是符号位代表这个整数数是负数。反码 符号位不变,其余数值全部取反。补码 在原有的二进制序列上 + 1。
//-1的 原码 反码 补码
10000000000000000000000000000001 原码
11111111111111111111111111111110 反码
11111111111111111111111111111111 补码
//-1取反解析
#include<stdio.h>
int main() {
int a = 0;
printf("反 = %d\n", ~a);
printf("%d", a);
return;
}
//整数a = 0;
//0的二进制序列如下:
//00000000 00000000 00000000 00000000
//未取反前,0存储于内存中的二进制序列,整数在内存中存储的是补码!!!
//~a 这里将0的二进制序列进行取反
//此时取反运算也是在内存中计算的,0取反后的二进制序列的值依然存储于内存中,
//整数在内存中存储的是补码,所以此时的~a(~0)的二进制序列补码如下。
//11111111 11111111 11111111 11111111 (此二进制序列值转为原码后值为-1)
int4个字节表示32个0,按位取反得32个1,内存储存方式为补码,打印函数电脑会把补码换算成原码。
4.6 关系操作符
>
<=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
#include <stdio.h>
int main()
{
int a = 21;
int b = 10;
int c ;
if( a == b )
{
printf("Line 1 - a 等于 b\n" );
}
else
{
printf("Line 1 - a 不等于 b\n" );
}
if ( a < b )
{
printf("Line 2 - a 小于 b\n" );
}
else
{
printf("Line 2 - a 不小于 b\n" );
}
if ( a > b )
{
printf("Line 3 - a 大于 b\n" );
}
else
{
printf("Line 3 - a 不大于 b\n" );
}
/* 改变 a 和 b 的值 */
a = 5;
b = 20;
if ( a <= b )
{
printf("Line 4 - a 小于或等于 b\n" );
}
if ( b >= a )
{
printf("Line 5 - b 大于或等于 a\n" );
}
}
当上面的代码被编译和执行时,它会产生下列结果:
Line 1 - a 不等于 b
Line 2 - a 不小于 b
Line 3 - a 大于 b
Line 4 - a 小于或等于 b
Line 5 - b 大于或等于 a
4.7逻辑操作符
#include <stdio.h>
int main()
{
int a = 5;
int b = 20;
int c ;
if ( a && b )
{
printf("Line 1 - 条件为真\n" );
}
if ( a || b )
{
printf("Line 2 - 条件为真\n" );
}
/* 改变 a 和 b 的值 */
a = 0;
b = 10;
if ( a && b )
{
printf("Line 3 - 条件为真\n" );
}
else
{
printf("Line 3 - 条件为假\n" );
}
if ( !(a && b) )
{
printf("Line 4 - 条件为真\n" );
}
}
当上面的代码被编译和执行时,它会产生下列结果:
Line 1 - 条件为真
Line 2 - 条件为真
Line 3 - 条件为假
Line 4 - 条件为真
4.8条件操作符
公式: exp1?exp2:expe3:
exp1成立(为真),exp2计算,整个表达式的结构式,exp2的结果
exp1不成立(为假),exp3计算,整个表达式的结构式,exp3的结果
int main(){
int a = 0;
int b = 3;
int max = 0;
max = a > b ? a : b;
printf("% d\n", max);
return;
}
4.9逗号操作符与其他操作符
逗号操作符 ,逗号
⬇代码演示⬇
int a = 0;
int b = 3;
int c = 5;
int d = (a = b + 2,c = a - 4,b = c + 2);
//逗号表达式,是从左向右依次计算的
//整个表达式的结果是最后一个表达式的结果
下标操作符 [ ] 中括号
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9 };
printf("%d\n", arr[5]);
return;
}
函数调用操作 ( )小括号与其他暂未讲解的操作符
ntf("hehe");
return 3;
}
int main(){
int well();
}
下方暂未讲解,文字简介
& 取地址操作符,读取一个变量的地址
* 解引用操作符,指针变量解引用后,即可改变对应地址的值。
. 点操作符 ab.c 去ab中取出或修改c的内容
->指针操作符 ab->c 去ab中取出或修改c的内容
5.C语言关键字1️⃣
下方是C语言中常见的关键字,先简单介绍几种,其他关键字使用时再做介绍
⬇代码演示⬇
auto break case char const
continue default do double
else enum extern float for
return short signed sizeof
static struct switch typedef
union unsigned void volatile while
5.1 Typede 类型名冲定义
typedef 顾名思义是类型定义,这里应该理解为类型重命名,他可以让需要修饰的复杂数据类型简化。
⬇代码演示⬇
typedef unsigned int u_nt; //类型名重定义。
int main() {
u_nt a = 100;// 这里等于typedef unsigned a = 100;
return 0;
}
5.2 Static 修饰
static是用来修饰变量和函数的
1. 修饰局部变量-称为静态局部变量
2. 修饰全局变量-称为静态全局变量
3. 修饰函数-称为静态函数
5.2.1 Static修饰局部变量
⬇代码演示⬇
void tet() {
static int a = 1; //int a = 1;
a++;
printf("%d ", a);
}
int main() {
int i = 0;
while (i < 10)
{
tet();
i++;
}
return 0;
}
static修饰局部变量改变了变量的生命周期。(本质是修改了变量的存储类型),让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。
5.2.2 Static修饰全局变量
⬇代码演示⬇
//代码1
//add.c
int g_val = 2018;
//test.c
int main()
{
printf("%d\n", g_val);
return 0;
}
//代码2
//add.c
static int g_val = 2018;
//test.c
int main()
{
printf("%d\n", g_val);
return 0;
}
代码1正常,代码2在编译的时候会出现连接性错误。
结论:一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用,本质是将函数的外部链接属性转换成内部连接属性
5.2.3 Static 修饰全局函数
⬇代码演示⬇
//代码1
//add.c
int Add(int x, int y)
{
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}
//代码2
//add.c
static int Add(int x, int y)
{
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}
代码1正常,代码2在编译的时候会出现连接性错误.
结论:一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。本质是将函数的外部链接属性转换成内部连接属性
剩余关键字后续课程中陆续会讲解。
6.C语言定义宏 与 关键字define1️⃣
计算机科学里的宏是一种抽象(Abstraction),它根据一系列预定义的规则替换一定的文本模式。解释器或编译器在遇到宏时会自动进行这一模式替换。对于编译语言,宏展开在编译时发生,进行宏展开的工具常被称为宏展开器。
简单来说 宏就是替换复杂的代码,简化代码。
⬇代码演示⬇
//define定义标识符常量 ,以后程序代码中只要输入max就是代表1000
#define MAX 1000
//define定义宏
#define ADD(x, y) ((x)+(y))
#include <stdio.h>
int main()
{
int sum = ADD(2, 3);
printf("sum = %d\n", sum);
sum = 10*ADD(2, 3);
printf("sum = %d\n", sum);
return 0;
}
C 基础3 指针初识📕
1.指针与内存
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节(1个字节 = 8bit(位))。为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。如果把一个bit设置成一个内存单元,内存空间就会细分的太细,所以平衡后,就直接把一个byte(字节)设成了一个内存单元。
2.指针是什么
指针变量存的就是存储普通变量的地址,然后通过这个地址找到普通变量的值,可以修改和查询对应地址存储的值。指针变量也是变量的一种。整形变量,字符变量,浮点型变量,他们都是变量,只是他们存储的对象不同。
1、c语言中*p表示此指针指向的内存地址中存放的内容。*p一般是一个和指针类型一致的变量或者常量。*p是让程序去那个地址取出数据。
2、例子:int a[5]={1,2,3,4,5};int *p=a;//这是在定义指针变量p的同时就直接给它初始化,即把数组a的首地址赋给它。
2.1 取地址符(&)和 解引用操作(*)
指针就是地址,使用指针变量的时候,使用的就是指针变量中存储的地址,所以指针就是地址,指针变量存储的值就是地址。所以改变指针的值,就是改变存储在指针中地址的相对应的变量的值。
int main()
{
int a = 10;
int b = 5;
int *p = &a; //将a的地址存到指针变量p中,*代表这个变量是一个指针.
*p = 30; //*p解引用后就相当于a,这里进行赋值操作后,a会变成30
b = *p; //同理,*p也可以把a中存储的常量值,来赋给其他变量,这里运算后,b会变成30;
return 0;
}
上图中代码是吧a的地址存到了指针pa中,*pa就相当于是把a的值拿过来,通过*pa可以修改a的值,也可以将a里面存储的值(30),赋给其他变量。
2.1 指针变量的大小
指针是一种变量类型,一般来说一个变量的大小都是固定的,比如 char 类型的变量是1个字节,int类型4个字节,指针变量在32位系统下,是固定的4个字节,64位系统即是8个字节,指针需要多大空间取决于地址的存储需要多大空间。
⬇代码演示⬇
int main()
{
printf("%d\n", sizeof(char *));
printf("%d\n", sizeof(short *));
printf("%d\n", sizeof(int *));
printf("%d\n", sizeof(double *));
return 0;
}
关于指针为什么是4个字节,大家可以看拓展链接:https://www.bilibili.com/read/cv5679546
3. 什么是结构体
结构体是一种数据结构,它由不同数据类型的数据组合成一个整体,以便引用,这些组合在一个整体中的数据是互相联系的,这样的数据结构称为结构体,它相当于其它高级语言中记录。
声明一个结构休类型的一般形式如下:⬇代码演示⬇
#include<stdio.h>
//结构体可以让C创建新的类型
//例 创建一个学生类型
struct Stu
{
char name[20];
int age;
double score;
};
//例 创建一个书类型
struct Book
{
char name[20];//成员变量
int price;
char id[30];
};
int main()
{
struct Stu s = { "Leek", 27, 66.99 }; //结构体的创建和初始化
printf("1:%s\n%d\n%lf\n", s.name, s.age, s.score); //结构体的变量,成员变量
struct Stu* ps = &s;
printf("\n2:%s\n%d\n%lf\n", (*ps).name, (*ps).age, (*ps).score);
printf("\n2:%s\n%d\n%lf\n", ps->name, ps->age,ps->score);
return 0;
公式如下
struct 结构体名
{成员列表};
C 基础 分支语句与循环语句📕
C语言是一门结构化的设计语言,C代码中主要包括顺序结构、选择结构、循环结构。
1.分支语句
1.1 判断语句 if
if的的语法结构
⬇代码示例⬇
//单判断
if(表达式)
语句1;
//双分支
if(表达式)
语句1;
else
语句2;
//多分支
if(表达式1)
语句1;
else if(表达式2)
语句2;
else
语句3;
else的匹配,else是和它最近的if匹配的。
⬇ 代码示例 ⬇
#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if(a == 1)
if(b == 2)
printf("hehe\n");
else
printf("haha\n");
return 0;
}
//代码结果直接返回0,不打印任何语句。
1.2 选择语句 switch
switch语句也是一种分支语句。常常用于多分支的情况,Switch(c)中C必须是整形,不能是float 浮点型。
⬇代码示例⬇
#include <stdio.h>
int main ()
{
/* 局部变量定义 */
char grade = 'B';
switch(grade)
{
case 'A' :
printf("很棒!\n" );
break;
case 'B' :
case 'C' :
printf("做得好\n" );
break;
case 'D' :
printf("您通过了\n" );
break;
case 'F' :
printf("最好再试一下\n" );
break;
default :
printf("无效的成绩\n" );
}
printf("您的成绩是 %c\n", grade );
return 0;
}
1.switch语句中的break
在switch语句中,我们没办法直接实现分支,搭配break使用才能实现真正的分支。break语句 的实际效果是把语句列表划分为不同的分支部分。
2.default子句
简单的说就是如果所有case语句都不满足条件,就会执行default语句
如果表达的值与所有的case标签的值都不匹配怎么办?其实也没什么,结构就是所有的语句都被跳过而已。程序并不会终止,也不会报错,因为这种情况在C中并不认为是个错误。但是,如果你并不想忽略不匹配所有标签的表达式的值时该怎么办呢?你可以在语句列表中增加一条default子句,把下面的标签default:写在任何一个 case 标签可以出现的位置。当 switch 表达式的值并不匹配所有 case 标签的值时,这个 default 子句后面的语句就会执行。所以,每个switch语句中只能出现一条default子句。但是它可以出现在语句列表的任何位置,而且语句流会像执行一个case标签一样执行default子句。
2.循环语句
while循环
do while 循环
for 循环
2.1循环语句 while
2.1.1while循环 语法结构
当条件满足的情况下,while语句内的语句执行,否则不执行。但是这个语句只会执行一次
⬇代码示例⬇
#include <stdio.h>
int main ()
{
/* 局部变量定义 */
int a = 10;
/* while 循环执行 */
while( a < 20 )
{
printf("a 的值: %d\n", a);
a++;
}
return 0;
}
2.1.1 while语句break和continue的作用
#include <stdio.h>
int main ()
{
/* 局部变量定义 */
int a = 10;
/* while 循环执行 */
while( a < 20 )
{
printf("a 的值: %d\n", a);
if(a == 12);
continue;//a等于12时,会直接跳过本次循环
if(a == 17);
break;//a等于12时,会直接跳出当前while循环。
a++;
}
return 0;
}
其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。所以while中的break是用于永久终止循环continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接跳转到while语句的判断部分。进行下一次循环的入口判断。
2.1.2 知识拓展 putchar/getchar 和EOF
⏩代码示例
int main()
{
int i = 0;
while (i <= 10)
{
int ch = 0;
while ((ch = getchar()) != EOF)
{
putchar(ch);
}
}
return 0;
}
EOF — End of file(文件结束标志(值为 -1)
putchar — 输出一个字符 (功能类似printf,从输入缓冲区获取并输出一个字符)
getchar — 获取一个字符 (功能类似scanf,从键盘获取一个字符)
2.2 循环语句 for
2.2.1for循环 语法结构文
FOR 循环允许您编写一个执行指定次数的循环控制结构。
表达式1 — 表达式1为初始化部分,用于初始化循环变量的。
表达式2 — 表达式2为条件判断部分,用于判断循环时候终止。
表达式3 — 表达式3为调整部分,用于循环条件的调整。
for ( init; condition; increment )
{
statement(s);
}
FOR (表达式1(初始化); 表达式2(判断); 表达式3(调整))
⏩代码示例
//代码1
#include <stdio.h>
int main ()
{
/* for 循环执行 */
for( int a = 10; a < 20; a = ++ )
{
printf("a 的值: %d\n", a);
}
return 0;
}
//代码2
int main() {
int j = 0;
int i = 0;
for (; i < 3; i++)
{
printf("%d ",i);
for (; j <3; j++)
{
printf("hehe \n");
}
}
return 0;
}
2.3 循环语句 do ··· while
2.3.1 do while循环的结构简介
DO While 循环允许您编写一个执行指定次数的循环控制结构,就是说不管条件成立与否,都会运行一次循环,不像 for 和 while 循环,它们是在循环头部测试循环条件。在 C 语言中,do...while 循环是在循环的尾部检查它的条件。do...while 循环与 while 循环类似,但是 do...while 循环会确保至少执行一次循环。
do//这里直接进行循环,不进行判断
{
statement(s);//循环语句
}while( condition );//判断语句
⏩代码示例
#include <stdio.h>
int main ()
{
/* 局部变量定义 */
int a = 10;
/* do 循环执行,在条件被测试之前至少执行一次 */
do
{
printf("a 的值: %d\n", a);
a = a + 1;
}while( a < 20 );
return 0;
}
//时间戳 — 一个时间转换成的一个数字
2.4 跳转语句 goto
goto语句就是直接跳转到锚点的地方开始执行。
从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程。
例如:一次跳出两层或多层循环。多层循环这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。
⏩代码示例
#include <stdio.h>
int main()
{
char input[10] = {0};
system("shutdown -s -t 60");
again: //锚点 agian
printf("电脑将在1分钟内关机,如果输入:我是猪,就取消关机!\n请输入:>");
scanf("%s", input);
if(0 == strcmp(input, "我是猪"))
{
system("shutdown -a");
}
else
{
goto again;//会跳转到again锚点开始执行语句。
}
return 0;
}
C 基础 什么是函数📕
在计算机科学中,子程序,是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
函数分为 库函数 和 自定义函数
库函数指的就是C语言已经集成的功能函数,如scanf,printf等这些都算是库函数
自建函数则是用户自己编写的函数,为了实现某一特定功能。
1. 库函数
为什么会有库函数,库函数就是C语言已经统一标准的的函数功能。例如:printf scanf gets 等它们都是属于C的库函数。
我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕上(printf)。
在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。那怎么学习库函数呢?这里我们简单的看看:www.cplusplus.com
C语言库函数手册
MSDN(Microsoft Developer Network) www.cplusplus.com
http://en.cppreference.com(英文版) http://zh.cppreference.com(中文版)
1.1库函数的分类
IO函数
字符串操作函数
字符操作函数
内存操作函数
时间/日期函数
数学函数
其他库函数
2.自定义函数
自定义函数。自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间
ret_type fun_name(para1, * )
{
statement;//函数功能语句项
}
//ret_type 返回类型
//fun_name 函数名
//para1 函数参数
上方为自定义函数的大概格式
2.1 自定义函数实例
2.1.1 比较两个值的大小
⏩代码示例
/
int getmax(int a , int b)
{
return a > b ? a : b;
}
int main()
{
int a = 30;
int b = 20;
int max = getmax(a, b);
printf("%d", max);
return 0;
}
2.1.2 交换两个变量的值
⏩代码示例
void Swap2(int* pa,int* pb)// 传 址 调用
{//在这里指针变量int* pa/pb已经存储了主函数中a的地址,所以修改*pa就等同于修改a,int* pb同理。
int z = 0;
z = *pa;
*pa = *pb;
*pb = z;
}
void Swap1(int x, int y)//传 值 调用
{ //在这里int x是自己创建了一个临时内存空间存储了 x 的值,在这里赋给 x 的值是存储在临时内存空间的,并没有对主函数中的a产生影响
//所以函数结束后,a和b的值并没有进行调换。
int z = 0;
z = x;
x = y;
y = z;
}
int main()
{
int a = 12;
int b = 11;
Swap1(a, b);
printf("S2a = %d\n", a);
printf("S2b = %d\n", b);
Swap2(&a, &b); //将a和b的地址穿到Swap1的函数中去。
printf("a = %d\n", a);
printf("b = %d\n", b);
return 0;
}
传值调用和传址调用的区别
1 传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
2 传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
3. 函数的代用
3.1实际参数
真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。
3.2形式参数
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了(类似局部变量,局部变量出了自己的作用域就自动销毁了)。因此形式参数只在函数中有效。
在实际工作应用中,函数的功能要尽量功能单一、独立,函数的功能不需要太复杂。
4. 函数的嵌套调用和链式访问
4.1函数的嵌套调用
函数和函数之间可以根据实际情况需求进行组合,也就是互相调用,但是C语言中函数内不能嵌套定义
//嵌套套用
void test3()
{
printf("%d\n", strlen("abc"));
}
void test2()
{
test3();
}
int main() {
test2();
return 0;
}
4.4 函数的链式访问
把一个函数的返回值作为另外一个函数的参数,意思就是直接在一个函数语句中直接调用函数的返回值
//函数的链式访问
//代码1
int len = strlen("abc");
printf("%d\n",len);
printf("%d\n", strlen("abc"));
//代码2
int main() {
char arr1[20] = { 0 };
char arr2[] = "abc";
printf("%s\n", strcpy(arr1, arr2));
return 0;
}
//代码3
printf("%d", printf("%d", printf("%d", 43)));
//以上输出的是4321,printf返回值:返回打印的字符数,发生错误则返回负值。
5. 函数的声明和定义
5.1 函数声明:
告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。只是声明了,但是并不是调用,所以并不影响主函数的运行,但是如果主函数中如果声明并调用了,但是却没有创建函数功能,程序会出现报错。
函数的声明一般出现在函数的使用之前,要满足先声明或使用。
函数的声明一般要放在头文件中。
5.1 函数定义
函数的定义指函数的具体实现,交代函数的功能实现。
//函数的定义
int add(x, y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
//函数的声明
int add(int, int);
printf("%d", add(a, b));
return 0;
}
6. 递归函数
6.1递归函数的概念
程序调用自身的编程技巧称为递归,递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需要少量的程序就可描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。
递归的两个条件
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后,越来越接近这个限制条件。
6.2 简单递归和图文解析
6.3.1 打印一个整数用递归实现
void print(unsigned int n)
{
if (n > 9)
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main() {
unsigned int num = 0;
scanf("%u", &num); //123 //要写成递归的形式。
print(num);
return 0;
}
6.3.2 递归实现strlen函数
//实现一个strlen功能的函数。
int arr_strlen(char* x)
{
if (*x != '\0')
return 1 + arr_strlen( x + 1);//++x虽然可以使用,但是不推荐
else
return 0;
}
int main() {
char arr[] = "bit";
// B I T \0
//模拟实现一个strlen函数
printf("%d\n", arr_strlen(arr));
return 0;
}
递归代码注意项
不能死递归,都有跳出条件,每次递归逼近跳出条件。
递归层次不能太深。
StackOverflow.com, 程序员的知乎
6.3.3 递归求n的继承
long int fac(int n)
{ //阶乘公式
if (n <= 1)
return 1;
else
return n * fac(n - 1);
}
int main()
{
long int n = 0;
scanf("%d", &n);
long int ret = fac(n);
printf("%d", ret);
return 0;
}
6.3.4 递归 求n的斐波那契数
//直接套用公式,虽然简单,但是进行了大量的重复计算,但是程序效率太低
int Fib(int n)
{
if (n <= 2)
{
return 1;
}
else
{
return Fib(n - 1) + Fib(n - 2);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d", ret);
return 0;
}
//不能用递归,只能用迭代,使用while循环避免了重复的无用计算。
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n>2)
{
c = a + b;
a = b;
b = c;
n--; //如果不用n--,会死循环。
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d", ret);
return 0;
}
C 基础 什么是数组📕
1. 一维数组的创建和初始化
数组是一组相同类型元素的集合。
变长数组:数组的大小是变量,VS2019不支持。
1.1 一维数组的创建
type_t arr_name [const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小
int arr[5] = {1,2,3,4,5};//整形数组
char arr[] = "bitbit"; //字符数组
char arr[n] ; //变常数组
变长数组
//这是一个变长数组
int n;
scanf ("%d", &n);
int array[n];
//虽然n确实是需要运行时动态确定的变量,但是在C99中,以这种变量作为数组大小的形式已经是允许的了。这样的数组就被称之为“变长数组”。
//注意:变长数组是指用整型变量或表达式声明或定义的数组,而不是说数组的长度会随时变化,变长数组在其生存期内的长度同样是固定的。
1.2 一维数组的初始化
所谓初始化,就是给创建数组的同时给数组内容设置一些合理的初始值(比如:0)
//整数数组的初始化
int arr1[3] = {1,2,3};//完全初始化
int arr2[10] = {1,2,3,4};//不完全初始化
int arr3[5] = {1,2,3,4,5,};
//int arr3[] = {1,2,3,4,5,}; 等同于上方的数组
//字符数组的初始化
char arr1[5] = {'a','b','c'}; // a b c 0 0
char arr2[] = {'a','b','c'}; // a b c
char ch3[5] = "abc"; // a b c 0 0
char ch4[] = "abc"; // a b c 0
char ch5[] = "{'a','b','c'}; // a b c 0
char ch6[] = "abc"; // a b c
1.3 一维数组的使用 下表引用操作符[ ]
对于数组的使用我们之前介绍了一个操作符: [ ] ,下标引用操作符。它其实就数组访问的操作符。
1. 数组是使用下标来访问的,下标是从0开始。
2. 数组的大小可以通过计算得到。
//代码1
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", sz);
printf("--------------------\n");
for ( int i = 0; i < sz; i++)
{
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
//代码2
//sizeof和strlen的区别
int main()
{
char arr[] = "halo bit";
printf("%d\n", sizeof(arr));
printf("%d", strlen(arr));
//strlen - 函数 求字符串长度的,找\0之前出现的字符个数
//sizeof - 操作符 - 计算类型/变量所占用内存的大小,单位是字节 转义字符也会占用内存空间
}
仔细观察输出的结果,我们知道,随着数组下标的增长,(int arr中的每个元素数组是以16进制存储的,每个元素占用4个字节)元素的地址,也在有规律的递增
由此可以得出结论:数组在内存中是连续存放的。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr; //数组名是数组首元素的地址
int i = 0;
for ( i = 0; i < 10; i++)
{
printf("%d ", *p);
p++;
}
return 0;
}
2. 二维数组的创建和初始化
二维数组可以简单的理解为,二维中的每一个 元素 是一个一维数组。
//数组创建
int arr[3][4]; //3行 4列 ,
//这是一个int类型的二维数组,这个二维数组中有 3个 一维数组。每个以为数组中有4个int类型的元素。
char arr[3][5];
double arr[2][4];
2.1 二维数组的初始化
int arr[2][5] = {1,2,3,4,5,6,7,8,9,10} //完全初始化
int arr[2][5] = {1,2,3,4,5,10}//不完全初始化,为完成的位置全部 补 0
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略
2.2 二维数组的使用和存储
二维数组的使用也是通过 [ ] 下标的方式。
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][%d] = %d", i, j, arr[i][j]);
printf(" ");
printf("址[%d][%d] = %p\n", i, j, &arr[i][j]);
}
printf("\n");
}
return 0;
}
通过以上代码和结果我们可以分析到,其实二维数组在内存中也是连续存储的。
3. 数组作为函数参数
冒泡排序
void bobo_sort(int arr[],int sz)//形参arr本质是指针
{
// 确定冒泡趟数
int i = 0;
for ( i = 0; i < sz - 1; i++)
{
//一趟冒泡排序的过程
int j = 0;
for ( j = 0; j < sz -1 - i; j++)
{
if(arr[j] > arr[j + 1])
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1 };
//排序为升序 - 冒泡排序
int sz = sizeof(arr) / sizeof(arr[0]);
bobo_sort(arr,sz); //数组传参的时候,其实是传的数组首元素地址
return 0;
}
4. 数组地址表达的区别
数组地址在不同的组合上有不同的区别,实际使用中代表的意思天差地别。
int main()
{
int arr[10] = { 0 };
printf("&arr");
printf(" %p\n",洒水多 &arr);
//printf(" ------- ");
printf("&arr +1");
printf(" %p\n\n", &arr + 1);
printf("arr");
printf(" %p\n", arr);
//printf(" ------- ");
printf("arr +1");
printf(" %p\n\n", arr + 1);
printf("&arr[0]");
printf(" %p\n", &arr[0]);
//printf(" ------- ");
printf("&arr[0] +1");
printf(" %p\n\n", &arr[0] + 1);
return 0;
}
&arr 008FFA30
&arr +1 008FFA58
arr 008FFA30
arr +1 008FFA34
&arr[0] 008FFA30
&arr[0] +1 008FFA34
&arr +1 在整个数组整体上增加一个数组本身长度的单位。
arr +1与&arr[0] +1 增加一个数组内单个单位长度。
C 基础 操作符讲解📕
0. 操作符分为
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
1. 算数操作符
+ 加
- 减
* 乘
/ 除
% 取模
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
//代码1 代码输出为0
int main()
{
int a = 3;
int b = a % 8;//3 % 8 等于3 ,然后把 3 这个值赋给了b ,b就是3
printf("%d\n",b);
return 0;
}
2. 移位操作符
移位操作符分为 左移操作符 和 右移操作符,移位操作符是移动的这个值相对应的二进制的位置。
2.1左移操作符
左移位规则:左移位规则:左边抛弃、右边补0。
int main()
{
int a = 2;
int b = a << 1;
printf("a = %d\n", a);// 2
printf("b = %d\n", b);// 4
return 0;
}
//2的二进制位0010
//左移位规则:左边抛弃、右边补0。
//得到 0100 0100二进制位对应的值是 4
//对于移位运算符,不要移动负数位,这个是标准未定义的。
int num = 10;
num>>-1;//error
2.2右移操作符
算数右移
左边用0填充,右边丢弃
逻辑右移
左边用原该值的符号位填充,右边丢弃 算数移位和逻辑移位的区别
有符号:用最高为来表示符号位 , 1表示负,0表示正无符号:全部用来表示实际数。两者的取值范围不同有符号整型为 -32768 到 32767 无符号整型 为 0 到 65535
3. 位操作符
位操作符的意思就是对这个值的 二进制位 进行位运算,两个操作数必须都是整数。
& //按位与 对应的二进制代码位进行 与 计算 对应数必须都为1 才为1
| //按位或 对应的二进制代码位进行 或 计算 对应数一个为1 就为1
^ //按位异或 对应的二进制代码位异或 两个相应位相同结果为0,否则为1
//按位 与
0000000 0000000 0000000 0000111
0000000 0000000 0000000 0000010
0000000 0000000 0000000 0000010
//按位 或
0000000 0000000 0000000 0000111
0000000 0000000 0000000 0000010
0000000 0000000 0000000 0000111
//按位 异或
0000000 0000000 0000000 0000111
0000000 0000000 0000000 0000010
0000000 0000000 0000000 0000101
//不创建第3个变量,交换啊a b变量的值
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d b = %d\n", a, b);
return 0;
}
//将a的二进制序列的第五位数 改成1
int main()
{
int a = 13;
printf("%d\n", a);
a = a | (1 << 4);
printf("%d\n", a);
//00000000 00000000 00000000 00001101 a = 13
//00000000 00000000 00000000 00010000 1 << 4
//00000000 00000000 00000000 00011101 a | (1 << 4);
return 0;
}
4. 赋值操作符
= 赋值
+= //a = a + b a += b
-= //a = a - b a -= b
*=
/=
%=
>>=
<<=
&=
|=
^=
5. 单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位) sizeof是一个操作符,不是函数。
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
5.1 关于sizeof
sizeof括号中的表达式不参与运算 sizeof是一个操作符,不是函数。
//sizeof 代码1
int main()
{
int b = 5;
int a = 10;
printf("%d\n", sizeof(b = a + 10));//sizeof中放表达式是不参与运算的
printf("%d\n", b);//b的值不会改变,还是5
return 0 ;
}
5.2 逻辑反 取反 自增 增减
逻辑反操作符 < ! >
// 逻辑反操作符
int main()
{
int a = 5;
//a不是0,条件为真,但是进行了取反操作,真 取反为 假
if(!a)
{
printf("%d\n",5);
}
//a不是0,所以条件为真,执行if语句
if(a)
{
printf("%d\n",10);
}
return 0 ;
}
二进制取反操作符 < ! >
//代码1
int main()
{
int a = -1;
printf("%d\n", a);
//整数在内存中存的是补码,整数的二进制序列中 第一个二进制数是符号码, 1代码负数,0代表整数
// 10000000 00000000 00000000 00000001 原码
// 11111111 11111111 11111111 11111110 反码
// 11111111 11111111 11111111 11111111 补码
a = ~a;
// 取反后 0000000 0000000 0000000 0000000
printf("%d\n", a);
return 0;
}
代码2
int main()
{
int a = 13;
a = a | (1 << 4); //把a的二进制序中第5位改成1
printf("%d\n", a);
a = a & ~(1 << 4); //把a的二进制序中第5位改成0
printf("%d\n", a);
return 0;
}
自增和自减操作符 <a++ a-->
int main()
{
int a = 10;
int b = a++;
printf("%d\m", a); //9
printf("%d\m", b); //10
int a = 12;
int b = a--;
printf("%d\m", a); //12
printf("%d\m", b); //11
int a = 12;
int b = --a;
printf("%d\m", a); //11
printf("%d\m", b); //11
int a = 12;
int b = ++a;
printf("%d\m", a); //13
printf("%d\m", b); //13
return 0;
}
6. 关系操作符
关系操作符跟数学中的理解差不多
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等
//测试相等和不相等
int main()
{
int a = 1;
int b = 2;
if (a == b)
{
printf("111\n");
}
if (a != b)
{
printf("111\n");
}
return 0;
}
7. 逻辑操作符
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;
//i = a++||++b||d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
//程序输出的结果是什么?
逻辑与 逻辑计算出两个真值或者计算出一个值为假以后,后面的表达式全部不再计算
逻辑或 逻辑计算出一个值为 真 以后,后面表达式全部不再计算
8. 三目操作符
三目操作符的功能可以取代简单的if判断
exp1 ? exp2 : exp3
//当 exp1为真时,执行exp2,否则执行exp3
//代码1
int a = 0;
int b = 0;
if (a > 5)
b = 1;
else
b = -1;
//下方的代码 计算的结果与上方的if语句一样。
b =( a > 5? 1:-1);
9. 逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少? //13
//代码2
if (a =b + 1, c=a / 2, d > 0)
//代码3
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.1 [ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
10.2 ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}
10.3 结构体成员的访问
. 结构体.成员名
-> 结构体指针->成员名
struct Book
{
//结构体的成员(变量)
char name[20];
char id[20];
int price;
};
int main()
{
int mum = 10;
struct Book b = {"C Prime","C2021b8b7",55};
struct Book* pb = &b;
//结构体指针 ->
printf("书名:%s\n", pb->name);
printf("书id:%s\n", pb->id);
printf("书price:%d\n", pb->price);
//printf("书名:%s\n", (*pb).name);
//printf("书ID:%s\n", (*pb).id);
//printf("书价:%s\n", (*pb).price);
. // 结构体.成员名
//printf("书名:%s\n", b.name);
//printf("书ID:%s\n", b.id);
//printf("书价:%d\n", b.price);
return 0;
}
11 表达式的求值
C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
意思就是,在整形运算中,如果变量达不到 int 整形类型的大小,就会强制提升为 int 整形类型。
11.1整形提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度,一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
//实例1
char a,b,c;
...
a = b + c;
//a b c都是char类型,各占用一个字节的空间,但是在CPU计算中,CPU难以实现两个字节的相加,
//所以,CPU在执行时会把 b c转换成int类型计算,最后把计算结果赋给a。
//b和c的值被提升为普通整型,然后再执行加法运算。加法运算完成之后
//结果将被截断,然后再存储于a中。
11.2如何进行整形提升的呢
整形提升是按照变量的数据类型的符号位来提升的
//代码1
int main()
{
char a = 3;
//00000000 00000000 00000000 00000011
//00000011 - a
char b = 127;
//00000000 00000000 00000000 01111111
//01111111 - b
char c = a + b;
//00000000 00000000 00000000 00000011
//00000000 00000000 00000000 01111111
//00000000 00000000 00000000 10000010
printf("%d\n", c);
//10000010 -c
//11111111 11111111 11111111 10000010 c- 补码 //有符号数打印的是原码
//11111111 11111111 11111111 10000001 c- 反码
//10000000 00000000 00000000 01111110 c- 原码
//c = -126
//发现a和b都是char类型的,都没有达到一个int的大小
//这里就会发生整形提升
return 0;
}
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0
//实例1
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if(a==0xb6)
printf("a");
if(b==0xb600)
printf("b");
if(c==0xb6000000)
printf("c");
return 0;
}
a,b要进行整形提升,但是c不需要整形提升
a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表
达式 c==0xb6000000 的结果是真.
所程序输出的结果是: C
//实例2
int main()
{
char c = 1;
printf("%u\n", sizeof(c)); //1
printf("%u\n", sizeof(+c));//4
printf("%u\n", sizeof(-c));//4
printf("%u\n", sizeof(!c));//4
return 0;
}
c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节.
12 算数转换
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
警告:但是算术转换要合理,要不然会有一些潜在的问题。
long double
double
float
unsigned long int
long int
unsigned int
int //从下至上转换,低精度向高精度转换。
13 操作符的属性
复杂表达式的求值有三个影响的因素。
操作符的优先级
操作符的结合性
是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
//表达式的求值部分由操作符的优先级决定。
//表达式1
a*b + c*d + e*f
//注释:代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不
//能决定第三个*比第一个+早执行。
//所以表达式的计算机顺序就可能是:
a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f
或者:
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f
//问题代码1
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);//输出多少?
return 0;
}
这个代码有没有实际的问题?
有问题!
虽然在大多数的编译器上求得结果都是相同的。
但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,
再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。
//问题代码2
//代码5
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
//VS计算顺序是先把全部++i计算一次,所有的i都赋值成4,3 * 4 = 12 所以输出为12
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
//尝试在linux 环境gcc编译器,VS2013环境下都执行,看结果。
//VS2019 12
//GCC 10
上述代码在实际日常中基本不会碰到,也很少会有人这么写。