系列介绍
本系列博客是博主自己的C语言学习笔记,分享出来即是为了整理学过的知识也希望帮助每一位零基础小白上手C语言。系列更新速度即为博主学习速度,如有错误疏漏,请务必及时指出!博主也会不定期的重新编辑所写。虽学无止境,望精益求精。
系列目录
【C语言从青铜到王者】第零篇·与C语言来个约会
【C语言从青铜到王者】第一篇·详解分支与循环
【C语言从青铜到王者】第二篇·详解函数
【C语言从青铜到王者】第三篇·详解数组
本篇前言
如果说编程是一场MOBA游戏,程序员是召唤师,那编程语言就是代替我们“参战”的英雄。C语言就是这样一个位于T0级别的热门英雄,常年位居TIOBE前三甲,是编程界当之无愧的“C位”。这篇文章,让我们先来初步认识一下它的每一个“技能”到底是什么,在今后的文章里再对这些技能的使用技巧做进一步的讲解。
博主的编程环境是Visual Studio 2019
被动技能
被动技能学习阶段虽然不难,但是是我们今后学习主动技能必不可少的基础,请各位稳扎稳打,稍安勿躁
1.注释
什么是注释
注释就是写在程序里但是不会执行的语句
为什么要写注释
作用一:注释掉暂时不需要的代码,以后需要再反注释回来
作用二:理解别人的程序时添加笔记,方便我们整理思路
作用三:为大型算法添加注解,方便自己和后来的读者能更容易的看懂
良好的注释习惯是我们成为优秀IT人必须具备的素养
注释的写法
下图是C++的注释风格
//
//I love C
//You love C
// We all love C
下图是C语言的注释风格
/* */
/*I love C
You love C
We all love C
*/
2.C语言程序基本框架
看下面这段程序和它的运行结果
#include<stdio.h>
int main()
{
printf("I love C\n");
return 0;
}
也就是说,我们敲了一段代码,然后程序在控制台给我们输出了“I love C”
也就是说我们一段程序实际上好像就只执行了这句话
printf("I love C\n");
再看这段程序和它的运行结果
#include<stdio.h>
int main()
{
printf("You love C\n");
return 0;
}
通过这样的“单一变量”的实验,我们大概可以知道了,除了“printf”开头的这句话以外的东西都是换汤不换药的
加上注释(运用一下刚学的注释)就是:
#include<stdio.h>
int main()
{
//在这里执行语句
return 0;
}
我们把上图除了注释以外的部分就叫做C语言的基本框架
我们来逐句讲解它们是什么意思
#include<stdio.h>
这一行的作用是引出“头文件”
#include表示的是“引出头文件”,后面<>中的就是头文件的名字,这些名字的后缀一般是.h
头文件是什么?为什么要引出头文件呢?问题留着往下看
int main()
这一行的作用是定义主函数
int是函数返回值的类型,这里是整型
main是函数名,表示这是主函数
()是形式参数,主函数由于默认形式参数为void,void的意思是“无类型”,所以()里是空的
什么是函数返回值呢?什么是形式参数呢?具体内容在本篇函数部分介绍,等不及的小伙伴可以直接走目录跳读
{
printf("I love C\n");
return 0;
}
{}一对大括号是一个整体,里面是主函数的内容
printf是一个函数,全称是print function,作用是把一些东西打印到屏幕上,用法是printf(“要打印的内容”)
但是这个函数我们自己并没有定义它是怎么运作的,它其实是我们“借来”的,像谁借的呢,就是像我们一开始定义的头文件stdio.h,头文件就是一个别人已经帮我们写好的函数库,stdio的全称是standard input output,表示一个涵盖输入输出类型函数的文件。我们引用它,计算机就可以调用别人已经写好的程序了
我们注意到I love C后面还有一个\n,它是转义字符,表示换行
return 0;表示这个函数的返回值是0
我们也注意到了在大括号内的每一行代码后面都有一个英文输入法的分号;它表示一行语句的结束,所以小伙伴们以后写语句千万不要忘记用“;”结束它哦
3.数据类型
我们写程序的目的其实是通过借助计算机强大的计算能力来解决生活中的问题,那么我们必须得学会向计算机描述这个问题
比如我的身高是1.75m,体重是65kg,我的国家是China
这些数据各有家门,必须分门别类,它们各自的类型就是数据类型
常见的数据类型
char
short
int
long
long long
float
double
逐个简单介绍一下:
名称 | 中文名 | 含义 | 例 |
---|---|---|---|
char | 字符数据类型 | 用来表示单个字符 | A a * # |
short、int、long、longlong | 短整型、整型、长整型、更长的整型 | 用来表示整数 | 1234 |
float、double | 单精度浮点数、双精度浮点数(浮点就是小数点的意思) | 用来表示小数 | 1.75 3.14 |
char | 字符数据类型 | 用来表示字符 | eg:ABCabc |
各种数据类型的大小
介绍操作符sizeof(),它的作用是显示数据类型的大小(长度)
我们运行一下下面的程序看一下结果(简约起见结果注释在行末)
#include<stdio.h>
int main()
{
printf("%d\n", sizeof(char));//1
printf("%d\n", sizeof(short));//2
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof(long));//4
printf("%d\n", sizeof(long long));//8
printf("%d\n", sizeof(float));//4
printf("%d\n", sizeof(double));//8
return 0;
}
这里的结果是数字,它的量纲是什么呢?答案就是“字节”,“字节”的英文名是byte
这里引入一下c语言中数据大小的单位
最小的单位是“比特位”,“比特”的英文名是bit
bit是一个二元量,在计算机语言中存放1或者0,表示计算机内部电路的“通”或者“断”,通过一连串的0/1我们就可以描述出一个整体的电路状态,这个电路状态就可以用来表示信息
我们可以把比特位想象成一个存放0或1的盒子,那么一个字节(byte)就是8个并排排放的盒子,可以同时存储8个0/1
以后我们说的数据的长度就是这一排存放0/1的盒子一共有多少个
大家看到上图有没有想到我们常见的8位二进制数,没错,实际上,一个byte就是可以存放一个8位的二进制数。一次类推,如果我们给一个数据分配了4字节的空间,那么就可以存放一个32位的二进制数,例如:
int a = 10;
我们定义了一个int类型的变量,变量名叫a,赋值为10,根据上面的知识我们知道int类型的变量长度是4字节
10 = 8 + 2 =1x23 + 0x22 + 1x21 + 0x20,那么,a在内存中的状态就是这样的:
上面的a是int类型,int类型的长度是四字节,也就是32个bit位,也就是说a这个变量有32个并排的盒子去装它的二进制序列。每个盒子中存放的1/0从左到右需要依次乘2的0次方、2的1次方、2的2次方…这是二进制数转换成十进制数的方法,是相当基础的知识,如果还有不懂的小伙伴可以自己做些功课哦
实际上,我们还有一系列的表示内存大小的单位
下面给出单位换算表
内存单位 | 中文名 | 换算 |
---|---|---|
bit | 比特位 | (用来存储0或1) |
byte (B) | 字节 | 1byte = 8 bit |
KB (K) | 千字节 | 1 KB = 1024 byte (1024 = 2^10) |
MB (M) | 兆字节 | 1 MB = 1024 KB |
GB (G) | 吉字节 | 1 GB = 1024 MB |
TB (T) | 太字节 | 1 TB = 1024 GB |
PB ( P) | 拍字节 | 1 PB = 1024 TB |
这里的kb、mb、gb等就是我们熟悉的U盘、网盘、硬盘、内存的容量单位,今天放到这里介绍,大家是不是对他们有更深的理解了呢
数据类型的作用
- 用于创建变量时表示新变量的类型
- 可以丰富的表达生活中的各种值
- 让内存的空间利用率更高(小数据用小类型,大数据用大类型)
4.变量与常量
什么是变量和常量
在程序中值可以继续变化的量叫做变量,值无法继续改变的量叫做常量
为什么要定义变量和常量的概念
因为生活中的数据就有变量和常量的区分。比如一个人的年龄,就是随时都在增长的一个变量,而自然对数e就是一个常量。程序中描述它们时当然也得分开描述
定义变量的方法
int age = 20;
float height = 1.74;
char ml = 'c';
定义变量时的写法是
数据类型 变量名 = 变量值 ;
也可以不给初值,但是我们在定义新变量的时候最好养成定义初值的习惯
给一个变量赋初值的这个行为还有一个帅气的名字——“初始化”
也就是说,我们应该在定义变量时就初始化它
变量的分类
- 局部变量
作用域在自己函数内部的变量 - 全局变量
作用域是全局的变量
由于还没有介绍作用域的概念,我们先感性的认识一下什么是局部变量和全局变量
看下面这段程序:
#include<stdio.h>
int a = 2019;
int main()
{
int b = 2020;
{
int c = 2021;
printf("%d\n", c);
}
printf("%d\n%d\n", a, b);
}
程序的结果是
我们来分析一下这段程序
a就是全局变量。可以看到a虽然在主函数外定义的但是可以在主函数里面使用,主函数的printf函数输出了a的值,也就是说a在全局都可以被使用。
b、c都是局部变量,可以发现b是在主函数的{}内被定义的,c是在主函数{}内部的{}内被定义的,那么b和c就只能在这个程序的局部被使用。
b和c都是局部变量,它们有区别吗?答案是有的
看下面的程序:
#include<stdio.h>
int a = 2019;
int main()
{
int b = 2020;
{
int c = 2021;
printf("%d\n", b);
}
//printf("%d\n",c);
}
结果是
我们发现b在printf(“%d\n”,b);的大括号外部被定义,但是仍可以使用
如果我们把注释掉的printf(“%d\n”,c)加上,会发现程序错误,检测不到c:
#include<stdio.h>
int a = 2019;
int main()
{
int b = 2020;
{
int c = 2021;
//printf("%d\n", b);
}
printf("%d\n", c);
}
上图结果
总结:局部变量的作用域是自己所在的大括号内部,包括内部的大括号
变量的作用域和生命周期
- 作用域
变量能在哪里使用哪里就是变量的作用域 - 生命周期
变量创建到销毁的时间段
作用域 | 生命周期 | |
---|---|---|
全局变量 | 整个工程 | 整个程序的生命周期 |
局部变量 | 变量所在的局部范围,一般是所属的大括号{}内部 | 进作用域生命周期开始,出作用域生命周期结束 |
- 补充知识:全局变量在相同解决方案不同源.c文件的使用
在同解决方案下另一源文件中使用全局变量需要用extern声明其为外部变量
下面的int d定义在同一个解决方案的一个c文件中,在另一个c文件中使用它时要写:
extern int d
常量的分类
C语言有四种常见的常量
- 字面常量
比如程序中出现的数字或者字符:123 1.75 12f
eg:
#include<stdio.h>
int main()
{
int a = 10;
printf("%d", a);
}
10就是字面常量
- const修饰的常变量
具有常属性(不能变值)的变量
为什么变量又会具有常属性呢?为什么变量要被分类为常量呢?看下面两个例子:
eg:
#include<stdio.h>
int main()
{
const int a = 10;
a = 20;
printf("%d", a);
}
从上图可知const定义的变量不可变值,不可变值,就是具有常属性
#include<stdio.h>
int main()
{
const int a = 10;
int b[a] = { 0 };
printf("%d", b[0]);
}
由于定义数组的大小必须是常量,从上图可知a的本质仍然是变量
这就解释了为什么const修饰的变量叫做常变量
-
#define 定义的标识符常量
直接永久赋值,相当于给字面常量取了个别名
这种常量的定义既可以在主函数外部也可以在内部定义
-
枚举常量
引入enum枚举关键字
通过例子看看它的用法
#include<stdio.h>
enum Sex
{
MALE,
FEMALE,
SECRET
};
int main()
{
enum Sex a = SECRET;
printf("%d", a);
return 0;
}
结果是2
我们解析一下:通过enum我们定义了一个“组”,名字是Sex,这个“组”实际上是一个常量组,组中的MALE,FEMALE,SECRET都是常量,它们的值分别是0,1,2,因为枚举常量未赋值时按序号赋默认值
在主函数中如果我们想要使用这样的常量,就先声明枚举组,就可以使用了:
enum 枚举组名称 变量名 = 枚举常量 ;
接下来看一下枚举常量的赋值问题,还是通过例子来看:
#include<stdio.h>
enum Sex
{
MALE=3,
FEMALE=5,
SECRET=123
};
int main()
{
enum Sex a = SECRET;
printf("%d", a);
return 0;
}
结果是123,因为SECRET被赋值为123
#include<stdio.h>
enum Sex
{
MALE=3,
FEMALE=5,
SECRET
};
int main()
{
enum Sex a = SECRET;
printf("%d", a);
return 0;
}
结果是6,因为SECRET上一个枚举常量FEMALE是5,所以按照序号,未赋值枚举常量SECRET是6
总结规律如下:
赋值枚举常量按所赋值定义
未赋值枚举常量按序号定义
5.字符、字符串、转义字符
字符
单个字母或符号
b就是字符
字符串
由双引号引起的一串字符
I love C\n就是字符串
转义字符
转义就是意义被转变了,转义字符就是不是原本含义的,有特殊作用的字符组合
还是这个程序,\n的意思不是“斜杠和n”,而是换行的意思,\n就是转义字符
下面给出常见转义字符
一个基本要求是认识常见的转义字符,会数出含有转义字符的字符串的长度
我们来数一数下面这串字符串的长度吧:
c:\test\328\test.c
补充知识:strlen函数
strlen也就是string length
用来计算字符串的长度
答案解析:
答案解析:
c : e s t 8 e s t . c共11个字符
两个\t是两个转义字符
一个\32是一个转义字符
一共14个字符
字符串的结束标志——字符终止符\0
\0是一个转义字符,表示字符串的终止,计算字符串长度的时候作为结束的标识,不算做字符串的内容,没有长度
看这段程序
总结:
" "内字符串默认存在\0作为结束标志
’ ’ 内字符必须手动添加\0作为结束标志,否则会产生乱码,比如上图的arr2
6.操作符
用来处理变量、常量、字符和它们之间关系的符号
算数操作符
- 加 +
- 减 -
- 乘 *
- 除 /
- 取余 %
(a%b=a除以b的余数)
移位操作符
- 左移操作符 <<
将对象的二进制位向左移动一位 - 右移操作符 >>
将对象的二进制位向右移动一位
简单介绍:
比如1的二进制位是00000000000000000000000000000001一共32个数字
在总数字个数不变的情况下把这段数字向左移动一位,缺位补0
那么
00000000000000000000000000000001
就变成了
00000000000000000000000000000010
也就是二进制的数字2
写成代码的形式就是
#include<stdio.h>
int main()
{
int a = 1;
int b = a << 1;
printf("%d", b);
return 0;
}
位操作符
- 与 & (同真为真 其余为假)
- 或 ^(同假为假 其余为真)
- 非 |(非假为真 非真为假)
赋值操作符与复合赋值符
- 赋值操作符 = (将右值赋给左边)
- 复合赋值符 += -= = /= &= ^= |= >>= <<=
(a x= b 等价于 a=a x b ,x是操作符)
单目操作符
- 逻辑反 !(!非0=0,!0=1)
- 负值 -(取相反数)
- 正值 +
- 取地址 & (见本篇指针部分)
- 以字节为单位计算类型长度 sizeof
见程序“打印数组的个数”
#include<stdio.h>
int main()
{
int arr[] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("%d", sz);
return 0;
}
- 对数二进制按位取反 ~
#include<stdio.h>
int main()
{
int a = 0;
printf("%d", ~a);
return 0;
}
输出的值是-1
为什么呢?
这里引入C语言中原码反码补码的概念
C语言中的二进制整数的第一位是符号位,0代表正数,1代表负数
- 正整数的原反补码相同
- 负整数的原码代表本身的值
设已知负整数的原码 则负整数的反码是原码符号位不动其他位按位取反(0变1,1变0)
补码是反码+1
接着看上面的程序,
a=0,也就是说a的原码是
00000000000000000000000000000000
那么~a就是
11111111111111111111111111111111
整数在内存中储存的是补码
也就是说打印出来的~a的补码是
11111111111111111111111111111111
那么~a的反码是(补码-1)
11111111111111111111111111111110
~a的原码是(反码除了符号位按位取反)
10000000000000000000000000000001
- 强制类型转换 (类型)
比如我们打出下面这句话
int a = 3.14;
输出的时候会报错,告诉我们数据类型不对的时候会丢失数据
那我们怎么才能强制让一个浮点数按照整形输出呢
int a = (int)3.14;
#include<stdio.h>
int main()
{
int a = (int) 3.14;
printf("%d", a);
return 0;
}
结果是3
- 前置++和后置++
++在前,先++,再参与本行运算
++在后,先参与本行运算,再++
#include<stdio.h>
int main()
{
int a = 0;
int b = ++a;
printf("%d\n", b);
int c = 0;
int d = c++;
printf("%d\n", d);
return 0;
}
++a——a先加一再参与b的赋值运算
c++——c先参与d的赋值运算再加一
上图结果是
1
0
n目操作符
双目操作符:操作两个对象的操作符
三目操作符:操作三个对象的操作符
…
关系操作符
测试是否大于 >
测试是否大于等于 >=
测试是否小于 <
测试是否小于等于 <=
测试是否不等于 !=
测试是否等于 ==
是结果为1,否结果为0
逻辑操作符
- 逻辑与 &&
“并且”的意思
同真(非0)为1,其余为0 - 逻辑或 ||
“或者”的意思
同假(0)为0,其余为1
条件操作符
- a ? b : c
a真为b,a假为c
逗号表达式
逗号隔开的一串表达式
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = (a = b - 1, b = c + 2, c = a - b);
printf("%d", d);
return 0;
}
整个逗号表达式表示的并列语句,从左向右进行计算
整个表达式的结果是最后一句计算结束的结果
上图结果为-4
7.常见关键字
关键字的特性
关键字是C语言自带的语法,不能自己创建关键字
在C语言中,关键字不能用作变量名
32个常用关键字
C语言中一共有32个常见关键字
给出32个关键字
auto break case char
const continue default do
double else enum extern
float for goto if
int long register return
short signed sizeof static
struct switch typedef union
unsigned void volatile while
介绍部分关键字
重点介绍static关键字,因为对于初学者我们会经常看到它
-
static
(静态的)static关键字可以用来修饰变量、常量和函数
- static修饰局部变量——局部变量出作用域后不会销毁
#include<stdio.h>
void Add()
{
static int a = 1;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
while (i < 10)
{
Add();
i++;
}
return 0;
}
第一次调用Add函数的时候,a=2
由于是static定义,a出作用域未被销毁,a仍然被定义且值为2
第二次调用Add函数的时候,由于a已经被定义,跳过定义语句,直接执行a++
a = a+1 = 2+1 = 3,以此类推
static修饰的局部变量,本质上是改变了变量的存储类型
把栈区的局部变量分配到了静态区
让局部变量的生命周期延长成了全局变量的生命周期
-
static修饰全局变量
使得全局变量只能在自己的源文件中使用,不再能通过extern声明引用跨源文件的全局变量(忘记了回看全局变量中的“全局变量在相同解决方案不同源.c文件的使用”) -
static修饰函数
static修饰函数使得函数只能在自己的源文件中被调用,不再能通过extern声明引用跨源文件的函数(与static修饰全局变量作用类似)
static修饰全局变量和函数,本质上是将它们的外连接属性变成了内连接属性,使得它们不再能在全局被引用或调用
- register
寄存器关键字
- 什么是寄存器?
简单理解,寄存器就是运行速度很快,空间很小的储存数据的东西
它比内存快的多,所以使用寄存器可以大大的提高数据调用速度
所以,被大量频繁使用的数据,就可以放在寄存器变量中,提升效率
但是现在的许多编译器已经能够自动识别这种数据并把它们放进寄存器,所以register关键字现实使用意义不大
- typedef
类型重定义
可以给数据类型重新起名字,用于简化代码
#include<stdio.h>
typedef unsigned int u_int;
int main()
{
u_int d = -100;
printf("%d", d);
return 0;
}
typedef 旧类型名 新类型名 ;
注意:
define 、include等不是关键字,是预处理指令,以后会介绍,特别是define和typedef不要弄混
主动技能
恭喜你已经习得C语言的各种被动技能,但是光有被动我们还只是一个任人宰割的小兵。接下来让我们开始学习C语言的主动技能吧,它们是C语言的重头戏,效果十分强劲,是我们以后征战沙场的得力助手。今后会继续就每一个主动技能做讲解,今天我们就先和它们打个照面,交个朋友吧!
1.选择语句
我们在生活中常常会面对各种各样的选择,有时候会陷入两难的境地
那么,如何向计算机描述这种选择的情况呢?接下来我们引入第一个主动技能——选择语句
语法
(由于是第零篇,仅介绍if-else语句)
if(条件)
{
条件为真的时候执行的语句;
}
else
{
条件为假的时候执行的语句;
}
案例分析
看下面这段程序
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int a = 0;
printf("你想要一个女朋友吗\n");
scanf("%d", &a);
if (a)
{
printf("恭喜你脱单\n");
}
else
{
printf("单身万岁\n");
}
return 0;
}
程序的意思就是:
你想要女朋友吗
输入1,就会脱单
输入0,就继续单身
这样就是做出了一个选择
接下来让我们逐句解读一下
#define _CRT_SECURE_NO_WARNINGS 1
这句话是干啥的呢?我们暂时只需要知道这句话的作用是保证调用scanf函数的时候系统不会报错的就行了。如果你在运行程序的时候在出现scanf报错的情况,就请先加上它吧
由于讲过了程序框架,接下来我们就只看程序主体部分
int a = 0;
定义一个新的变量a并且初始化,初值为0
printf("你想要一个女朋友吗\n");
打印“你想要一个女朋友吗”这句话并且换行
scanf("%d", &a);
引入函数scanf
作用是把从键盘上输入的值赋值给指定变量
我们可以发现scanf函数和printf函数很像,唯一的不同就是在变量名前面必须加上一个取地址符&
这句话的意思从键盘接受一个数并且把它赋值给a
if (a)
{
printf("恭喜你脱单\n");
}
else
{
printf("单身万岁\n");
}
判断a的值,如果a的值是真(非零),就执行if下的语句:打印“恭喜你脱单”,如果a的值是零,就打印“单身万岁”
2.循环语句
计算机最强大的地方就是可以把同一个步骤在极短的时间内不知疲倦的重复成千上万次,我们利用这个强大特性的语句就是循环语句。接下来引入第二个主动技能——循环语句
语法
(由于是第零篇,仅介绍while语句)
while(条件)
{
条件为真时重复执行的语句;
}
条件为假则跳出循环
综合案例
“编程之路”
#include<stdio.h>
int main()
{
int input = 0;
printf("欢迎来到编程的世界\n");
printf("你要好好学习吗?(1/0):");
scanf("%d", &input);
if (input == 1)
{
printf("好工作\n");
int day = 0;
int line = 0;
printf("开始编程\n");
scanf("%d", &day);
while (line < day)
{
printf("坚持敲代码 坚持天数:%d\n", line);
line++;
}
if (line >= 20000)
printf("好offer\n");
else
printf("坠入爱河无心学习\n");
}
else
printf("回家种地\n");
return 0;
}
提示:使用了if的嵌套和while循环,并且使用if判断条件跳出循环
3.函数
我们在初中就学过函数,比如这样一个函数
f1(x)=2x
它的意思是把对象变成自身的二倍,你可能觉得这个函数并不能起到什么简化的作用,再看这一个函数
f2(x,y)=(x3+y3)/(x3-y3)
如果我们每次使用都把后面这一串运算表示出来就太麻烦了,我们可以直接用f2(x,y)来表示
同理,c语言中我们可以用已经写好的函数来极大的帮助我们简化主函数,让我们的代码更加清晰,层次分明,可读性强
语法
函数返回值类型 函数名(形式参数类型 形式参数名)
{
形式参数参与的语句;
return 返回值;
}
形式参数就就相当于f(x)=…中的x(实际参数就相当于使用函数时x的实际值)
返回值就是我们调用函数以后,函数输出的结果
案例
“比大小”
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Cpa(int a,int b)
{
if (a > = b)
{
return a;
}
else
{
return b;
}
}
int main()
{
int x = 0;
int y = 0;
int z = 0;
scanf("%d%d", &x, &y);
z = Cpa(x, y);
printf("%d", z);
return 0;
}
提示:注意定义函数的写法和调用函数的写法,注意形式参数和实际参数不要弄混
4.数组
要想存储5个数,怎么存储?你可能觉得我定义5个变量abcde就好了。那50个数呢?500个数呢?
引出C语言中数组的概念:一组相同类型元素的集合
定义数组
数组数据类型 数组名[数组元素个数] = {数组元素1,数组元素2};
访问数组元素
数组名[下标]
#include<stdio.h>
int main()
{
int i = 0;
int arr[10] = { 1 };
printf("%d", arr[0]);
return 0;
}
下标是按照0、1、2、3、4…的顺序排列的,所以第一个元素的下标是0
案例
打印数组中所有的元素
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", arr[0]);
int i = 0;
while (i < 10)
{
printf("%d\n", arr[i]);
i++;
}
return 0;
}
5.define定义宏
语法
#defne 宏名(形式参数)(宏内容)
综合案例
#include<stdio.h>
#define Add(x,y) (x+y)
int main()
{
printf("%d", 5*Add(2, 3));
return 0;
}
上图结果为25
define定义宏本质就是替换,作用和函数类似
6.指针
指针是C语言的灵魂,是当之无愧的大招。接下来我们详细讲解一下指针到底是个啥
内存
内存是计算机中用来储存数据的空间
就像是一个个空房子
为了方便我们拜访和管理它们,每个房子都有自己的地址
-
内存是怎么编号的?
32位、64位——指的是32/64根地址线——物理线——通电——1/0——产生了电信号
电信号就能够存储数字信息
以32位的机器举例
二进制数字
从
00000000000000000000000000000000
到
11111111111111111111111111111111
一共232个数字
可以用来当作232个房间的地址
我们就把这232个房间的地址叫做地址
也就是说我们可以管理232个内存单元 -
最小的内存单元是多大空间?
假如最小的内存空间是bit
1GB=210MB=220KB=230Byte=233bit
也就是说以bit为最小单位的232个地址可以管理0.5GB的内存
这样的话管理的空间就太小了,不太合适
所以我们规定
最小的内存单元是字节(byte)
这样232个地址就可以管理4GB的内存
指针
承接上面内存的内容,如果我们想要定义一个int型变量,将给它分配4个字节的空间
- 取地址
引入取地址符号&
取地址就是把一个变量的地址取出来
如果我们定义是四个字节的变量,虽然它每个字节都会分配地址,但是取地址就是取出这个变量第一个字节的地址
%p是打印地址的类型 - 指针变量(存地址)
专门定义出来用来存放地址的变量叫做指针变量,简称指针(指针其实就是这玩意儿)
int main()
{
int a = 10;
int* pa = &a;
return 0;
}
*表示pa是指针变量,int说明pa执行的对象int类型的
再比如char类型的指针变量就是char *
int main()
{
char ch = 'a';
char* pa = &ch;
return 0;
}
- 访问地址
int main()
{
int a = 10;
int* pa = &a;
*pa = 20;
return 0;
}
*解引用操作,*pa就是通过pa里面存放的地址找到a
*pa = 20;
这句话就是通过指针pa操作了指针指向的对象a,将a重新赋值为20
- 指针变量的大小
指针是用来存放地址的,而在32位机器上地址是32位的二进制数,大小是32bit,而地址有多大就决定了指针变量的大小
所以指针变量的大小就是32bit
也就是4个字节
(在64位机器上就是8个字节)
7.结构体
结构体可以通过不同的维度来描述复杂对象
创建结构体
创建结构体可以理解为创建了一个新的复合数据类型
struct 结构体名
{
定义成员变量1;
定义成员变量2;
};
struct Stu
{
char Name[20];
int age;
int height;
double score;
};
使用结构体
#include<stdio.h>
struct Stu
{
char Name[20];
int age;
float height;
double score;
};
int main()
{
struct Stu s = { "小白",18,1.74,85.5 };
printf("%s %d %f %lf", s.Name, s.age, s.height, s.score);
return 0;
}
struct Stu s = { "小白",18,1.74,85.5 };
s是结构体变量名
{}内部是结构体各项的内容
printf("%s %d %f %lf", s.Name, s.age, s.height, s.score);
s.Name中的点操作符是用来访问结构体成员
结构体变量 . 结构体成员
结构体指针
#include<stdio.h>
struct Stu
{
char Name[20];
int age;
float height;
double score;
};
int main()
{
struct Stu s = { "小白",18,1.74,85.5 };
struct Stu* pa = &s;
printf("%s %d %f %lf", (*pa).Name, (*pa).age, (*pa).height, (*pa).score);
return 0;
}
pa就是结构体指针
但是这种写法有点啰嗦,看下面这种写法
#include<stdio.h>
struct Stu
{
char Name[20];
int age;
float height;
double score;
};
int main()
{
struct Stu s = { "小白",18,1.74,85.5 };
struct Stu* pa = &s;
printf("%s %d %f %lf", pa->Name, pa->age, pa->height, pa->score);
return 0;
}
->就是箭头操作符
语法是
结构体指针->结构体成员名
至此为止,初识C语言结束。我们已经不再是雾里看花的门外汉,真正走上了编程之路。但是这只是刚刚完成了“定级赛”,只是我们从青铜到王者的起点。由于是初识C语言的文章,许多知识实际上并没有形成良好的闭环,希望部分看官谅解。