1. 什么是C语言
1.1 语言是什么
要想明白什么是C语言,首先要清楚语言是什么,语言的作用是什么.
语言就广义而言,是采用一套具有共同处理规则来进行表达的沟通指令,指令会以视觉、声音或者
触觉方式来传递.我们所说的语言是我们互相沟通的一种工具,那我们如何与计算机沟通呢,这就需要计算机语言,C语言就是我们和计算机交流的众多语言中的一种.
1.2 C语言主要用在哪些方面
在企业开发中,主要有两种开发形式,分为上层开发和底层开发.C语言主要用于底层开发.
-
上层开发:主要是应用程序开发,各种操作系统有不同的语言用来进行开发,更加多地考虑算法和应用的实现
Windows:C++, MFC / QT
Android: Java
Linux: C/C++, GTK+/QT(Linux图形界面应用程序)
-
底层开发:主要是对硬件的直接操作,更需要抽象能力
C语言主要用在底层编程,比如编译器,JVM,嵌入式软件
1.3 C语言标准
为了规范C语言,就像人类语言也有语法一样,由美国国家标准局制定了ANSI C标准,从C89->C90->C99->C11->C17…
1.4 计算机语言的发展历程
从一开始的二进制编码,到使用一些助记符通过汇编语言编写程序,再到后来的B语言,C语言等等第三代语言,计算机语言一直在发展.
1.5 编译器
编译器,就是将一种语言(通常为高级语言)翻译为另一种语言(通常为低级语言)的程序.
-
一个现代编译器的主要操作流程:源代码->预处理器->编译器->目标代码->链接器->可执行程序
-
C语言的编译器有很多:Microsoft Visual C++,GCC,Clang
2. 创建第一个C语言代码
2.1 创建过程
创建项目->创建.c源文件->编写代码
2.2 执行过程
.c—预处理器—>.i—编译器—>.s—汇编器—>.o—链接器—>.exe可执行程序
2.3 内容细节
- 程序第一步会从main函数开始逐步执行,
main
函数是程序的入口
#define _CRT_SECURE_NO_WARNINGS 1
//在屏幕上打印:HelloWorld!
#include <stdio.h> //预处理器在这导入对应库
//<stdio.h>是表示输入输出的
int main() //main是一个关键字,代表主函数
{
printf("HelloWorld!\n"); //使用printf函数打印
//""表示一个字符串,这里用printf函数将字符串打印
return 0;
}
- 几种main函数写法
int main()
{
return 0;
}//常用写法
void main()
{
}//太过古老
int main(void)
{
return 0;
}//也可以写成这种
int main(int argc, char* argv[])
{
return 0;
}//涉及命令行参数,之后再说
- 打印细节:使用print函数打印,需要先导入<stdio.h>库
3. 数据类型
编程为了解决生活中的问题,而数据类型则是生活中各种数字类型的体现,比如小数,整数对应浮点型,整型
3.1 分类
数据类型 | 名称 |
---|---|
char | 字符类型 |
short | 短整型 |
int | 整型 |
long | 长整型 |
long long | 更长的整型 |
float | 单精度浮点型 |
double | 双精度浮点型 |
C99引入了 _Bool 布尔类型,long double类型
3.2 数据类型大小
在计算机内部存储数据是需要空间的,那么如何看到计算机内部对应数据类型的大小呢,这里就要利用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;
}
可能有些电脑上sizeof(long) == 8
,这是不同编译器所规定的.C标准规定了sizeof(long) >= sizeof(int)
.
这里的1,2,4,8,所指的单位指的是数据类型所占字节长度.提到字节长度,就要提到计算机中的单位了
3.3 计算机中的单位
单 位 | |
---|---|
bit | 比特位 一个1或者一个0占 1bit |
Byte | 字节 1 Byte = 8 bit |
KB | 1 KB = 2 ^ 10 Byte |
MB | 1 MB = 2 ^ 10 KB |
GB | 1 GB = 2 ^ 10 MB |
TB | 1 TB = 2 ^ 10 GB |
PB | 1 PB = 2 ^ 10 TB |
… | … |
4 变量,常量
4.1 变量的命名
只能由字母(包括大写和小写),数字和下划线(_
)组成
不能以数字开头
长度不能超过63个字符
变量名中间区分大小写
4.2 定义变量的方法
#include <stdio.h>
int main()
{
int a = 10; //a是整数
float b = 10.0; //b是浮点数,C语言默认为double类型,即使是用float定义的变量
float c = 10.1f; //可以在数字末尾加个f,表示这个数是单精度浮点型
double pi = 3.14; //变量名要见名知意,例如pi指圆周率
return 0;
}
4.3 变量的分类
- 局部变量
- 全局变量
当全局变量和局部变量的名称有冲突时,局部优先.
#include <stdio.h>
int a = 2022; //这里的a是全局变量
int main()
{
int b = 2022; //这里的b是全局变量
//下面这样定义不会有什么问题
int a = 2023; //这里的a是局部变量
printf("%d\n", a); //2023
return 0;
}
局部可以修改全局变量名称的值,而全局则不可以修改局部变量的值.
#include <stdio.h>
int a = 2022; //这里的a是全局变量
b = 2022; //修改失败
int main()
{
a = 2023; //修改成功
int b = 2023;
printf("%d\n", a); //2023
printf("%d\n", b); //2023
return 0;
}
4.4 变量的使用
变量可以直接进行赋值修改.
#include <stdio.h>
int main()
{
int a = 0;
a = 1; //赋值修改
return 0;
}
变量可以通过scanf()
输入,通过printf()
输出.
#include <stdio.h>
int main()
{
int a = 0;
scanf("%d", &a); //输入
printf("%d", a); //输出(打印)
return 0;
}
补充
使用scanf()
和printf()
需要用到<stdio.h>
.
scanf()
: 数据从键盘读取到内存中.printf()
: 数据从内存输出到屏幕上.
- 格式匹配
占位符需要与变量相匹配.
名称 | 解释 |
---|---|
%d | 整型int |
%c | 字符类型char |
%f | 浮点型float |
%lf | 浮点型double |
%s | 字符串类型 |
%p | 地址类型 |
… | … |
- 输入输出函数格式
scanf("[占位符]", [变量地址]) ;
printf("[占位符]", [变量名]) ;
4.5 变量的作用域和生命周期
作用域
作用域(scope)是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,
而限定这个名字的可用性的代码范围就是这个名字的作用域.
变量在哪里可以使用,哪里就是变量的作用域.
- 局部变量的作用域是变量所在的局部范围.
int main()
{
{
int a = 0; //这是一个局部变量,其作用域为定义该变量的范围
}
a = 1; //Error 在范围外即变量作用域外不可使用或修改
return 0;
}
- 全局变量的作用域是整个工程,是在一个project内,而不仅仅局限于一个.c源文件.
使用同一工程不同文件的全局变量需要用到extern
关键字,这是一个声明外部符号的关键.字
//1.c
#include <stdio.h>
int a = 0;
//test.c
#include <stdio.h>
extern int a; //声明外部变量a
int main()
{
printf("%d\n"); //0
}
全局变量是不安全的,工程内任何地方可以使用它,任何地方可以修改它.
不同文件中定义相同名称的全局变量会产生冲突.
//1.c
#include <stdio.h>
int a = 0;
//test.c
#include <stdio.h>
extern int a; //声明外部变量a
int a; //Error 找到一个或多个多重定义的符号
int main()
{
printf("%d\n"); //0
}
生命周期
变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段.
创建变量本质是在内存开辟一个空间,出作用域这块空间被销毁(还给操作系统),这块空间里面内容不可被编译器使用.
- 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束-----定义位置到本作用域的结束.
- 全局变量的生命周期是:整个程序的生命周期-----定义位置到本文件的结束.
4.5 常量
C语言中常量与变量的定义的形式有所差异.
C语言中常量分为以下几种:
- 字面常量
const
修饰的常变量#define
定义的标识符常量- 枚举常量
enum
不管哪一种形式的常量都不可以被修改.
#include <stdio.h>
#define PI 3.14 //#define 修饰的标识符常量
#define W 'c' //#define 修饰的标识符常量
//枚举常量
enum Week
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturaday
};
int main()
{
//字面常量
3.14;
2023;
//const 修饰的常变量
const int YEAR = 2023;
YEAR = 2022; //Error const修饰的常变量不可被修改
//#define 定义的标识符常量演示
printf("PI = %lf\n", PI); //3.140000
printf("W = %c\n", W); //c
//enum 定义的枚举常量演示
printf("%d\n", Sunday); //0
printf("%d\n", Monday); //1
printf("%d\n", Thursday); //4
return 0;
}
注:
- 枚举常量默认第一个元素的值从0开始,依次递增1.
可以自己指定值,下一个元素默认值加一.
#include <stdio.h>
enum Score
{
Math,
Chinese = 60,
History,
English,
Physics = 90
};
int main()
{
printf("%d\n", Math); //0
printf("%d\n", Chinese); //60
printf("%d\n", History); //61
printf("%d\n", English); //62
printf("%d\n", Physics); //90
return 0;
}
- 上面
const
修饰的常变量YEAR
在C语言中只是语法层面限制了变量YEAR
不能直接被改变,但本质上YEAR
还是一个变量,所以叫做常变量.与之相关的,使用#define
定义的标识符常量本质就是常量,不是变量.
#include <stdio.h>
#define SIZE2 10 //#define 定义的标识符常量
int main()
{
const int SIZE1 = 5; //const 定义的常变量
int arr1[SIZE1]; //Error 定义数组时,数组方括号[]内必须存放常量表达式
int arr2[SIZE2]; //运行通过 #define 定义的是常量
return 0;
}
5. 字符+字符串+转义字符
5.1 字符+字符串
'a'
'b'
'1'
"Hello World"
"Windows"
由单引号引起来的称为字符,以ASCII码值存储在计算机底层.
由双引号(Double Quoto)引起来的一串字符称为字符串字面值(String Literal),或者简称字符串.
#include <stdio.h>
int main()
{
char ch1 = 'c';
char ch2 = '1';
char str1[] = "Hello World!";
char str2[] = "Windows";
printf("%c\n", ch1); //c
printf("%c\n", ch2); //1
printf("%s\n", str1); //Hello World!
printf("%s\n", str2); //Windows
return 0;
}
注:字符串的结束标志是一个\0
的转义字符.在计算字符串长度时,\0
是结束标志,不算作字符串的内容.
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = {'a', 'b', 'c'}; // 'a' 'b' 'c'
char arr2[] = "abc"; // 'a' 'b' 'c' '\0'
printf("%s\n", arr1); //abc烫烫烫烫烫烫烫烫烫烫烫烫烫烫蘟bc
printf("%s\n", arr2); //abc
printf("%d\n", strlen(arr1)); //35
printf("%d\n", strlen(arr2)); //3
return 0;
}
- 字符串本质上是在内存上申请一串连续的空间存放一串字符.
- 字符串末尾存在一个
'\0'
.字符串是一个末尾是'\0'
的字符数组,'\0'
只起一个表结束的作用,不影响字符串的长度和内容. - 打印字符串,
print()
里占位符为%s
,将从字符串首地址依次打印字符直至遇到'\0'
- 使用
strlen()
计算字符串长度时,计算首地址到'\0'
位置之前字符的个数
5.2 转义字符
转变原来意思的字符
我们要在屏幕上打印一个目录:c:\test\test.c
该怎么写代码呢?
#include <stdio.h>
int main()
{
printf("c:\test\test.c");
return 0;
}
这样写是得不出我们需要的结果的,结果为c: est est.c
,在这里\t
其实代表了一个转义字符,用于表示一个水平制表符.
下面是一些转义字符
转义字符 | 释义 |
---|---|
\? | 在书写连续多个问号时使用,防止被解析成三字母词. 如:??( -> [ |
\’ | 用于表示字符常量’ |
\" | 用于表示一个字符串内部的双引号" |
\\ | 用于表示一个反斜杠,防止被解析成一个转义序列符 |
\a | 警告字符,蜂鸣 |
\b | 退格符,相当于Backspace |
\f | 进纸符 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | d d d 表示一个1-3个八进制的数字. 如: \130 表示字符X |
\xdd | d d 表示2个十六进制的数字. 如: \x30 表示字符0 |
注:
\ddd和 \xdd
在计算机底层都是一串二进制数,对照ASCII码表得到对应字符
问题1:打印一个单引号’
问题2:打印一个双引号"
#include <stdio.h>
int main()
{
printf("%s\n", "\'"); //'
printf("%s\n", "\""); //"
return 0;
}
问题3:程序输出什么
#include<stdio.h>
int main()
{
printf("%d\n", strlen("c:\test\628\test.c")); //14
printf("%d\n", strlen("\t")); //1
return 0;
}
- 一个转义字符占据一个字符长度 ,
\t
长度为1 \62
是一个转义字符而非\628
,改行共三个转义字符,最终长度为14
6. 注释
注释是一个解释性语句,主要用于解释代码的用途,也可用于对不需要使用的代码的注释
- C语言注释风格:块注释
/*...*/
/*int Sub(int x, int y)
{
return x - y;
}
*/
/*...*/
:用于注释一块内容,不能嵌套注释.例如:/*... /*...*/ ... */
,块注释只接收一个*/*
,最后一个*/
会被忽略
- c++注释风格:行注释
//......
//int Sub(int x, int y)
//{
// return x - y;
//}
//......
:可以注释一行,也可注释多行
7.选择语句
C语言通过if
和switch
实现选择
简单实现一下使用if
的选择语句代码
#include <stdio.h>
int main()
{
int flag = 0;
print("你会好好学习吗?(0(不) or 1(会):");
scanf("%d", &flag);
if (flag == 0)
{
printf("卖红薯.\n");
}
else
{
printf("好好学习吧!\n");
}
return 0;
}
8. 循环语句
C语言通过三种语句实现循环
while
语句
for
语句
do...while
语句`
简单实现一下使用while
的选择语句代码,用while
实现打印0-10000的数
#include <stdio.h>
int main()
{
int i = 0;
while (i <= 10000)
{
printf("%d\n", i);
i++;
}
return 0;
}
9. 函数
函数简单来说,就是把一块代码封装成一个模块,它能完成一个独立功能.
下面的代码的呢容是输入两个数并输出两数之和
#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
scanf("%d %d", &num1, &num2);
sum = num1 + num2;
printf("%d\n", sum);
return 0;
}
可以将它写成下面这样,使用到函数
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
scanf("%d %d", &num1, &num2);
sum = Add(num1, num2);
printf("%d\n", sum);
return 0;
}
上面的代码中,我们自定义了一个Add
函数,这个函数接收了两个整型参数,返回了一个整型值,实现的功能是将两个整数相加返回.
函数的特点就是简化代码,代码复用.
10. 数组
C语言数组的定义:一组相同类型元素的集合
10.1 数组的定义
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 }; //定义了一个整型数组,最多存放十个元素
char ch[10]; //定义了一个字符数组,最多存放十个元素
10.2 数组的下标
C语言规定:数组的每个元素都有一个下标,下标是从0开始的,下标最大值为数组的长度减一.
数组可以通过下标直接访问
#include <stdio.h>
int main()
{
int arr[10] = { 0,4,3,77,445,67,13,43,45,676};
printf("%d\n", arr[0]); //0
printf("%d\n", arr[1]); //4
printf("%d\n", arr[2]); //3
printf("%d\n", arr[3]); //77
printf("%d\n", arr[4]); //445
printf("%d\n", arr[5]); //67
printf("%d\n", arr[6]); //13
printf("%d\n", arr[7]); //43
printf("%d\n", arr[8]); //45
printf("%d\n", arr[9]); //676
return 0;
}
int arr[10] | 0 | 4 | 3 | 77 | 445 | 67 | 13 | 43 | 45 | 676 |
---|---|---|---|---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10.3 数组的使用
因为可以直接通过数组下标访问数组,可以通过使用for循环遍历数组
#include <stdio.h>
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
//遍历数组并进行打印
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
11. 操作符
- 算数操作符
+ - * / %
算数操作符对应数学中的运算符号,需要注意的是:两个整数使用/
进行除法,得到的结果会自动取余(而非四舍五入)成整数.若要得出浮点数结果,需要将至少一个整型运算数转换成浮点型
{
//算数操作符的应用
int a = 10;
int b = 3;
int result1 = a + b; //13
int result2 = a - b; //7
int result3 = a * b; //30
int result4 = a / b; //3
int result5 = a % b; //1
double result6 = 10.0 / 3; //3.3333333
printf("%d\n", result1);
printf("%d\n", result2);
printf("%d\n", result3);
printf("%d\n", result4);
printf("%d\n", result5);
printf("%lf\n", result6);
return 0;
}
- 移位操作符
>> <<
- 位操作符
& ^ |
位操作符都是对二进制直接操作
- 赋值操作符
= += -= *= /= &= ^= != >>= <<=
对变量操作并进行赋值
- 单目运算符(只有一个操作数)
! //逻辑反操作
- //负值
+ //正值
& //取地址
sizeof //操作数的类型长度(以字节为单位)
~ //对一个数的二进制按位取反
-- //前置,后置--
++ //前置,后置++
* //间接访问操作符(解引用操作符)
(类型) //强制类型转换
- 逻辑反操作
!
C语言中用0
表示假,用非0
表示真,非真
都等于0
int main()
{
int a = 0;
if (a) //a为真 打印haha
{
printf("haha\n");
}
else if (!a) //a为假 打印hehe
{
printf("hehe\n");
}
int b = 5;
printf("%d\n", !b); //!真 == 0
printf("%d\n", b); //5
return 0;
}
-
sizeof
sizeof是一个操作符不是一个函数,所以后面跟着为变量名时,可以省略()
,但是后面跟着类型名,不可以省略()
例如:sizeof a
是可以的,而sizeof int
是不可以的 -
--
和++
符号在前,先运算再操作
符号在后,先操作再运算
这里的操作指使用这个变量所绑定的值
#include <stdio.h>
int main()
{
int a = 0;
int b = a++;
int c = ++a;
printf("a = %d, b = %d, c = %d\n", a, b, c); //2, 1, 2
printf("%d\n", a++); //2
printf("%d\n", ++a); //4
return 0;
}
- 强制类型转换
(类型)
强制按类型所占字节对数据进行截取,不是四舍五入
#include <stdio.h>
int main()
{
int pi = (int)3.14;
printf("%d\n", pi);
return 0;
}
- 逻辑操作符
&& 逻辑与
|| 逻辑或
短路运算:
1.&&
两者都为真才为真,只要左边为假就为假
2.||
两者都为假才为假,只要左边为真就为真
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 1;
int b = 0;
if (a && b) // 0 && 1 为假
{
printf("haha\n");
}
if (a || b) // 0 || 1 为真
{
printf("hehe\n");
}
return 0;
}
- 条件操作符(三目运算符)
exp1 ? exp2 : exp3
exp1 为真,执行表达式exp2
exp1 为假,执行表达式exp3
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int m = a > b ? a : b; //m为a,b中的最大值
//等价于 if...else
//if (a > b)
//{
// m = a;
//}
//else
//{
// m = b;
//}
printf("%d\n", m);
return 0;
}
- 逗号表达式
exp1, exp2, exp3, ...expN
从左向右依次计算,结果是最后一个表达式的结果
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = 2;
int d = (a += 3, b += 6, c += 1, a + b + c);
printf("%d\n", a); //6
printf("%d\n", b); //11
printf("%d\n", c); //3
printf("%d\n", d); //20
return 0;
}
- 下标引用,函数调用和结构成员
[] () . ->
#include <stdio.h>
int Add(int a, int b)
{
return a + b;
}
int main()
{
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
printf("%d\n", arr[9]); //[] 为下标引用操作符
int sum = Add(arr[0], arr[1]); //() 为函数调用操作符
//在这例子中有三个操作数 Add arr[0] arr[1]
printf("%d\n", sum);
return 0;
}
12. 常见关键字
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
C语言提供了丰富的关键字,这些关键字都是语言本身预先设定好的,用户自己是不能创造关键字的.
- 与变量位置相关的
关键字 | 描述 |
---|---|
auto | 自动,自动为局部变量 |
register | 建议编译器将变量存储在寄存器中 |
extern | 声明外部符号 |
static | 声明静态变量 |
- 循环相关
关键字 | 描述 |
---|---|
break | 跳出循环 |
continue | 跳出本次循环 |
do while | |
while | |
for |
- 分支相关
关键字 | 描述 |
---|---|
break | |
switch…case | 多选择判断 |
goto | 跳至某一区域 |
default | 默认 |
if…else |
- 变量类型相关
数据类型 | 修饰类型 | 自定义类型 | ||
---|---|---|---|---|
char | const | struct | 结构体类型 | |
double | signed | 有符号 | enum | 枚举体类型 |
float | unsigned | 无符号 | union | 联合体类型 |
short | typedef | 类型重定义 | ||
int | ||||
long | ||||
short | ||||
void |
-
函数的返回
return
-
其他
关键字 | 描述 |
---|---|
volatile | |
void | 函数的参数 代表参数为空 |
修饰指针 代表空指针 |
12.1 关键字 typedef
typedef
顾名思义就是类型定义,这里应该理解为类型重命名.
#include <stdio.h>
typedef unsigned int uint_32;
int main()
{
//uint_32 和 unsigned int 这两个类型是一样的
uint_32 num1 = 0;
unsigned int num2 = 0;
return 0;
}
12.2 关键字static
在C语言中:
static是用来修饰变量和函数的
1.修饰局部变量-称为静态局部变量
2.修饰全局变量-称为静态全局变量
3.修饰函数-称为静态函数
12.2.1 修饰局部变量
//代码1 最后程序执行会打印出十个1
//1 1 1 1 1 1 1 1 1 1
#include <stdio.h>
void test()
{
int i = 0;
i++;
printf("%d ", i);
}
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
test();
}
return 0;
}
//代码2 最后程序执行会打印出十1-10
//1 2 3 4 5 6 7 8 9 10
#include <stdio.h>
void test()
{
static int i = 0;
i++;
printf("%d ", i);
}
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
test();
}
return 0;
}
上述代码1中,test()
函数中的i
是局部变量,每次调用test()
都需要重新像操作系统申请内存,创建变量i
存储到内存中去,test()
运行结束后,该局部变量i
变量被销毁(将内存归还给操作系统).
上述代码2中,test()
函数中的i
被static
修饰,是静态局部变量,在程序被编译器编译阶段就已经向操作系统申请了一块空间,创建了该静态局部变量,调用test()
后`不会对该变量进行销毁,直至程序彻底结束,才将这块内存归还给操作系统.
注:在这里要简单提一下c/c++中的内存分布
在c/c++中,内存大概分为三块区域:栈区,静态区,堆区
栈区:存放局部变量,形式参数等临时局部变量
特点:进入作用域创建,出作用域即销毁
堆区:有关动态内存分配,相关函数malloc realloc calloc free
静态区:存放全局变量,静态变量
特点:创建好后直至程序结束才被销毁
结论:
static修饰的局部变量,本来应该是存放在栈区,被修饰后,在编译的时候就分配好内存创建好放在了静态区.
static改变了局部变量的存储类型(位置),改变了局部变量的生命周期,该局部变量的作用域仍然不变.但让局部变量出了作用域仍然存在,到程序结束,生命周期才结束.
12.2.2 修饰全局变量
//代码1
//add.c
int g_val = 2023;
//test.c
#include <stdio.h>
extern int g_val;
int main()
{
printf("%d\n", g_val); //2023
return 0;
}
//代码2 编译不通过
//add.c
static int g_val = 2023;
//test.c
#include <stdio.h>
extern int g_val;
int main()
{
printf("%d\n", g_val);
return 0;
}
上述代码1中,在test.c
中使用extern
声明了外部全局变量g_val
,程序运行通过,打印出g_val
的值2023
上述代码2中,add.c
中使用static
修饰了全局变量g_val
,程序运行出现链接性错误,没有运行出来.
结论:
static
修饰全局变量,改变了全局变量的外部链接属性,由外部属性转变了内部链接属性,被static
修饰后,这个静态全局变量只能在当前文件中被使用,不能再其他文件内部使用.
12.2.3 修饰函数
//代码1
//add.c
int Add(int a, int b)
{
return a + b;
}
//test.c
#include <stdio.h>
extern Add(int a, int b);
int main()
{
int a = 1;
int b = 3;
int sum = Add(a, b);
printf("%d\n", sum); //4
return 0;
}
//代码2 编译不通过
//add.c
static int Add(int a, int b)
{
return a + b;
}
//test.c
#include <stdio.h>
extern Add(int a, int b);
int main()
{
int a = 1;
int b = 3;
int sum = Add(a, b);
printf("%d\n", sum);
return 0;
}
使用static
修饰函数和使用static
修饰全局变量一样,static
修改了函数的外部链接属性,将其改为内部链接属性,使得该函数只能在本文件中使用,不可以在其他文件中调用该函数.
12.3 register关键字
建议将变量的值放入寄存器中,只是建议,最终执行还是要看编译器具体怎么编译.
这里的寄存器是计算机的一个存储结构,计算机存储结构大致有下面这些:
寄存器 | 空间以字节为单位,都在计算机中的cpu中 |
缓存 | 几十MB |
内存 | 8G/16G/32G |
硬盘 | 500G/1T |
网盘 | 10T |
13. #define 定义常量和宏
#include <stdio.h>
#define M 300 //define 定义标识符常量
#define ADD(x, y)((x) + (y)) //define 定义宏
//ADD-名字 x, y-参数 ((x) + (y))-宏的实现体
int main()
{
int m = M;
printf("%d\n", m); //300
printf("%d\n", M); //300
printf("%d\n",ADD(m, M)); //600
return 0;
}
编译器预处理时,将这些名字全部替换成了对应后面的数据
14. 指针
14.1 内存
- 什么是内存?
内存(Memory) 是计算机中最重要的部件之一,它是程序与CPU进行沟通的桥梁
计算机中所有程序的运行都是在内存中进行的,因此内存对计算机的影响非常大,内存又被称为主存,其作用是存放CPU中的运算数据, 以及与硬盘等外部存储设备交换的数据。
只要计算机在运行中, CPU就会把需要运算的数据调到主存中进行运算, 当运算完成后CPU再将结果传送出来, 主存的运行也决定了计算机的稳定运行。 - 内存的模型
为了便于理解,将内存想象成一条街道,街道连续排列着一排房子(内存空间),每个房子都有自己的编号(地址),可以根据编号(地址)找到对应的房子(内存空间)内存 一个字节 0xFFFFFFFF 一个字节 0xFFFFFFFE 一个字节 … … … … … … … … … … … … 一个字节 0x00000001 一个字节 0x00000000 - 内存的大小
- 计算机中有地址线(物理上真实存在的电线),电线通过高低电平传送数字信号(高电平传1,低电平传0)
32位电脑内存有32条地址线,理论上最大可以支持4G内存,这里的4G就是通过32条地址线算出来的. - 每根地址线控制地址的每一位,32条地址线一共控制32位
bit
位,计算机又是二进制的,也就是说一共有2^32
个地址,地址使用十六进制表示的,即32位地址范围0x00000000 - 0xFFFFFFFF
- 每个地址对应一块内存空间,规定这一块内存空间占
1Byet
(字节).那2^32
个地址能存储2^32Byte
空间,即4GB
(1GB == 2^10MB == 2^20KB == 2^30Byte)
- 计算机中有地址线(物理上真实存在的电线),电线通过高低电平传送数字信号(高电平传1,低电平传0)
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的.
所以为了有效地使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小时**1个字节
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号称为该内存的地址
int main()
{
int a = 15;
printf("%p\n", &a); //%p 地址占位符,打印出对印书局的第一个字节的地址(较小的地址)
return 0;
}
上面这段代码打印出了a的地址,因为我的电脑是64位的,所以得到的地址是16位十六进制数,每次得到的结果不一定一样.
我这次得到的结果是000000B02E6FFC74
,在调试查看内存窗口输入该地址后,在000000B02E6FFC74
这一行开头存放了a
的值:0f 00 00 00
(int类型占据4字节空间,一条地址存放一字节内容,所以为8位十六进制数字). a
占据了000000B02E6FFC74,000000B02E6FFC75,000000B02E6FFC76,000000B02E6FFC77
这四个内存空间.
还注意到,a
的值的低位存放在内存低地址上,由此得出我的电脑是小端存储.在小端存储中,数据的最低有效字节存
储在最低地址,而最高有效字节则存储在最高地址.
&
操作符取得是该数据的第一个字节的地址(较小的地址),虽然在上述代码中a
占据4个字节,但&a
得到的值是a
第一个字节的地址
- 地址的存储
那么地址该怎么存储,需要定义指针变量来存放地址的值
# include <stdio.h>
int main()
{
int a = 0;
int* p = &a;
*p = 20; //通过 * 解引用操作符 操作指针变量修改指针指向的值
printf("%d\n", a);
return 0;
}
上面代码的变量p
就是指针类型,*
代表指针变量,存放指针的变量,int
代表指针变量p
指向的是int
类型变量.
通过*
解引用操作符,操作指针变量修改指针指向的值
指针 == 地址 == 编号
指针变量 == 变量 == 存放地址的变量
14.2 指针变量的大小
指针变量的大小取决于地址的大小
32位平台下地址是32个bit位(即4个字节)
64位平台下地址是64个bit位(即8个字节)
可以使用sizeof
操作符得到指针变量的大小
#include <stdio.h>
int main()
{
printf("%zd\n", sizeof(char*)); //8
printf("%zd\n", sizeof(int*)); //8
printf("%zd\n", sizeof(short*)); //8
printf("%zd\n", sizeof(double*)); //8
return 0;
}
我的电脑是64位平台,所以得出的结果是8,即一个指针变量占8个字节.如果结果是4,说明电脑是32位平台的.
15. 结构体
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型,将多个类型的变量组合抽象成一个结构体类型.
比如描述学生,学生包含:名字+年龄+性别+学号
这几项信息.
就只能用结构体类型来描述了.
#include <stdio.h>
struct Stu
{
char name[20]; //姓名 一般一个汉字是两个字符即两个字节
int age; //年龄
char sex[5]; //性别
char id[5]; //学号
};
void print1(struct Stu* ps)
{
//结构体指针通过->访问成员变量
printf("name = %s age = %d sex = %s id = %d\n", ps->name, ps->age, ps->sex, ps->id);
return;
}
int main()
{
//结构体初始化
struct Stu s = {"张三", 20, "男", "1001"};
//.为结构体变量访问成员符号
printf("name = %s age = %d sex = %s id = %d\n", s.name, s.age, s.sex, s.id);
//定义结构体指针变量
struct Stu *ps = &s;
//调用打印函数
print1(ps);
return 0;
}
访问结构体变量成员:
结构体变量.成员
结构指针->成员
本章完.