抽丝剥茧C语言(初阶 下)

2024-02-23 07:20
文章标签 语言 初阶 抽丝剥茧

本文主要是介绍抽丝剥茧C语言(初阶 下),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

C语言初阶 下

  • 导语
  • 操作符
    • 算术操作符
    • 位移操作符
    • 位操作符
    • 赋值操作符
    • 单目操作符
    • 关系操作符
    • 逻辑操作符
    • 条件操作符
    • 逗号表达式
    • 下标引用、函数调用和结构成员
  • 常见关键字
    • 关键字 typedef
    • 关键字static
  • define 定义常量和宏
  • 指针
    • 内存
    • 指针变量的大小
  • 结构体
  • 结论
  • 给家人们的留言!!!

导语

如果到现在你也感觉有很多地方听不懂,没关系,我的这三篇博客只是让我们一起了解C语言大概是什么样子,对它有一个最初的了解就可以了,以后会有更详细的博客。

操作符

操作符是很重要的东西,这里我们讲解一下简单的,我没讲的家人们先记住就可以了,因为涉及到一些其他知识,我们慢慢来。

算术操作符

+     -     *      /      %

这是我们目前最常见运算操作符,字面意思,加减乘除什么的。

#include <stdio.h>
int main()
{int a = 3;int b = 2;int c, d, e, f, g;c = a + b;//加法运算d = a - b;//减法运算e = a * b;//乘法运算f = a / b;//除法运算g = a % b;//这个是取模,也就是取余数printf("%d %d %d %d %d", c, d, e, f, g);return 0;
}

我们来看一下运算结果(结果我就不放图片了)

5 1 6 1 1

我们除了变量 f 其他结果看起来是正常的对吗,这个符号确实是除号,这里我要说明一下,并不是你输入法打出来一个➗才是除号,而是要取键盘上的键打出来的符号,C语言定义中,/ 这个符号就是除号,其他的符号也是一样的,虽然看起来和你手写不一样,但他的定义就是这个意思。

那么为什么结果不正确呢?是因为在C语言规定中,两个数据相除,如果想得到浮点数(也就是小数,之所以叫浮点数,是因为小数点可以移动,所以叫做浮点数)那么 / 两边必须有一个浮点数才行!

例如:

#include <stdio.h>
int main()
{float a = 3.0 / 2;//float b = 3 / 2.0;//printf("%f %f", a, b);//打印浮点类型的数据就要用%freturn 0;
}

我们这段代码的结果就是:

1.50000 1.500000

至于为什么后面有这么多零,大家先忽略,我们先不进行深入了解。
其实不仅仅是除法运算,其他运算也是一样的,只要两边有一个浮点数就可以算出来浮点数的结果。

位移操作符

>>     //右位移操作符
<<     //左位移操作符

位操作符

&     //按位与
^     //按位异或
|     //按位或

赋值操作符

=    +=    -=    *=    /=    &=   ^=   |=    >>=   <<=

这些就比较容易理解了,第一个是赋值,顾名思义,是把一个数值赋予给一个变量,如果被赋值是一个常量,那么编译器就会报错,因为常量不可以被修改的;第二个是加等,a+=1 等同于 a=a+1 ;第三个是减等,请参考第二个;第三个是乘等;第四个是除等。剩下的我就不说名字了,因为大家都已经知道了。
看代码

#include <stdio.h>
int main()
{int a = 2;printf("%d ", a);int b = 2;printf("%d ", b += 1);int c = 2;printf("%d ", c -= 2);int d = 2;printf("%d ", d *= 2);int e = 2;printf("%d ", e /= 2);return 0;
}

输出结果:

2 3 0 4 1

剩下的赋值操作符我就不说了,后面的博客会讲。

单目操作符

!           //逻辑反操作
-           //负值
+           //正值
&           //取地址
sizeof      //操作数的类型长度(以字节为单位)
~           //对一个数的二进制按位取反
--          //前置、后置--
++          //前置、后置++
*           //间接访问操作符(解引用操作符) 
(类型)      //强制类型转换

