阅前说明
C语言学习之路系列博客,是博主自己在学习C语言的过程中所做的笔记,把知识框架整理记录下来,为了后续回顾与复习,同时也希望该博客可以帮助到一些正在学习C语言的小伙伴。博客内容如有错误或疏漏,请大家指出,谢谢啦,博主一定多多学习,及时改正过来。
使用的编译器:Visual Studio 2019(下载地址)
本篇前言
本篇大致介绍了C语言的基础知识,希望能让大家对C语言有一个大概的认识。每个知识点只是简单介绍,后续篇章都会一一详细讲解。
点击可直接跳转
C语言广泛应用于底层开发(不做详细介绍,可百度)
计算机语言发展过程
汇编语言 - 助记符 ADD/AND
//test.c(testcode project)
#include
int main()
{
printf("hello C language\n");
return 0;
}
main函数是程序的入口
一个工程/项目可以有多个.c文件,但只能有一个main函数
char //字符数据类型
short //短整型
int //整型
long //长整型
long long //更长的整型
float //单精度浮点数
double //双精度浮点数
C语言中没有没有字符串类型?
没有
为什么会出现这么多类型?
更加丰富的表达生活中的各种值,可以提高空间利用率
这里需要用到
sizeof()
操作符 - 获取类型或者变量所占空间大小(以字节为单位)
#include
int main()
{
//每种类型的大小是多少?
printf("%d字节\n", sizeof(int));
printf("%d字节\n", sizeof(short));
printf("%d字节\n", sizeof(long));
printf("%d字节\n", sizeof(long long));
printf("%d字节\n", sizeof(char));
printf("%d字节\n", sizeof(float));
printf("%d字节\n", sizeof(double));
printf("%d字节\n", sizeof(long double));
return 0;
}
运行结果(x86平台):
C语言标准:规定只要
sizeof(long)>=sizeof(int)
就可以了,不需要一定大于
bit - 比特位 - 一个比特位存放一个二进制位 0/1
byte - 字节 - 一个字节 = 8bit
kb - 1kb = 1024byte
mb - 1mb = 1024kb
二进制:0 1
八进制:0 ~ 7(三位二进制等于一位八进制)
十进制:0 ~ 9
十六进制:0 ~ 9 , A ~ F(四位二进制等于一位十六进制)
注:建议定义变量时给它初始化一个值
#include
int main()
{
//推荐定义变量时给它初始化一个值,比如:
int age = 0;
double weight = 52.3;
return 0;
}
#include
int a = 100; //全局变量 - { }外部定义的
int main()
{
int a = 10; //局部变量 - { }内部定义的
printf("a = %d", a); //输出a = 10
return 0;
}
思考:为什么会输出 a = 10 呢?
当局部变量和全局变量名称冲突时,局部优先
不建议把全局变量和局部变量的名字写成一样(如上面代码中的 a)
变量的作用域
这个变量可以在哪里使用,哪里就是它的作用域
局部变量的作用域
变量所在的局部范围
#include
int main()
{
int a = 10;
printf("%d\n", a);
{
int b = 20; //b的作用域为此 {} 内
}
//printf("%d\n", b); //error: "b"是未声明的标识符
return 0;
}
全局变量的作用域
整个工程(在同一工程的其它文件下使用需要用 extern 声明一下变量)
/*源文件demo2.c(test_4_5 project)*/
int x=5;
/*源文件demo.c(test_4_5 project)*/
#include
int main()
{
//声明 demo2.c 文件中的全局变量
extern int x;
printf("%d\n", x);
return 0;
}
变量的生命周期
变量的创建到销毁之间的时间段
//局部变量的生命周期:进入局部范围生命开始,出局部范围生命结束
//全局变量的生命周期:就是整个程序的生命周期
#include
int main() //一个程序的生命周期就是 main 函数的生命周期
{
{
//变量 a 声明开始
int a = 10;
printf("%d\n", a);
} //变量 a 声明结束
return 0;
}
C语言中常量分为以下几种:
const
修饰的常变量#define
定义的标识符常量1、字面常量
//字面常量
3.14;
10;
'a'; //字符常量 - 由一对单引号引起来的单个字符
"abcdef"; //字符串常量
2、const
修饰的常变量
常变量 - 具有常属性(不能被改变的属性) - 本质上还是变量,不能当常量使用
这样是错误的(只有在C99语法下才支持变长数组)
这样是可以的
int arr[10] = {
0 };
const int N = 9;
//思考:这两种写法正确吗?
arr[N] = 10; //这样是可以的
N = 20; //error
3、#define
定义的标识符常量
使用
#define
指令为程序中的常量赋予有意义的名称,它只是一个符号,在预编译阶段进行字符替换
(如下:所有 MAX 符号将被替换成 100)
#include
#define MAX 100 //#define 定义的标识符常量
int main()
{
MAX = 200; //error
return 0;
}
4、枚举常量
枚举常量 - 可以一一列举的常量:比如星期、三原色、性别
//定义枚举常量Sex
enum Sex
{
//枚举常量默认的值: 0 1 2 3 …… 依次递增 - 其值不可被更改
Male, // 0
Female, // 1
Secret // 2
};
#include
int main()
{
enum Sex S1 = Male;
printf("%d\n", Male); //输出:0
printf("%d\n", Female); //输出:1
printf("%d\n", Secret); //输出:2
return 0;
}
枚举常量不可以赋值,但可以在定义的时候 指定值,这个可以认为是定义值,而不是赋值。
enum Sex
{
//这个可不是赋值哦
Male = 3,
Female = 100,
Secret
};
printf("%d\n", Male);
printf("%d\n", Female);
printf("%d\n", Secret);
打印其值,运行结果为:
3
100
101
字符串就是由双引号引起来的一串字符
"hello world"
因为C语言没有字符串这种数据类型,所以要用到「 字符数组 」来存储字符串(每个元素都是字符类型的数组)
#include
int main()
{
char arr[] = "hello";
//['h']['e']['l']['l']['o']['\0']
return 0;
}
C语言规定,在每一个字符串常量的结尾,系统都会自动加一个字符 ‘\0’ 作为该字符串的“结束标志符”,系统据此判断字符串是否结束。计算字符串长度时,’\0’ 不算作字符串的内容哦;但是计算数组长度(或元素个数或所占内存空间大小),’\0’ 要算进去。
通过调试,打开监视窗口可看到字符数组内部情况
#include
int main()
{
char arr1[] = "abc"; //字符串
char arr2[] = {
'a','b','c' }; //是字符数组,但不代表字符串,末尾没有'\0'
printf("%s\n", arr1);
printf("%s\n", arr2);
return 0;
}
调试这段代码,我们发现,arr1 数组尾部有结束标志 ‘\0’ ,而 arr2 数组后面却没有 ‘\0’
再来看看运行结果,arr1 正常输出,而 arr2 后面却乱码了,输出了一堆奇怪的符号,这是为什么呢?
因为在输出 arr1 中的内容过程中,遇到了 ‘\0’ 是字符串的结束标志,所以停止输出;
而输出 arr2 中的内容时,输出完 abc 没有遇到字符串结束标志 ‘\0’ ,不知道什么时候停下来,而后面的内存空间的内容是未知的,放的是啥我们不知道,所以输出乱码。
![](https://img-blog.csdnimg.cn/img_convert/3984e84c9a5a3f9e3c9507928fe6bd98.png style="zoom:80%)
现在我们修改代码,主动在 arr2 后面放上 ‘\0’ ,输出看看效果
#include
int main()
{
//定义并初始化字符串的两种方式
//一定不要忘记结束标志 '\0' 了哦
char arr1[] = "abc";
char arr2[] = {
'a','b','c','\0' };
printf("%s\n", arr1);
printf("%s\n", arr2);
return 0;
}
运行结果:都能打印出 abc 由此可见,字符串的结束标志是至关重要的
这里要使用到一个函数
strlen()
- string length - 调用该函数需要引用头文件
#include
#include //调用strlen()函数
int main()
{
char arr1[] = "abc";
char arr2[] = {
'a','b','c' };
char arr3[] = {
'a','b','c','\0' };
//打印字符串的长度
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
printf("%d\n", strlen(arr3));
return 0;
}
运行结果:获取字符串的长度并输出时,字符数组 arr2 因为末尾没有字符串结束标志 ‘\0’ ,不知道长度到底是多少,所以输出随机值,获取不了字符串的长度。
注:在计算字符串长度的时候 ‘\0’ 是结束标志,不算作字符串的内容。
计算数组长度(或元素个数或所占内存空间大小), ‘\0’ 要算进去
#include
int main()
{
char arr[] = "abc";
int len = 0;
//计算数组 arr 的长度:数组内存总大小(字节)/单个数组元素内存大小(字节)
len = sizeof(arr) / sizeof(arr[0]);
printf("len = %d\n", len);
return 0;
}
运行结果:4(因为字符数组 arr 后面还有一个系统自动添加的结束符 ‘\0’ )
字符 ‘0’ —— ASCII码值为 48
转义字符 ‘\0’ —— 字符串的结束标志(不算作字符串的内容) - ASCII码值为 0
数字 0
转义字符顾名思义就是转变了字符原来的意思
来段代码感受一下,如果我们想要在屏幕上打印一个目录:c:\test\test.c
#include
int main()
{
printf("c:\test\test.c");
return 0;
}
运行结果:很奇怪,和我们想象的不一样唉,没有打印出目录,却出现了两段空白,这个字符\
和字符t
不再是普通的字符的意思了,转变了原来的意思,变成了转义字符\t
(水平制表符)
同理,字符\
和字符n
也不再是两个普通的字符了,而是转义字符\n
(换行符)
转义字符 | 释义 |
---|---|
\’ | 用于表示字符常量' |
\" | 用于表示一个字符串内部的双引号" |
\\ | 用于表示一个反斜杠,防止它被解释为一个转义序列符 |
\a | 警告字符,蜂鸣 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符(键盘上的 Tab 键) |
\v | 垂直制表符 |
\ddd | ddd表示 1-3 个八进制的数字,如:\130(从 \000 到 \377) |
\xdd | dd表示 2 个十六进制的数字,如:\x30 |
\b | 退格符 |
\f | 进制符 |
? | 在书写连续多个问号时使用,防止他们被解析成三字母词 |
'
怎么做?大家再没有学习转义字符之前可能会想直接输出打印就好啦,而写成下面这个错误示范了哦
错误示范:中间的你想要输出的那个单引号 ’ 和左边的单引号组成一对了,编译会 error
printf("%c\n", ''');
修改正确:为了防止其与左边的单引号组成一对,需要在单引号前加一个转义符
printf("%c\n", '\''); //这里 \'被解析成一个转义字符
同理:在屏幕上打印一个双引号"
就可以这样写啦
printf("%s\n", "\"");
c:\test\test.c
怎么做?printf("c:\\test\\test.c");
//输出字符 A - 对应的ASCII码的8进制形式是101 - 8进制的130是十进制的65
printf("%c\n", '\101');
//输出字符 a - 对应的ASCII码的8进制形式是141 - 8进制的141是十进制的97
printf("%c\n", '\141');
//输出字符 B - 对应的ASCII码的16进制形式是42
printf("%c\n", '\x42');
//输出字符 b - 对应的ASCII码的16进制形式是62
printf("%c\n", '\x62');
对于转义字符来说,只能使用八进制或者十六进制。
转义字符的初衷是用于 ASCII 编码,所以它的取值范围有限:
\ddd
,最大取值是\177
。\xdd
,最大取值是\x7F
。@参考文章:C语言转义字符
补充知识:
计算机只认识 0 和 1 两个数字,数据在内存中以二进制形式存储,我们在屏幕上看到的文字,在存储之前都被转换成了二进制(0和1序列),在显示时也要根据二进制找到对应的字符。
那么,怎么将英文字母和二进制对应起来呢?这就需要有一套规范,一种专门针对英文的字符集 - ASCII编码就被设计出来了,为每个字符分配了唯一的编码值,在C语言中,一个字符除了可以用它的实体表示,还可以用编码值表示。使用编码值来间接地表示字符的方式称为转义字符。
在 ASCII 编码中,大写字母、小写字母和阿拉伯数字都是连续分布的(见下表),这给程序设计带来了很大的方便。例如要判断一个字符是否是大写字母,就可以判断该字符的 ASCII 编码值是否在 65~90 的范围内。
@参考文章:ASCII编码,将英文存储到计算机
此图转载自百度
#include
#include
int main()
{
//程序输出什么呢?
printf("%d\n", strlen("c:\test\328\test.c"));
return 0;
}
运行结果:14(\t
和\32
分别是一个转义字符)
注释有两种风格:
在编写C语言源代码时,应该多使用注释,这样有助于对代码的理解。
/*xxxxxxxxxxxx*/
//xxxxxxxxxxxx
这里只是简单介绍一下各种操作符,目的是能够让大家在代码中识别出这些,后面我们会详细讲解和学习
+ - * / %
移位操作符 | 含义 |
---|---|
>> | 右移操作符 - 把二进制位向右移动 - 缺位补 0 |
<< | 左移操作符 - 把二进制位向左移动 - 缺位补 0 |
举例说明:
int a = 1;
int b = a << 1;
//a - 00000000000000000000000000000001 --> a = 1
//b - 00000000000000000000000000000010 --> b = 2
位操作符 | 含义 |
---|---|
& | 按位与 |
| | 按位或 |
^ | 按位异或 |
= //赋值符
+= -= *= /= &= ^= |= >>= <<= //复合赋值符
举例说明:
int a = 0;
a = a + 1;
//等效于:
a += 1;
单目操作符 | 含义 |
---|---|
! | 逻辑反操作 |
- | 负值 |
+ | 正值 |
& | 取地址 |
sizeof | 操作数的类型长度(以字节为单位) |
~ | 对一个数的二进制序列按位取反 |
– | 前置、后置– |
++ | 前置、后置++ |
* | 间接访问操作符(解引用操作符) |
(类型) | 强制类型转换 |
注:
双目操作符:有两个操作数(比如:a + b)
单目操作符:只有一个操作数
补充知识:C语言中如何表示真和假?
0 表示假,非 0 表示真
下面来展开讲解几个操作符
对一个数的所有二进制位取反,0 变成 1,1 变成 0
#include
int main()
{
int a = 0;
int b = ~a; //按位取反
printf("%d\n", b); //输出的是 b 的原码
return 0;
}
运行结果:-1
这个时候大家可能会有疑问了,为啥会是 -1 呀,别着急,仔细听我道来
这里需要引入原码 反码 补码的概念
整数在内存中存储的都是二进制形式的「 补码 」,输出打印时 要将其转换成「 原码 」
「 原码 」第一位是「 符号位 」表示 「 正负 」(1为负,0为正)
所以:
a - 00000000000000000000000000000000(补码)
对 a 的二进制位( ~ 按位取反)得到 b 的二进制形式的「 补码 」,因为输出的是「 原码 」所以我们进一步转换:
b - 11111111111111111111111111111111(补码)
b - 11111111111111111111111111111110(补码 -1得到反码)
b - 10000000000000000000000000000001(符号位不变,其它位按位取反得到原码)
而二进制序列 10000000000000000000000000000001
就是 -1
知识点:
整数(正数、负数)的原码反码补码:
int i = 2;
00000000000000000000000000000010 - 原码
00000000000000000000000000000010 - 反码
00000000000000000000000000000010 - 补码
二进制形式 | 转换规则 |
---|---|
原码 | 按照整数正负 写出二进制序列 |
反码 | 「 原码 」符号位不变 其它位按位取反 |
补码 | 反码 + 1 |
int i = -2;
10000000000000000000000000000010 - 原码
11111111111111111111111111111101 - 反码
11111111111111111111111111111110 - 补码
#include
int main()
{
int a = 10;
int b = ++a; //前置++:先++ 后使用
int c = a++; //后置++:先使用 后++
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
前置后置-- 同理
同时建议大家不要去研究这样的代码,浪费时间,在不同的编译器得到的结果都不一样,平时开发也几乎不会这样去写,可读性太差
int a = 1;
int b = (++a) + (++a) + (a++);
一般在类型不匹配的时候使用,不推荐,既然你会使用强制类型转换,说明一开始在编写程序的时候就没有设计好各变量的数据类型,导致程序有缺陷
int a = (int)3.14;
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
描述并且、或者的关系
逻辑 与 / 或 的结果为:真 / 假
&& 逻辑与
|| 逻辑或
表达式 exp1 成立,整个表达式的结果是 exp2 的结果
表达式 exp1 不成立,整个表达式的结果是 exp3 的结果
exp1 ? exp2 : exp3 //也是三目操作符,有三个操作数
举例说明:
#include
int main()
{
int a = 10;
int b = 20;
int max = a > b ? a : b;
printf("max = %d\n", max);
return 0;
}
运行结果:max = 20
逗号隔开的一串表达式,从左向右依次计算的,整个表达式的结果是最后一个表达式的结果
exp1, exp2, exp3, …expN
举例说明:
#include
int main()
{
int a = 1;
int b = 2;
int c = 5;
//思考 d 的值是多少?
int d = (a = b + 2, c = a - 4, b = c + 2);
printf("d = %d\n", d);
return 0;
}
运行结果:d = 2
思考题:
函数调用 exec( ( vl, v2 ), ( v3, v4 ), v5, v6 ); 中,实参的个数是:( 4 ) - 分别是v2 v4 v5 v6
[] 下标引用操作符
() 函数调用操作符
. 结构体成员访问操作符
->
举例说明
1.关键字是C语言提供的,不能自己创建关键字
2.关键字不能做变量名
auto(修饰局部变量,一般是省略的) break case char const continue default
do double else enum(枚举) extern(声明外部符号) float for goto if int
long register(寄存器) return short signed(有符号数) unsigned(无符号数)
sizeof(计算机类型/变量所占内存空间大小) static(静态) struct(结构体) switch
typedef(类型定义) union(联合体/共用体) void volatile(C语言中暂时不讲) while
计算机中,数据可以存放到哪里呢?
寄存器(会被频繁调用的数据,放在寄存器中,可以提高效率,现在编译器已经能够自动识别这种数据并放在其中,register实用意义不大)
高速缓存
内存
硬盘
#define
是不是关键字?include
是不是关键字?都不是,它们是预处理指令(预编译期间处理的指令)
关键字这次先简单介绍几个,后续遇到了再讲解
typedef
类型重定义,类型重命名,相当于取了一个别名
举例说明:
#include
//把 unsigned int 重命名为 u_int,现在 u_int 也是一个类型名了
typedef unsigned int u_int;
int main()
{
//a 和 b 的类型相同
unsigned int a = 10;
u_int b = 20;
return 0;
}
static
static
用来修饰变量和函数
1)修饰局部变量 - 静态局部变量
2)修饰全局变量 - 静态全局变量
3)修饰函数 - 静态函数
static
修饰局部变量改变了局部变量的生命周期(本质上是变量的存储类型),让静态局部变量出了作用域依然存在,直到程序结束生命周期才结束
代码1:
#include
void test()
{
int a = 1; //局部变量 a
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
test();
}
return 0;
}
运行结果:2 2 2 2 2 2 2 2 2 2(10个2)
代码2:
#include
void test()
{
static int a = 1; //静态局部变量a
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
test();
}
return 0;
}
运行结果:2 3 4 5 6 7 8 9 10 11
为啥会出现这样的结果呢?
这里补充一点小知识,内存会被分为几个区域,今天在这里我们只讨论和这次C语言有关的几个区域,局部变量 a 开始放在栈区的,被 static
修饰后就被放到静态区去了,改变了它的存储类型,存在不同的区域,就有不同的特点了,静态变量 a 的生命周期和全局变量一样,为整个程序
static
修饰全局变量
static
修饰全局变量,使得这个全局变量只能在自己所在的源文件(.c)内使用不能在同一工程的其他源文件内使用
代码实例:
/*源文件demo2.c(test project)*/
//静态全局变量g_val
static int g_val = 2021;
/*源文件demo.c(test project)*/
#include
//声明静态全局变量 g_val
extern int g_val;
int main()
{
printf("%d\n", g_val);
return 0;
}
运行结果:error
运行程序,结果error了,那么 static
修饰全局变量的本质是什么呢?
全局变量,可以在同一工程的其他源文件内被使用,是因为全局变量具有外部链接属性
但是当被 static
修饰成静态全局变量,就变成了内部链接属性,其他源文件就不能链接到这个静态全局变量了
所以无法被其他源文件所使用,只能在自己所在的源文件内使用
static
修饰函数
static
修饰函数,使得这个函数只能在自己所在的源文件(.c)内使用不能在同一工程的其他源文件内使用
本质上是:
static
将函数的外部链接属性变成了内部链接属性(和static
修饰全局变量一样)
仔细对比代码1和代码2,理解 static
在修饰函数前后的意义
代码1:
代码2:
#define
定义宏
#define
是C语言提供的宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译,宏替换是简单的替换。
还有就是要记住这是简单的替换而已,不要在中间计算结果,一定要替换出表达式之后再算。
//普通宏
#define PI (3.1415926)
//带参数的宏
#define ADD(a,b) ( (a) + (b) )
//关键是十分容易产生错误,包括机器和人理解上的差异等等
来看一个例子:
int a = 2 * ADD(2, 3);
预处理阶段之后,ADD(2, 3)
则会被替换成 ( (2) + (3) )
int a = 2 * ( (2) + (3) ); //结果为 10
如果没有括号
#define ADD(a,b) a + b
替换之后的结果是:
int a = 2 * 2 + 3; //结果为 7
为了先将C语言拉通介绍一遍,能够对其有一个整体的认识,这里的内容只进行一个简单的介绍,后续会详细讲解
生活中的选择无处不在,那么在计算机中,怎么用C程序设计语言表示呢?
如果(if)就……
否则(else)就……
这就是选择!
#include
int main()
{
int a = 10;
int b = 20;
if (a > b)
{
printf("较大值:a\n");
}
else
{
printf("较小值:b\n");
}
return 0;
}
如果 a 比 b 大,输出 a,否则就输出 b
生活中有些事,要一直做,重复做,比如学习、吃饭、睡觉等等,如何用C语言实现循环呢?
后面会详细讲解
假设(我是 main函数)(张三是 add 函数),有一天,我想让张三给我带饭,所以得告诉他我想带什么饭,并给他带饭的钱,(蛋炒饭,10元),张三接收到信息和拿到钱,最后返回(return)给我一份蛋炒饭
实例:用函数实现求两数的和
#include
int add(int x, int y) //求两数的和
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int sum = add(a, b);
printf("sum = %d\n", sum);
return 0;
}
函数的特点就是简化代码,代码复用
我们需要存储 10 个数字,怎么办呢?如果去定义 10 个变量来存储,那也太麻烦了,这时我们可以用数组
C语言中数组的定义:一组相同数据类型的元素的集合
int arr1[10] = {
1,2,3,4,5,6,7,8,9,10 }; //完全初始化
int arr2[5] = {
1,2,3 }; //不完全初始化,剩余的默认为 '\0'
arr1[0] = 1;
arr1[1] = 2;
……
arr1[9] = 10;
指针是C语言中非常非常重要的内容,我们要谈指针,必须先要搞明白内存
内存是电脑上特别重要的存储器,计算机中所有程序的运行都是在内存中进行的。
为了有效的使用内存,就把内存划分成一个个小的内存单元,「 每个内存单元的大小是1个字节 」。
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该「 内存单元的地址 」。
电脑分为 32位 和 64位 的,以 32位 举例
32位 - 有32根地址线 - 通电会产生 1/0 电信号 - 电信号转换成数字信号 - 变成32个 1/0 组成的二进制序列
共有 2 (32) = 4,294,967,296 种不同的序列
00000000000000000000000000000000
00000000000000000000000000000001
…………
01111111111111111111111111111111
11111111111111111111111111111111
我们就可以用这些二进制序列来给内存单元编号,一个字节的编号就会由32个0/1组成,这就是它们的地址。
内存地址只是一个编号,代表一个内存空间。在计算机中存储器的容量是以字节为基本单位的。也就是说一个内存地址代表一个字节(8bit)的存储空间。例如经常说 32位 的操作系统最多支持4GB的内存空间,也就是说CPU只能寻址2的32次方(4GB)空间。
类比到生活中,一栋宿舍楼相当于一块内存区域,被划分成了一个个宿舍,为了方便管理和找到某一个宿舍,我们给每一间宿舍都编了门牌号,比如307,表示3楼7号,这样很容易就找到它们的位置在哪里了。
#include
int main()
{
//num在内存要被分配 4字节 的内存空间
int num = 10;
//%p - 以地址的形式打印
printf("%p\n", &num);
return 0;
}
运行结果:004FFD64(这个地址是变量num所在内存空间的第一个字节的地址)
在VS2019中,F10进入调试模式,选择调试菜单 - 窗口 - 内存,就可以找到内存窗口
输入 &变量名
,取出变量的地址,这个地址是变量所在内存空间的第一个字节的地址
指针变量存放相同数据类型的变量的首地址
//定义一个整型指针变量,存储整型变量num的地址
int num = 10;
int* p = #
//字符型指针变量指向字符型变量
char ch = 'b';
char* str = &ch;
指针变量的使用举例:通过指针变量存放的变量的地址,找到所指向的变量
#include
int main()
{
int num = 10;
//指针变量 p
int* p = #
//* 是 解引用操作符 / 间接访问操作符,访问指针 p 指向的内存空间
*p = 20;
printf("num = %d\n", num);
return 0;
}
运行结果:num = 20
任何类型的指针变量在 32位平台 是4个字节大小,64位平台 是8个字节大小
指针变量占几个字节跟语言无关,取决于变量的地址的存储需要多大的空间,进一步讲,是与系统的寻址能力有关*(提示:结合上面内容 - 1、什么是内存 - 内存是如何编号的? - 进行理解)*
32位系统 - 32根地址总线 - 一个地址是 32 个比特位 - 4字节
64位系统 - 64根地址总线 - 一个地址是 64 个比特位 - 8字节
所以,不管你是整型变量还是字符型变量还是浮点型变量,在 32/64 位平台中,你的第一个字节(首地址)的编址是由 32/64 个比特位组成的二进制序列,占 4/8 个字节
#include
int main()
{
printf("%d字节\n", sizeof(char *));
printf("%d字节\n", sizeof(int *));
printf("%d字节\n", sizeof(double *));
return 0;
}
运行结果:在(32位平台)均输出 4字节、在(64位平台)均输出 8字节
结构体是C语言中特别重要的内容,结构体使得C语言可以描述复杂类型
比如描述一个人,用之前学过的任何一种类型变量都不足以描述清楚,int? float? char?,似乎都没内味,人是一个复杂对象,有姓名,年龄,性别等等;描述一本书,有作者,定价,书类等等
而我们用结构体,就可以创造一些类型,来描述这些复杂类型了
接下来,我们来创建一个学生的结构体
struct Student
{
char name[20]; //姓名
int age; //年龄
double grade; //成绩
};
结构体变量的定义与初始化
一般在定义时初始化,比较方便;定义之后初始化,就只能对每一个成员一一赋值初始化了
//定义一个结构体变量并初始化
struct Student s1 = {
"玛卡巴卡",20,85 };
//定义之后初始化
strcpy(s1.name, "李四"); //因为name是数组名,它是一个地址,不能用 s1.name = "李四";
//strcpy - 字符串拷贝函数 - 库函数 - 引用头文件string.h
s1.age = 25;
s1.grade = 90;
访问结构体变量中的成员
//方法(1):结构体变量.结构体成员
printf("name: %s age: %d grade: %lf\n", s1.name, s1.age, s1.grade);
//方法(2):结构体指针->结构体成员
struct Student* ps = &s1;
printf("name: %s age: %d grade: %lf\n", ps->name, ps->age, ps->grade);
//方法(3):太麻烦,建议用 ->操作符 访问更直观
printf("name: %s age: %d grade: %lf\n", (*ps).name, (*ps).age, (*ps).grade);