文章目录
初识C语言
注:如果说化学是一门以实验为基础的学科,那么计算机一定以动手实际敲代码为基础.
我们要会写,也要会看他人写的代码.理解与学习高手写的代码,也是我们进步的重要途径.
细碎的知识点无法做到较为全面的扫盲,因此,作者写了如下知识点脉络。
以供学习者的参考与自己的复习参考。
万丈高楼平地起,在初始C语言部分,我们可以将其看作领略C语言知识库的一扇装饰丰富的大门,亦或是一本宏大书籍的小序.C的魅力要我们在以后的学习中一同领略.
以下代码均是笔者在 VS2019 环境下编译的。
天外有天,作者会在后续学习中不断完善出新。希望保持与读者的交流!
1.什么是C语言
类比人和人交流的语言:汉语、德语、葡萄牙语、法语……
C语言是人和计算机交流的语言,为通用计算机编程语言,广泛用于底层开发(不代表不可用于上层开发,如Linux、早期的WPS)。
人和计算机交流的语言还有:C++、Java、Python……
目前已知计算机已有上千种,C语言经久不衰。
底层开发(应用软件之下的)
为让电脑(硬件)工作起来,我们需要装操作系统(win/Mac/Linux),在系统上可以装上层应用软件(QQ、WeChat)。操作系统和硬件间的驱动层,驱动层将两者关联,操作系统调用驱动层来关联硬件,使其工作。
应用软件(QQ、WeChat) |
---|
操作系统(win/Mac/Linux) |
驱动层 |
电脑(硬件) |
1.1 C语言的发展
二进制序列(如:1001) | ADD | 助记符 |
---|---|---|
编译为可执行程序(二进制) | C语言/C++/Java/Python | 高级计算机语言 |
1.2 ASCII(为统一而生的表)
为了统一做出的努力:
K&R,C语言的创始人。
二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言制定了一套完整的美国国家标准语法,称为ANSI C,作为C语言最初的标准。 [1] 目前2011年12月8日,国际标准化组织(ISO)和国际电工委员会(IEC)发布的C11(C89最早 C90 C89 C99 C11……) 标准是C语言的第三个官方标准,也是C语言的最新标准,该标准更好的支持了汉字函数名和汉字标识符,一定程度上实现了汉字编程。
C语言常见的编译器
Clang GCC WIN-TC SUBLIME MSVC Turbo C等
2.第一个C语言程序
VS 2019:集成开发环境——IDE(创建与使用)
#include <stdio.h>
int main()
//int函数返回类型:整型
//void main()写法古早,不推荐
{
printf("hello beauty!\n");// \n换行,""引起的是字符串
//printf是库函数,函数的使用需要包含头文件stdio.h
return 0;
//C语言习惯,返回0为正常返回,返回非0表示异常返回
}
//写好代码后,ctrl+f5运行(菜单栏:调试:开始执行(不调适))
//VS:编译+链接+运行
//这样写法可以:
int main(void)
//void表示main函数不需要参数
{
return 0;
}
//ctrl+k+c 注释
//ctrl+k+u 取消注释
main函数有且仅有一个,是程序的入口:按键盘上的f10或fn+f10,从main函数进入,逐句执行。
3.数据类型
char | 字符数据类型 |
---|---|
short | 短整型 |
int | 整型 |
long | 长整型 |
long long | 更长的整型 |
float | 单精度浮点型 |
double | 双精度浮点型 |
思维:写代码解决生活中的问题(线上购物时)
线下:货架上选商品,购物车,收银台结账
线上:商品,选商品,购物车,结算
命名规则的部分解析:
35整数;3.5小数
1.234=12.34*10^-1小数点浮动的小数
#include <stdio.h>
int main()
{
printf("%d\n",sizeof(char));//1bit
//%d,打印十进制的整数
printf("%d\n", sizeof(long));//4
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof(short));//2
printf("%d\n", sizeof(float));//4
printf("%d\n", sizeof(double));//8
printf("%d\n", sizeof(long long));//8
printf("%d\n", sizeof(long double));
return 0;
}
//存在这么多类型其实是为了更加丰富的表达生活中的各种值。
//C语言标准规定:long满足sizeof(long)>=sizeof(int)即可,故long:4 byte or 8 byte
- // 后的单位:字节( 1 bit =存储数据的一个空间)
从小到大 | 一些中文名称与单位换算 |
---|---|
bit | 比特 |
byte | 字节 1 byte = 8 bit (字节跳动:ByteDance) |
KB | 1 KB = 1024 byte |
MB | 1 MB= 1024 KB |
GB | 1 GB = 1024 MB |
TB | 1 TB = 1024 GB |
PB | 1 PB = 1024 TB |
… | … |
计算机能够处理的是2进制:
十进制 | 2进制 | 八进制 | 十六进制 |
---|---|---|---|
0-9 | 0-1 | 0-7 | 0-9 、a-f |
//类型的使用:
char ch='w';
int weight=129;
int salary=20000;
4.变量、常量
生活中的有些值是不变的(比如:圆周率,性别,身份证号码,血型等等)。
不变的值,C语言中用常量来表示。
有些值是可变的(比如:年龄、体重、薪水etc)。
4.1定义变量的方法
int age = 150;
float weight = 45.5f;
//类型 变量名=数值
//45.5默认是double类型,加f(45.5f)保证float类型
char ch = 'w';
4.2变量的命名
- 只能由字母(包括大写和小写)、数字和下划线( _ )组成。
- 不能以数字开头。
- 长度不能超过63个字符。
- 变量名中区分大小写的。
- 变量名不能使用关键字
注:变量名尽量取的有意义,避免一直用alphabet的无意义的a-z
4.3变量的分类
4.3.1局部变量(大括号内部的)
void test()
{
int c;
}
int main()
{
int a=10;
return 0;
}
4.3.2全局变量(大括号外部的)
#include <stdio.h>
int main()
int global=2022;//全局变量
{
int local=2011;//局部变量
int global=2020;//局部变量
printf("global=%d\n",global);//2020
return 0;
}
总结:
上面的局部变量global变量的定义其实没有什么问题的。
当局部变量和全局变量同名的时候,局部变量优先使用。
4.4变量的使用
写一个函数计算两个整数的和
#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
//1.输入两个整数
scanf("%d %d",&num1,&num2);
//2.求和
sum=num1+num2;
//3.输出
printf("sum=%d\n",sum);
return 0;
}
4.5变量的作用域和生命周期
int main()
{
{
int a=10;
printf("1:%d\n",a);
}
printf("2:%d\n",a);//报错,a=10出大括号以销毁
return 0;
}
全局变量在任何位置都可以使用,甚至可以跨文件使用。又可被任意改变,因此不安全。
//全局变量
int g_val=2011;
//extern用来声明外部符号
extern int g_val;
int main()
{
printf("%d\n",g_val);
return 0;
}
4.5.1作用域
作用域(scope)是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的而限定这个名字的可用性的代码范围就是这个名字的作用域。
1.局部变量的作用域是变量所在的局部范围。
2.全局变量的作用域是整个工程。
4.5.2生命周期
变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段。
局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
全局变量的生命周期是:整个程序的生命周期。
4.6常量
C语言中的常量和变量的定义的形式有所差异。
C语言中的常量分为以下几种:
- 字面常量
- const 修饰的常变量
- #define定义的标识符常量
- 枚举常量
#include <stdio.h>
enum Sex
{
MALE,
FEMALE,
SECRET//最后一个不需要加 逗号
};//大括号后必须加 分号
//括号中的MALE,FEMALE.SECRET是枚举常量
//枚举常量演示
int main()
{
enum Sex s=MALE;//性别
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
//注:枚举常量的默认是从0开始,依次向下递增1的
return 0;
}
int main()
{
//字面常量演示
3.14;//浮点字面常量
1000;//字面常量
"abcd"//字符串字面值
'@'//字符字面常量
'a'//字符字面常量
}
//const 修饰的常变量
const float pai = 3.14f; //这里的pai是const修饰的常变量
pai = 5.14;//是不能直接修改的!
//例:
int arr[10]={0};//数组
int n=10;
int arr[n]={0};//error
const int num=10;//const修饰常变量,本质是变量
int arr[num]={0};//error
//#define的标识符常量 演示
#define MAX 100
printf("max = %d\n", MAX);
MAX=200;//err
注:上面例子上的 pai 被称为 const 修饰的常变量, const 修饰的常变量在C语言中只是在语法层面限制了变量 pai 不能直接被改变。
pai本质上还是一个变量,所以叫常变量。
5.字符串+转义字符+注释
5.1字符串
"hello beauty.\n"
这样由双引号(Double Quote)引起的一串字符称为字符串字面值(String Literal),或者简称字符串。
注:字符串的结束标志是一个\0的转义字符,不算做字符串内容。
#include <stdio.h>
#include <string.h>
//下面的代码,打印出来的结果是什么?为什么?(突出'\0'的重要性)
int main()
{
char arr1[] = "bit";
// 查看隐藏的\0:f10;菜单栏:调试+窗口+监视
char arr2[] = {'b', 'i', 't'};
char arr3[] = {'b', 'i', 't', '\0'};
printf("%s\n", arr1);//bit
printf("%s\n", arr2);//bit烫烫烫烫烫
printf("%s\n", arr3);//bit
//string lenth
//strlen是库函数,是专门求字符串长度的函数,使用需包含头文件<string.h>
printf("%d\n",strlen(arr1));//3
printf("%d\n",strlen(arr2));//随机值
printf("%d\n",strlen(arr3));//3
return 0;
}
//%s 是打印字符串,遇到\0打印自然结束
5.2转义字符
假如我们要在屏幕上打印一个目录: c:\code\test.c
#include <stdio.h>
int main()
{
printf("c:\code\test.c\n");
return 0;
}
实际上运行结果:
一些转义字符
转义字符 | 释义 |
---|---|
? | 在书写连续多个问号时使用,防止他们被解析成三字母词 早期编译器:??)——>] ??(——>[ |
\` | 用于表示字符常量 |
\" | 用于表示一个字符串内部的双引号 |
\\ | 用于表示一个反斜杠,防止它被解释为一个转义序列符 |
\a | 警告字符,蜂鸣 |
\b | 退格符 |
\f | 进纸符 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | ddd表示1~3个八进制的数字。 如: \130 X |
\xdd | dd表示2个十六进制数字。 如: \x30 0 |
进制转换
123(十进制)
1 | 2 | 3 | |
---|---|---|---|
10^2 | 10^1 | 10^0 | 权重 |
1*10^3 | 2*10^1 | 3*10^0 | sum=123 |
130(八进制转化为十进制)
1 | 3 | 0 | |
---|---|---|---|
8^2 | 8^1 | 8^0 | 权重 |
1*8^2 | 3*8^1 | 0*8^0 | sum=88 |
二进制 找对应 ASC II 表:130这个8进制数字转化为10进制数字,作为ASC II码值,表示的字符就是‘X’
#include <stdio.h>
int main()
{
//问题1:在屏幕上打印一个单引号',怎么做?
//问题2:在屏幕上打印一个字符串,字符串的内容是一个双引号“,怎么做?
printf("%c\n", '\'');
printf("%s\n", "\"");
return 0;
}
读转义字符的题:
//程序输出什么?
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));
// \62被解析成一个转义字符
printf("%d\n", strlen("c:\test\628\test.c"));//14
return 0;
}
5.3注释
代码中有不需要的代码可以直接删除,也可以注释掉
代码中有些代码比较难懂,可以加一下注释文字
比如:
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
/*C语言风格注释
int Sub(int x, int y)
{
return x-y;
}
*/
int main()
{
//C++注释风格
//int a = 10;
//调用Add函数,完成加法
printf("%d\n", Add(1, 2));
return 0;
}
注释有两种风格:
-
C语言风格的注释 /* */ ;缺陷:不能嵌套注释
-
C++风格的注释 // ; 可以注释一行也可以注释多行(推荐)
6.选择语句
实现非黑即白的选择语句时,我们可以使用选择语句。(以下示例为作者喜好编写)
#include <stdio.h>
int main()
{
int coding = 0;
printf("你想要成为rap star吗?(选择1 or 0):>");
scanf("%d", &coding);
if(coding == 1)
{
prinf("坚持,跟马思唯当同事\n");
}
else
{
printf("放弃,做做白日梦就好啦\n");
}
return 0;
}
7.循环语句(while循环)
循环往复的做一件事,如日复一日地坚持“日出而作日落而息”。
int main()
{
int a=1;
int b=1;
while(a<7)
{
b=b-a;
a=a+2;
}
if(7==a)
printf("a=%d\n",a);
return 0;
}
8.函数
#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &num1, &num2);
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
//上述代码,写成函数如下:
#include <stdio.h>
int Add(int x, int y)
//返回类型 函数名 函数参数
{
int z = x+y;
return z;
}//函数体
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &num1, &num2);//不要轻易加\n
sum = Add(num1, num2);
printf("sum = %d\n", sum);
return 0;
}
函数的特点就是简化代码,代码复用。
9.数组
要存储1-10的数字,怎么存储?
C语言中给了数组的定义:一组相同类型元素的集合。
9.1数组定义
int main()
{
//定义
int arr[10];
char ch[5];
int count[3];
//初始化
int arr[10] = {1,2,3,4,5,6,7,8,9,10};//定义一个整形数组,最多放10个元素
int arr1[10]={1,2,3};//不完全初始化,只初始化前三个值,剩余的初始化为0
int arr2[]={1,2,3};
int arr3[];//error
int arr4[10];//不推荐
return 0;
}
C99 (gcc)编译器下可运行的:
int main()
{
int n=0;
int arr[n];
//C99 变长数组,允许数组在创建的时候,数组大小用变量指定,但是这种数组不能初始化
return 0;
}
9.2数组的下标
C语言规定:数组的每个元素都有一个下标,下标是从0开始的。
数组可以通过下标来访问的。
比如:
int arr[10] = {0};
//如果数组10个元素,下标的范围是0-9
9.3数组的使用
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
for(i=0; i<10; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
#include <stdio.h>
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int i=0;
while(i<10)
{
printf("%d",arr[i]);
i++;
}
return 0;
}
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
while(i<10)
{
//输入
scanf("%d",&arr[i]);
i++;
}
int j=0;
while(j<10)
{
//输出
printf("%d",arr[j]);
j++;
}
return 0;
}
10.操作符
10.1算术操作符
+(加) -(减) *(乘) %(取余or取模) /(除) |
---|
10.1.1关于除法运算
int main()
{
printf("%d\n",8/2);//得4
printf("%d\n",7/2);//得3
//为何出现值不是3.5?
//除号两端为整数,故计算结果为整数
//数学计算来说:7/2=3...1(3余1)
//令结果位小数:
printf("%lf\n",7/2.0);//得3.500000
//注意:d变为lf,2变为2.0
//若想仅得到3.5:
printf("%.1lf\n",7/2.0);//得3.5
//注意:lf变为.1lf,表示小数点后保留1位小数
//同理,也可写为:
printf("%.1lf\n",7.0/2);//也得3.5
return 0;
}
10.1.2关于取模(取余)运算
a.不能整除
int main()
{
printf("%d\n",7%2);//得1
//数学运算:7/2=3...1(3余1)
return 0;
}
b.能整除
int main()
{
printf("%d\n",8%2);//得0
//数学运算:8/2=4...0(4余0)
return 0;
}
c.写判断能否整除的代码(详细见作者初始C语言刷题记录)
10.2移位操作符
>> << (二进制位的移动运算) |
---|
int a=1 |
---|
4字节=32bit |
00000000 00000000 00000000 00000001(1的二进制序列)(移位的对象) |
10.3位操作符(位:二进制位)
& | 按位与 |
---|---|
^ | 按位异或 |
| | 按位或 |
10.4赋值操作符
= 复合赋值符:+= -= *= /= &= ^= |= >>= <<= |
---|
//一些举例
a = a + 5;
a += 5;
a = a-3;
a -=3;
10.5单目操作符(只有一个操作数的操作符)
! | 逻辑反操作(假变真,真变假,真假转换) |
---|---|
- | 负值 |
+ | 真值 |
& | 取地址(常运用于指针) |
sizeof | 操作数的类型长度(以字节为单位);不是函数 |
~ | 对一个数的二进制按位取反 |
– | 前置(先–后使用)、后置– |
++ | 前置(先++后使用)、后置++ |
* | 间接访问操作符(解引用操作符) |
(类型) | 强制类型转换 |
在C语言中如何表示真假?
0表示假,非0表示真。
10.5.1 !用法举例
a、基础理解
int main()
{
int a=0;
scanf("%d",&a);//5
printf("%d\n",a);//5
printf("%d\n",!a);//0
return 0;
}
int main()
{
int a=0;
scanf("%d",&a);//0
printf("%d\n",a);//0
printf("%d\n",!a);//1,语法规定
return 0;
}
b、实例
int main()
{
int flag=0;
if(flag)//flag为真,打印HI
{
printf("HI\n");
}
if(!flag)//flag为假,打印FINE
{
printf("FINE\n");
}
return 0;
}
10.5.2 -用法举例
int main()
{
int a=-10;
int b=-a;
printf("%d\n",b);
return 0;
}
10.5.3 sizeof相关知识点讲解
int main()
{
int a =0;
printf("%d\n",sizeof(a));//4字节,建议写法
printf("%d\n",sizeof a );//4字节,说明sizeof不是函数,是操作符
//或者写成
printf("%d\n",sizeof(int));//4字节
printf("%d\n",sizeof int );//error
return 0;
}
与 strlen 的区分
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[]="abc";
//a b c \0
char arr2[]={'a','b','c'};
//a b c
printf("%d\n",strlen(arr1));//3
printf("%d\n",strlen(arr2));//15(随机数)
printf("%d\n",sizeof(arr1));//4
printf("%d\n",sizeof(arr2));//3
return 0;
}
a. srtlen
- 库函数
- 用来求字符串长度(关注的字符串中是否有\0,统计\0之前出现的字符个数)
b. sizeof
操作符
只关注内存空间的大小:占据了多大的内存空间
int main()
{
int arr[10]={0};
printf("%d\n",sizeof(arr));//40,计算的是数组的总大小,单位是字节
printf("%d\n",sizeof(arr[0]));//4,
printf("%d\n",sizeof(arr)/sizeof(arr[0]));//计算数组的元素个数:40/4=10
return 0;
}
10.5.4 前置or后置–、++
int main()
{
int a =10;
int b=++a;//前置++,先使用,再++ b=a,
//a = a + 1, b = a
int c=a++;//后置++,先使用,后++
//c = a,a = a + 1
printf("a=%d b=%d c=%d\n",a,b,c);
int b=--a;
//a=a-1,b=a
int b=a--;
//b=a,a=a-1
return 0;
}
10.5.5 强制类型转换
int main()
{
//int double
int a=(int)3.14;
printf("%d\n",a);//强制转化为整型,3.14——>3
return 0;
}//强制类型转换并非所有情况下都使用
10.6关系操作符
> | |
---|---|
>= | |
< | |
<= | |
!= | 用于测试“不相等”;注意与数学符号的区分 |
== | 用于测试“相等“。=:赋值;==:判断。 |
int main()
{
int age=10;
if(18<=age<=35)//错误写法
//该语句的逻辑(0<=35) 18<=10逻辑为假得结果:0
//改写
if(age>=18 && age<=35)
{
printf("青年\n");
}
return 0;
}
10.7逻辑操作符
&& | 逻辑与(并且) |
---|---|
|| | 逻辑或(或者) |
10.7.1代码举例(并且 or 或者)
int main()
{
int a=0;
int b=5;
if(a && b)//并且
{
printf("allure\n");//由0,不执行该语句
}
if(a||b)//或者
{
printf("allure\n");//打印
}
}
10.8条件操作符(三目操作符)
exp1? exp2: exp3(表达式1为真,运行表达式2;表达式1为假,运行表达式3) |
---|
10.8.1在循环语句中的等价写法举例(比大小,取较大值)
int main()
{
int a=0;
int b=0;
int c=0;
if(a>b)
m=a;
else
m=b;
}
等价于:m=(a>b?a:b);
10.9逗号表达式
exp1 , exp2 , exp3 , … expN |
---|
10.9.1(从左往右依次计算)
int main()
{
int a=3;
int b=0;
int c=4;
int d=(a=b-3,b=a+c,c=a-b,a=c+3);
// a=-3 b=1 c=-4 a=-1
printf("%d\n",d);//-1
return 0;
}
10.10下标引用、函数调用和结构成员
[ ] :下标引用操作符 ( ) :函数调用操作符 结构体中使用: . -> |
---|
10.10.1 下标引用操作符 和 函数调用操作符 的 代码介绍
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%d\n",arr[7]);//[]就是下标引用操作符,arr,7
return 0;
}
//函数调用操作符
int Add(int x,int y)
{
return x+y;
}
int main()
{
int a=10;
int b=20;
int c=Add(a,b);//( )就是函数调用操作符
return 0;
}
11.常见关键字
auto自动 break停止、default (switch case语句中,实现循环)for、while、do char、short、int、long 、double、float (const,类型里面修饰变量) extern(声明外部符号) goto(可用来实现分支也可用来实现循环) sizeof 计算类型的大小 static修饰变量 struct结构体 、enum 枚举(自定义类型)、union联合体/共用体(自定义类型) continue 继续 if、else(实现分支) typedef 类型重定义unsigned 无符号的 signed 有符号的 void 无类型/空类型(函数返回值、函数参数)volatile return(函数中返回)register(寄存器关键字) |
---|
本质上所有局部变量都是auto(自动)变量:自动创建自动销毁。
//对unsigned的一些解释
int main()
{
int a=10;
unsigned int num=-10;//unsigned将放入的数值当作正整数处理
return 0;
}
注:C语言提供了丰富的关键字,这些关键字都是语言本身预先设定好的,用户自己是不能创造关键字的。
11.1 register 寄存器关键字
寄存器:计算机上的一种存储设备
字节 | 寄存器<=>CPU(集成,相互传输) |
---|---|
xx M | 高速缓存(cache) |
8 G / 16 G | 内存 于 CPU(中央处理器)相互传输数据(早期读写) |
500 G | 硬盘 |
2 T | 网盘 |
上述,由下往上:
速度:加快 | 占用空间:变小 |
---|
int main()
{
//register起建议作用,并非决定性作用
register int num=10;
return 0;
}
//现在编译器自动判断是否将数据加入寄存器
11.2 关键字typedef(类型重命名)
typedef unsigned int uint_32;//将unsigned int重命名为的uint_32也是一个类型名
int main()
{
//num1和num2,这两个变量的类型是一样的
unsigned int num1=0;
uint_32 num2=0;
return 0;
}
typedef struct Node
{
int data;
struct Node* next;
}Node;
11.3关键字static(修饰变量和函数,可以看成对程序或语句的一些保护)
void test()
{
int a=1;
//上行改为:static int a=1;
//被static修饰后,局部变量不会被销毁
a++;
printf("%d",a);
}
int main()
{
int i=0;
while(i<10)
{
test();
i++;
}
return 0;
}
//原本运行结果10个2
//修改后运行结果:2 3 4 5 6 7 8 9 10 11
内存划分区域:
栈区 | 局部变量、函数的参数(进入范围创建,出范围销毁) |
---|---|
堆区 | 动态内存的分配、malloc、calloc、realloc、free |
静态区 | 静态变量、全局变量 |
static,静态变量的生命周期就是整个程序的生命周期,在程序结束后静态变量才回收空间。
static修饰局部变量的时候,改变了变量的存储类型,普通的局部变量储存在栈区,被static修饰的变量存储在静态区。
存储在静态区的变量,出了作用域不会销毁,依然存在。
总结:
- 生命周期:变长
- 作用域:没变
11.3.1修饰局部变量(静态局部变量)
#include <stdio.h>
void test()
{
int i=0;
//上句修改为: static int i = 0;
i++;
printf("%d",i);
}
int main()
{
int i=0;
for(i=0;i<10;i++)
{
test();
}
return 0;
}
结论:
static修饰局部变量改变了变量的生命周期。
让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。
11.3.2修饰全局变量(静态全局变量)
//源文件下两个文件:add.c和test.c
//add.c
int g_val = 2018;
//修改为:static int g_val = 2018;
//test.c
extern int g_val = 2018;
//声明外部函数
int main()
{
printf("%d\n", g_val);
return 0;
}
修改后代码在编译的时候会出现连接性错误。
过程:
编译+链接:生成可执行文件 exe
结论:
一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。
解释:
全局变量具有外部链接属性,static修饰全局变量的时候,由外部连接属性改为了内部连接属性。
这个全局变量只能在自己所在的.c文件中看到,其他源文件看不到了。
11.3.3修饰函数(静态函数)(类似于static修饰全局变量)
//源文件下两个文件:add.c和test.c
//add.c
int Add(int x, int y)
//上述语句修改为:static int Add(int x, int y)
{
return c+y;
}
//test.c
extern int Add(int x, int y);
//声明外部函数
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}
修改后代码在编译的时候会出现连接性错误。
结论:
一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。
解释:
函数本身是具有外部连接属性,如果static修饰函数,函数的外部链接属性就变成了内部链接属性,这个函数只能在自己所在的源文件内部使用,其他源文件无法使用该函数。
static使用场景:
避免源文件下不同文件对相同定义赋予不同值造成的使用上的冲突。
使用较少。视情况而定。
12. #define可以定义常量和宏
#define和#include为预处理指令
12.1 define定义标识符常量
#include <stdio.h>
//运用在数组中
#define SIZE 10
//名称,如SIZE可自行修改
int main()
{
int arr[SIZE];
printf("%d\n",SIZE);
return 0;
}
12.2 define定义宏(宏是有参数的,和函数很像)
int Add(int x,int y)
{
return x+y;
}
int main()
{
int a=10;
int b=20;
int sum1=Add(a,b);
printf("sum1=%d\n",sum1);
return 0;
}
//写成宏
#define ADD(x, y) ((x)+(y))
//注意:ADD全大写
int main()
{
int a=10;
int b=20;
int sum1=Add(a,b);
printf("sum1=%d\n",sum1);
int sum2=((a)+(b));
printf("sum2=%d\n",sum2);
return 0;
}
//另一个例子
#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;
}
12.2.1宏和函数近距离对比(后续博客会更新详细版)
int Add(int x,int y) |
---|
#define ADD (x,y) ((x)+(y)) |
13.指针
13.1内存
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存(8G、12G、32Getc)中进行的。
所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
变量是创建内存中的(在内存中分配空间的),每个内存单元都有地址,所以变量也是有地址的。
32位机器上有32根地址线,通电进行0/1变换。
内存单元的编号——>地址——>指针
编号=地址=指针
13.1.1取出变量地址如下:
#include <stdio.h>
int main()
{
int num = 10;
#//取出num的地址
//注:这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)
printf("%p\n", &num);//打印地址,%p是以地址的形式打印,显示16进制数字
//看监视器:调试(D)——>窗口(w)——>内存(M)
return 0;
}
二进制到16进制(转化后倒放)
十六进制:0——9、a——f | |
---|---|
int a=10; | 在内存中存储十进制的10 |
00000000 00000000 00000000 00001010 | 32位二进制中表现形式 |
0x 00 00 00 0a | 转化后的16进制表现形式 |
0a 00 00 00 00 | 显示取地址后的形式 |
13.2定义指针变量
地址如何存储?——>需要定义指针变量。
int num = 10;
int *p;//p为一个 整型 指针变量
//*说明p为指针变量
//int说明p指向对象为int型
p = #//&取地址操作符
//p指针变量对应的4个字节以首地址的形式表示出来
13.2.1指针的使用实例(重要的基础知识):
#include <stdio.h>
//指针,本质是地址
//口头语中,说的指针是指:指针变量
int main()
{
int num = 10;
int *p = #//&取地址操作
*p = 20;//*解引用操作
return 0;
}
13.3指针变量的大小
要想知道“指针变量有多大?”
就要知道指针变量存储的是什么?——>地址
故,指针变量的大小取决于地址的大小。
- 32位机器 — 一个地址是32个二进制位,存储需要32个bit位的空间,所以32位机器上,指针变量的大小是4个字节
- 64位机器 — 一个地址是64个二进制位,存储需要64个bit位的空间,所以64位机器上,指针变量的大小是8个字节
- 最终取决于编出的程序是32位或64位(打开任务管理器)
#include <stdio.h>
int main()
{
printf("%zu\n", sizeof(char *));
//zu专门用来打印sizeof的返回值
printf("%zu\n", sizeof(short *));
printf("%zu\n", sizeof(int *));
printf("%zu\n", sizeof(double *));
//4(x86环境)
//8(x64环境)
return 0;
}
结论:指针大小在32位平台是4个字节,64位平台是8个字节。
13.4代码举例
void test(double* pd)
{
*pd=5.6;
}
int main()
{
double d=3.14;
test(&d);
double*pd=&d;
*pd=5.6;
//而不是简单的:d=5.6;
printf("%lf\n",d);
return 0;
}
14.结构体
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如描述学生,学生包含:名字+年龄+性别+学号 这几项信息(只能使用结构体来描述)。
代码举例:
struct Stu
{
char name[20];//名字
int age; //年龄
char sex[5]; //性别
char id[15]; //学号
};
结构体的初始化:
void print(struct Stru*ps)
{
printf("%s %d %s %s\n",(*ps).name,(*ps).age,(*ps).sex,(*ps).id); //结构体变量.结构体成员
printf("%s %d %s %s\n",ps->name,ps->age,ps->sex,ps->id); //结构体指针->结构体成员
}
int main()
{
//打印结构体信息
struct Stu s = {"小李", 20, "男", "20220114"};//将类型实例化变为结构体变量
//输入
scanf("%s %s %s %s",s.name,s.sex,s.id,&(s.age));
printf("name = %s age = %d sex = %s id = %s\n", s.name, s.age, s.sex, s.id);//.为结构成员访问操作符
printf(&s);
//struct Stu *ps = &s;
printf("name = %s age = %d sex = %s id = %s\n", ps->name, ps->age, ps->sex, ps- >id);//->操作符
return 0;
}
#include <stdio.h>
int main()
{
printf("%zu\n", sizeof(char *));
//zu专门用来打印sizeof的返回值
printf("%zu\n", sizeof(short *));
printf("%zu\n", sizeof(int *));
printf("%zu\n", sizeof(double *));
//4(x86环境)
//8(x64环境)
return 0;
}
13.4代码举例
void test(double* pd)
{
*pd=5.6;
}
int main()
{
double d=3.14;
test(&d);
double*pd=&d;
*pd=5.6;
//而不是简单的:d=5.6;
printf("%lf\n",d);
return 0;
}
14.结构体
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如描述学生,学生包含:名字+年龄+性别+学号 这几项信息(只能使用结构体来描述)。
代码举例:
struct Stu
{
char name[20];//名字
int age; //年龄
char sex[5]; //性别
char id[15]; //学号
};
结构体的初始化:
void print(struct Stru*ps)
{
printf("%s %d %s %s\n",(*ps).name,(*ps).age,(*ps).sex,(*ps).id); //结构体变量.结构体成员
printf("%s %d %s %s\n",ps->name,ps->age,ps->sex,ps->id); //结构体指针->结构体成员
}
int main()
{
//打印结构体信息
struct Stu s = {"小李", 20, "男", "20220114"};//将类型实例化变为结构体变量
//输入
scanf("%s %s %s %s",s.name,s.sex,s.id,&(s.age));
printf("name = %s age = %d sex = %s id = %s\n", s.name, s.age, s.sex, s.id);//.为结构成员访问操作符
printf(&s);
//struct Stu *ps = &s;
printf("name = %s age = %d sex = %s id = %s\n", ps->name, ps->age, ps->sex, ps- >id);//->操作符
return 0;
}
15.函数如果不写返回值:
函数默认会返回一个值。
有些编译器上返回的是:最后一条指令产品的结果。
但这种写法本身是不合规定的