第一个是逻辑反操作,比如说,你的 if 语句的判断条件是非零的数字,但是你在前面加上了!就会让判断条件两极反转;加号减号我就不说了,正负值而已;sizeof这是个操作符,不是函数,我们之前和它见过面了;- - 和++分前后,在前面就是先运算,后使用,在后面就是先使用,后运算,运算 - - 是减 1,++是加1(这里就不进行展示,后面会了解);(类型)里面是什么整形,浮点型之类的,作用是强制转换一个已经定义的类型。

#include <stdio.h>
int main()
{int q = 0;if (!q == 0)printf("w\n");int e = 0;printf("%d\n", sizeof (int));printf("%d\n", sizeof e );int a = (int)'a';printf("%d\n", a);return 0;
}

输出结果:

4
4
97

我们的第一个if语句并没有进去,因为!的功劳。sizeof后面加括号打印 int 类型的大小是因为C语言语法规定,而下面的 e 就不用。最后打印的97是字符 a 的ASCII值。

关系操作符

>    //大于
>=   //大于等于
<    //小于
<=   //小于等于
!=   //用于测试“不相等”
==   //用于测试“相等”

这些大多数用来判断两边的值是什么关系。

逻辑操作符

&&     //逻辑与
||     //逻辑或

例如:

#include <stdio.h>
int main()
{int a, b;scanf("%d %d", &a, &b);if (a && b)printf("q\n");elseprintf("w\n");int z, x;scanf("%d %d", &z, &x);if (z || x)printf("p\n");elseprintf("o\n");return 0;
}

实验1:
输入1 2
结果 q
输入1 0(或者是1 2)
结果 p
实验2:
输入1 0(或者是0 0)
结果 w
输入0 0
结果 o

以上实验说明什么?
逻辑与这个符号,两边需要都为真(非零)才能通过,一个真一个假(为零)或者是都为假则不能通过。
逻辑或这个符号,两边只要有一个为真就能通过,两个真也可以,两个假就不行了。

条件操作符

exp1 ? exp2 : exp3

这里的exp是一条语句,作用是如果exp1成立,那么就选择exp2;反之选择exp3.
例如:

#include <stdio.h>
int main()
{int a = 3;int b = 2;int c = (a < b ? a : b);printf("%d", c);return 0;
}

因为条件操作符需要返回一个值,这里返回一个整形,所以用整形来接收。
输出结果是:

2

逗号表达式

exp1, exp2, exp3, …expN

这个是取最后一个表达式的值。

#include <stdio.h>
int main()
{int a = 1;int b = 2;int c = 3;int d = 4;int e = 5;int m, n, v;int z = (m = a + b, n = c + m, v = e + n);printf("%d", z);return 0;
}

输出结果如下:

11

下标引用、函数调用和结构成员

[]     ()      .      ->

第一个我们之前说过了对吧,除了数组的初始化以为,剩下使用就表示数组的下标了,用法是数组名后面加一个 [ ] 里面是变量或者是常量都可以;圆括号是不仅仅是强制类型转换也是函数的调用,也就是我们之前说的参数,也就是传参,你把你想传过去的值传过去就好了(具体的后面会说),用法是前面加上函数名。剩下的以后会了解的。
至于花括号 { } 这个是是什么?当然是作用域了,你们慢慢体会。
这两个我就不举例子了。

常见关键字

C语言提供了丰富的关键字,这些关键字都是语言本身预先设定好的,用户自己是不能创造关键字的。

auto :声明自动变量

break:跳出当前循环

case:开关语句分支

char :声明字符型变量或函数返回值类型

const :声明只读变量

continue:结束当前循环,开始下一轮循环

default:开关语句中的“默认”分支

do :循环语句的循环体

double :声明双精度浮点型变量或函数返回值类型

else :条件语句否定分支(与 if 连用)

enum :声明枚举类型

extern:声明变量或函数是在其它文件或本文件的其他位置定义

float:声明浮点型变量或函数返回值类型

for:一种循环语句

goto:无条件跳转语句

if:条件语句

int: 声明整型变量或函数

long :声明长整型变量或函数返回值类型

register:声明寄存器变量

return :子程序返回语句(可以带参数,也可不带参数)

short :声明短整型变量或函数

signed:声明有符号类型变量或函数

sizeof:计算数据类型或变量长度(即所占字节数)

static :声明静态变量

struct:声明结构体类型

switch :用于开关语句

typedef:用以给数据类型取别名

unsigned:声明无符号类型变量或函数

union:声明共用体类型

void :声明函数无返回值或无参数,声明无类型指针

volatile:说明变量在程序执行中可被隐含地改变

while :循环语句的循环条件

关键字 typedef

typedef 顾名思义是类型定义,这里应该理解为类型重命名。
比如:

//将unsigned int 重命名为uint_32, 所以uint_32也是一个类型名
typedef unsigned int uint_32;
int main()
{//观察num1和num2,这两个变量的类型是一样的unsigned int num1 = 0;uint_32 num2 = 0;return 0; 
}

这个关键字的实际作用就是你觉得某个关键字太长或者是陌生你不想一遍一遍的去打,那就自己定义一个简单又熟悉的。

关键字static

**在C语言中:
static是用来修饰变量和函数的

  1. 修饰局部变量-称为静态局部变量
  2. 修饰全局变量-称为静态全局变量
  3. 修饰函数-称为静态函数**

在这里插入图片描述

看到没,我们这次多了一个源文件,这是为了方便演示。
在这里插入图片描述
如图,在test.c的源文件已经有main函数了,所以add.c的源文件就不用main函数了,因为它们是一个工程。
这里我们的编译器报错了,为什么呢?
因为你在add.c这个文件里声明了a但是test.c文件不知道,你需要去声明一下,利用关键字extern如图
在这里插入图片描述
这里就成功的打印出来了我们变量a 的值,你只需要声明它是什么类型,变量名称就可以了,这也说明,变量是有外部链接属性的,但我们的主题是关键字static,那么我们来看一下static修饰变量a会怎么样。(无论什么修饰什么记得需要中间加一个空格)
在这里插入图片描述
这里我们又报错了,着说明我们关键字static变量a的外部链接属性变成了内部的链接属性,其实我们本质是改变了变量a的储存方式给改变了,我们储存的大概方式一般是这个样子的。

这里科普一下:我们的电脑里有硬盘,比如说我目前的电脑,512G的硬盘,16G的内存,内存是什么?是传给你处理器数据的储存空间,但是内存这个东西造价昂贵,成本高,技术难突破,而处理器不一样,所以处理器的处理速度越来越快,而内存跟不上,处理器也不能之在那里等着对吧,所以,内存之上还有一块区域叫做高速缓存,在高速缓存之上还有一个叫做寄存器,越往上读写速度越快,但是成本和造价也越贵,空间也越小,高速缓存的单位是mb,寄存器的单位就是kb了。
处理器会先从寄存器里面拿,如果发现没有就去高速缓存里找,高速缓存没有就去内存里拿,这样就大大的提高了效率。

C语言的一大特点就是与内存强相关,C语言拥有三种不同的内存池。
1.静态区(static):全局变量,静态变量储存(生命周期是整个工程)
2.栈区(stack):局部变量存储(自动,连续的内存)
3.堆区(heap):动态存储(非常大的内存池,非连续分配)
在这里插入图片描述
因为static修饰的局部变量是存储在静态区的,static修饰全局变量时,实际改变的是变量的存储位置。 局部变量放在栈区的,被static修饰后放在了静态区。从而导致除了作用域依然存在,生命周期并没有结束。
比如说我再写一个代码:

void add()//我们不需要add函数返回,所以返回类型就是void
{int a = 5;//每一次调用都要重新创建临时变量a,初始化的值是5a++;printf("%d ", a);//这里打印add函数里面临时变量a的值
}#include <stdio.h>
int main()
{int i = 0;while (i < 10)//这里循环里面内容十次{add();//这里是调用add函数i++;}return 0;
}

我们的输出结果是:

6 6 6 6 6 6 6 6 6 6

这次我们用static来修饰一下局部变量a

void add()//我们不需要add函数返回,所以返回类型就是void
{static int a = 5;//这次我们用static修饰了局部变量aa++;printf("%d ", a);//这里打印add函数里面临时变量a的值
}#include <stdio.h>
int main()
{int i = 0;while (i < 10)//这里循环里面内容十次{add();//这里是调用add函数i++;}return 0;
}

输出结果如下:

6 7 8 9 10 11 12 13 14 15

这也能说明static修饰的局部变量a的储存位置被改变了,它的生命周期会一直到工程结束为止。
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在编译的时候会出现连接性错误。
其他关键字以后用到我会讲解。

define 定义常量和宏

这里先说一下,define是预处理指令,也就是说在编译的初阶段时,对于某些东西进行文本上的替换。
例如:

//define定义标识符常量
#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);printf("%d\n",MAX);return 0; 
}

运行结果如下:

sum = 5
sum = 50
1000

对于ADD来说,x和y被替换成了2和3假如说没有那个括号的话,我们第二个结果就不一样了。对于MAX来说,它以后就代表1000这个常量了。
看这里!靓仔!标识符.

指针

它来了,它来了!
嗯,我们C语言最灵魂的地方——指针来了。

内存

内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。
所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址
就相当于某人在广东省深圳市某区某街某小区某号楼某单元某层楼几号房一样!也就是说指向了你的这个位置。
所以说,地址就是指针。
在这里插入图片描述
变量是创建内存中的(在内存中分配空间的),每个内存单元都有地址,所以变量也是有地址的。
我们这里来说一下单位转换:
8bit=1byte(字节)
1kb=1024byte
1mb=1024kb
1gb=1024mb
1tb=1024gb

再说一下我们最常用的进制,二进制八进制十六进制,至于怎么互相转换的,小白看一下视频吧。
链接在这里进制转换
我们来看,一个字节等于8个bit位,一个bit位里面只能储存一个数字,因为我们计算机储存的本质正负电信号,也就是二进制0 1
至于后面为什么是0x什么什么的,这是因为0x表示十六进制

00000000000000000000000000000001
这个结果就等于1
二进制转换为十六进制
0x00000001
上面的视频最好一定要看哦。

二进制储存没有十六进制储存简洁明了,所以地址编号用十六进制表示!
我们了解了这么多,来取出一个地址打印看看!

#include <stdio.h>
int main()
{int num = 10;&num;//前面加一个&符号,这里是取出num的地址,不能是常量//注:这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)printf("%p\n", &num);//打印地址,%p是以地址的形式打印return 0;
}

打印结果(32位系统)

0012FF47
打印的结果每次都不一样,这是因为程序在运行的时候创建局部变量num当整个程序运行结束之后这个局部变量的生命周期也走到尽头了,每次创建变量都需要在栈区里随机的一个位置创建,所以每次的结果都不一样。

在这里插入图片描述

看到了吗,取出来的只是首个字节的地址,因为能通过首个地址找到后面三个字节的地址。

常量可以储存到变量里面,那地址如何存储?这需要定义指针变量。

int num = 10;
int *p;//p为一个整形指针变量,如果int换成char就是字符类型,也就是说前面这个地方是决定指针类型的地方
p = &num;//将变量num储存到p这个指针里面

当 * 与一个变量结合时,说明这个变量是指针变量。
那么指针该如何使用呢?

#include <stdio.h>
int main()
{int num = 10;int *p = &num;*p = 20;//*这个操作符叫做解引用操作符,不仅仅是初始化可以用到,也是释放储存到指针变量的钥匙return 0;
}

解引用这个操作符你不要把他和初始化弄混, C语言定义指针的初始化就是这个样子,初始化前面是有类型定义的,而解引用并没有。
解引用是咋回事呢,指针的储存就相当于你把地址num这个东西放进了包裹p里,你想打开这个包裹,就需要解引用这个操作才能打开这个包裹。
然后通过num地址找到了里面的10这个元素。
至于为什么是指针变量,就拿p来说,它可以指向num的地址,也可以指向其他地址。

int a=30;
p = &a;//这里不再储存变量num的地址而是变量a的地址

只要是数据储存在内存里就会有地址
在这里插入图片描述

就算是指针变量也一样。(这里先不套娃了,后期再说)
以整形指针举例,可以推广到其他类型,如:

#include <stdio.h>
int main()
{char ch = 'w';char* pc = &ch;*pc = 'q';printf("%c\n", ch);return 0; 
}

输出结果为

q

指针变量的大小

//指针变量的大小取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即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
4
4
4

为什么呢?不是说char类型是两个字节,short类型是两个字节吗?
其实这是指针的大小,我们上面说过了,所以不要在意指针变量前面的是什么类型,这个以后会说用处的,不要急。

结构体

结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如描述学生,学生包含: 名字+年龄+性别+学号 这几项信息。
这里只能使用结构体来描述了。

struct Stu//前面是定义结构体的声明关键字,后面是自定义标识符
{char name[20];//名字int age;      //年龄char sex[5];  //性别char id[15]//学号
};

这就是就结构体了。下面我们来看看结构体的初始化和使用方法:

//打印结构体信息
#include <stdio.h>
struct Stu//前面是定义结构体的声明关键字,后面是自定义标识符
{char name[20];//名字int age;      //年龄char sex[5];  //性别char id[15]; //学号
};
int main()
{struct Stu s = { "张三", 20, "男", "20180101" };//.为结构成员访问操作符,利用这个操作符访问s这个学生的个人信息printf("name = %s age = %d sex = %s id = %s\n", s.name, s.age, s.sex, s.id);//->操作符,相当于指针变量ps解引用之后访问s这个学生的信息一样struct Stu* ps = &s;printf("name = %s age = %d sex = %s id = %s\n", ps->name, ps->age, ps->sex, ps -> id);
}

输出结果如下:
在这里插入图片描述
结构体以后会详细讲的,先了解这么多吧。

结论

操作符主要是介绍了一些简单的,注意平时积累,慢慢就会了,以后遇到没讲的我会详细讲解的。
用户自己是不能创造关键字
关键字static
static修饰局部变量改变了变量的生命周期让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。
一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。
一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。
预处理指令是在编译初阶更改文本!
注意自定义标识符的规则!
指针大小在32位平台是4个字节,64位平台是8个字节。
结构体是像数据类型的那种东西,我的理解是自定义的数据类型。

给家人们的留言!!!

家人们,抽丝剥茧C语言的初阶——初识C语言,到此就完结了, 目前我们已经了解了C语言大概是什么样子的,对于以后学习C语言更加方便。
我并不是C语言只讲了这么些,而是让大家熟悉下C语言,不过很重要!!!
请路过的同志们点点赞,互关一波!!!

这篇关于抽丝剥茧C语言(初阶 下)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/737918

相关文章

使用Go语言开发一个命令行文件管理工具

《使用Go语言开发一个命令行文件管理工具》这篇文章主要为大家详细介绍了如何使用Go语言开发一款命令行文件管理工具,支持批量重命名,删除,创建,移动文件,需要的小伙伴可以了解下... 目录一、工具功能一览二、核心代码解析1. 主程序结构2. 批量重命名3. 批量删除4. 创建文件/目录5. 批量移动三、如何安

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允

Android kotlin语言实现删除文件的解决方案

《Androidkotlin语言实现删除文件的解决方案》:本文主要介绍Androidkotlin语言实现删除文件的解决方案,在项目开发过程中,尤其是需要跨平台协作的项目,那么删除用户指定的文件的... 目录一、前言二、适用环境三、模板内容1.权限申请2.Activity中的模板一、前言在项目开发过程中,尤

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3

Go语言实现将中文转化为拼音功能

《Go语言实现将中文转化为拼音功能》这篇文章主要为大家详细介绍了Go语言中如何实现将中文转化为拼音功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 有这么一个需求:新用户入职 创建一系列账号比较麻烦,打算通过接口传入姓名进行初始化。想把姓名转化成拼音。因为有些账号即需要中文也需要英