C语言学习第八天

英语:
pointer:指针

回顾:
1.函数
封装=声明+定义
1.1.函数功能:一堆语句的组合,具有独立性,并且完成一定通用功能的代码区
本质目的:简化将来实际开发的工作量
例如:封装的将数据的第n位清0或者置1此类函数
1.2.函数使用三步骤:声明,定义,调用
a)声明:
功能:给自己或者别人使用
语法:extern 返回值数据类型 函数名(形参表);
注意:extern可以不用写,但是不规范
定义在调用前面,可以不用声明
定义在调用后面,如果不写,gcc会给默认的函数声明(不建议)
如果函数没有返回值,返回值数据类型写void
如果函数不需要传递参数,形参表也void
不会分配内存
函数声明时形参表的变量名可以不用写:extern int A(int, int, int, int);
b)定义:
功能:分配一块内存区域用来存函数的代码,给自己或者别人使用
语法:返回值数据类型 函数名(形参表/此变量名必须写/)
{
函数体语句;
return / return 返回值 /exit(0)/没有
}
形式:
void A(void){不加return或者return}
void A(int a, int b /最多四个/){不加return或者return}
int A(void) {必须添加return 返回值}
int A(int a, int b, …) {必须添加return 返回值}

c)调用
功能:使用
语法:函数名(空或者实参表);
注意:函数定义在调用之前,可以不用声明,否则必须声明
如果是普通的变量,实参给形参传递参数本质就是两块内存区域的拷贝赋值
如果是传递数组的首地址,函数通过数组的首地址可以对数组元素可以修改

2.函数和数组的使用公式:
声明操作数组的函数:extern int/void A(int a[], int lenght …);
定义操作数组的函数:int/void A(int a[], int lenght …) {for循环各种操作数组}
操作数组函数的调用:A(数组名, sizeof(数组名)/sizeof(数组名[0]))

3.作用域和可见性
3.1.明确:C语言变量按照数据类型(占内存大小)分:12种(char…double)
3.2.明确:C语言变量按照作用域和可见性分两类:局部变量和全局变量
3.3.局部变量定义:定义在函数内部的变量
例如:
void A (void)
{
int a = 250; //局部变量
}
3.4.全局变量定义:定义在函数之外的变量
例如:
int g_b = 520; //全局变量
void A (void)
{
int a = 250; //局部变量
}
int g_c = 521; //全局变量

7.5.static关键字
如果定义变量时前面加static关键字修饰,表示此变量为静态变量
如果定义变量是前面么有加static关键字修改,表示此变量为非静态变量
例如:
int a = 250; //非静态变量
static int a = 250; //静态变量

7.6.终极结论:最终C语言变量按照作用域和可见性来分,最终分四大类:
局部非静态变量/局部静态变量/全局非静态变量/全局静态变量

7.7.详解局部非静态变量特点:
a)形式1:
void A(void)
{
printf(“a = %d\n”, a); //不可以,报错,gcc编译时报没有定义的错误
int a = 250; //定义局部非静态变量
printf(“a = %d\n”, a); //可以
}

b)形式2:
void A(void)
{
if(1) {
int a = 250;
printf(“a = %d\n”, a); //可以
}
printf(“a = %d\n”, a); //不可以,报错,gcc编译时报没有定义的错误
}

c)函数的形参
void A(int a) //a在整个函数体内都可以用,出了函数不能用

d)局部非静态变量特点
1.此变量使用范围:从定义的地方开始依次向下直到最近的花括号结束
注意:if…else/switch…case/for/while/do-while
2.此变量分配的内存生命周期:从定义的地方操作系统就会给变量分配内存
直到最近的花括号操作系统立马收回变量的内存
只能等下一次运行这些代码才能给变量重新分配内存

7.7.详解局部静态变量特点(钉子户):
a)形式1:
void A(void)
{
printf(“a = %d\n”,a);//不可以,报错,gcc编译时报没有定义的错误
static int a = 250; //局部静态变量
printf(“a = %d\n”,a); //可以
}
b)形式2:
void A(void)
{
if(1) {
static int a = 250; //局部静态变量
printf(“a = %d\n”,a); //可以
}
printf(“a = %d\n”,a);//不可以,报错,gcc编译时报没有定义的错误
}

c)局部静态变量特点:
1.此变量使用范围:从定义的地方开始到最近的花括号结束
2.此变量分配的内存生命周期:从定义的地方开始分配内存直到程序结束
一次分配,程序不结束,下次不会分配,接着上一次的使用
程序不结束,下次就不会重新分配内存
3.由衷建议:少用此类变量

7.8.详解全局非静态变量:
a)形式1:全局非静态变量的定义和使用访问都是在同一个文件
vim var.c
void B(void)
{
printf(“g_a = %d\n”, g_a); //不可以,gcc报没有定义的错误
}

int  g_a = 10; //定义初始化全局非静态变量

void A(void)
{
printf(“g_a = %d\n”, g_a); //可以,函数内部访问全局非静态变量
}

int main(void)
{
A();
B();
printf(“g_a = %d\n”, g_a);
return 0;
}

b)形式2:全局非静态变量定义和使用在不同的源文件中进行
切记:如果一个源文件定了一个全局非静态变量,另一个源文件想直接访问这个全局非静态
变量,只需声明这个全局非静态变量即可
声明全局非静态变量的语法:extern 数据类型 全局非静态变量名;
结论:一旦声明,就可以访问别的文件定义好的全局非静态变量了
vim var1.c 添加如下内容
int g_a = 250; //定义全局非静态变量

//定义A函数
void A(void)
{
printf(“A函数:g_a = %d\n”, g_a); //查看打印全局非静态变量
}

//定义B函数
void B(void)
{
g_a = 520; //修改全局非静态变量
}

vim var2.c 添加如下内容
/定义D函数/
void D(void)
{
printf("%d\n", g_a); //gcc编译报错,没有定义
}

//由于跨文件访问函数,声明函数
extern void A(void);
extern void B(void);

//为了使用var1.c的全局非静态变量,声明它
extern int g_a;
/定义函数C/
void C(void)
{
printf("%d\n", g_a); //可以访问
}
int main(void)
{
A();
B();
A();
printf("%d\n", g_a);
return 0;
}
参考代码:var1.c和var2.c
编译命令:gcc -o var var1.c var2.c //将var1.c和var2.c编译生成一个可执行文件var

c)切记:全局非静态变量特点
1.此变量的使用范围:分两种情况
1.如果是本文件访问,范围是从定义的地方开始依次向下所有的函数都可以访问,
前面的函数无法访问
2.如果是不同文件(跨文件)之间访问,范围是从声明的地方开始依次向下所有的函数都可以 访问,前面的函数无法访问

2.此变量分配的内存生命周期:从启动,运行程序时操作系统就会为其分配内存
直到程序结束,操作系统将其内存回收
3.切记切记切记:全局非静态变量实际开发时少用,慎用,否则极易出现乱序访问效果!
如果非要用,一定要记得互斥访问保护,但是这种互斥保护又降低了
代码的执行效率
等第二阶段课程详解如何互斥保护!
例如:A程序对g_a做++,10万次
B程序对g_a做++,10万次
结果是g_a的值小于20万次,就是乱序访问了!

7.9.详解全局静态变量:
a)形式:全局静态变量的定义和使用访问都是在同一个文件
并且此全局静态变量只能用于本文件,其他源文件不可访问
vim var3.c
void B(void)
{
printf(“g_a = %d\n”, g_a); //不可以,gcc报没有定义的错误
}

static int  g_a = 10; //定义初始化全局静态变量,只能用于var3.c中

void A(void)
{
printf(“g_a = %d\n”, g_a); //可以,函数内部访问全局非静态变量
}

void C(void)
{
printf(“g_a = %d\n”, g_a); //可以,函数内部访问全局非静态变量
}

vim var4.c
extern void A(void); //声明函数A
extern void B(void); //声明函数B
extern int g_a; //声明全局静态变量g_a
int main(void)
{
A();
B();
printf(“g_a = %d\n”, g_a); //gcc报错
return 0;
}

参考代码:var3.c和var4.c
编译:gcc -o var var3.c var4.c
b)全局静态变量特点:
1.此变量使用范围:只能用于定义变量的文件中(var3.c),并且是从定义的地方开始依次向下
所有的其他函数都可以访问,之前的函数不可访问
2.此变量分配内存的生命周期:从启动,运行程序时操作系统就会为其分配内存
直到程序结束,操作系统将其内存回收
3.全局静态变量同样要少用,慎用,如果迫不得已非要访问,记得要互斥访问
但是它比全局非静态变量发生乱序现象的概率要小!

7.10.static关键字终极总结(笔试题必考):
1.static修饰的全局变量只能用于本文件,其余文件不可访问
例如:static int g_data = 250;
2.static修饰的函数(定义函数时)只能用于本文件,其余文件不可调用访问
例如:static int add(int x, int y)
{
return x + y;
}
3.static修饰的变量和函数将来用起来相对比较安全,起到了间接保护的作用
乱序发生的概率降低了
int g_a = 0;
A程序:
for(int i = 0; i < 1000000; i++)
//让B等着:保护
1.先从内存读
2.加1
3.写入内存
//让B运行

       B程序:
             for(int i = 0; i < 1000000; i++)
                  	1.先从内存读
                 2.加1
	3.写入内存

       A程序优先级 < B程序优先级
       抢夺CPU能力 < 抢夺CPU能力
                  CPU->足球

8.指针(C语言的灵魂)
8.1.指针的定义:指针本质就是一个变量,而这个变量永远只能存储一个内存地址(编号)
所以此变量对应的专业术语叫指针变量
通过指针变量保存的地址就可以对这块内存区域任意访问(读查看,写修改)
而指针指向的内存区域可以保存一个数字,而这个数字有数据类型
8.2.指针变量定义的语法格式:
a)书写形式1:
int * 变量名;
例如:int * pa; //定义一个指针变量
b)书写形式2:
int* 变量名;
例如:int* pa; //定义一个指针变量
c)书写形式3:
int *pa; //定义一个指针变量
语义:都是定义一个指针变量,将来这个变量pa能够保存一块内存区域的首地址
并且这块内存区域保存着一个int类型的数据
由于指针变量也是变量,同样需要分配内存空间
问:指针变量占用多大的内存空间呢?
答:得看保存的地址有多大,这个跟计算机硬件相关:
32位系统,一个地址值32位,4字节
64位系统,一个地址值64位,8字节
结论:指针变量分配的内存空间为4字节或者8字节,所以指针变量本身没有数据类型
只是它指向的内存区域保存的数字有数据类型,所以int不是给指针变量用
而是给指针变量指向的内存区域保存的数字用的
d)连续定义指针变量形式:
int *pa, *pb; //定义两个指针变量
int *pa, pb; //pa是指针变量,而pb就是一个普通的int类型变量

e)切记:定义指针变量后如果不初始化,此指针变量保存的一个地址值是随机的
也就是此指针变量指向任意内存区域,相当危险,因为此块内存区域
不是操作系统合法给你分配的内存,此指针变量为野指针!

8.3.指针变量初始化通过取地址&来进行:
int a = 250; //分配4字节内存空间,存储250数字,而这个数字类型为int类型
int *pa = &a; //定义一个指针变量,也就是分配一个4字节内存空间(前提是32位)
保存变量a对应的内存空间的首地址
俗称pa指向a
务必脑子中浮现内存的指向图!

 问:一旦通过指针变量来获取到指向的内存区域的首地址,如何通过指针变量
        对指向的内存区域为所谓欲呢,也就是对指向的内存区域进行读查看或者写修改呢?
 答:通过解引用运算符:*

8.4.解引用运算符(又称取目标运算符):*
功能:就是通过指针变量对指向的内存区域进行读查看或者写修改
语法格式:*指针变量 = 取目标
例如:
char a = 100;
char *pa = &a;
或者:
char a = 100;
char *pa = NULL;
pa = &a;
//打印pa指向a的内存数据
printf("%d\n", *pa); //100
//修改pa指向a的内存数据
*pa = 10; //结果是变量a的内存由原来的100变成10
结论:sizeof(指针变量名) = 4(永远的)

8.5.特殊指针:空指针和野指针
a)空指针:空指针变量保存一个空地址,用NULL表示,其实就是编号为0地址
空指针不可以随意访问,否则造成程序的崩溃!
例如:
int *pa = NULL;
printf(“pa指向的0地址保存的数据为%#x\n”, *pa);
*pa = 250; //向0地址写入数据250
b)野指针:没有初始化的指针变量,它保存着一个随机地址,指向着一块无效的内存
因为这块内存区操作系统并没有给你分配,如果对野指针进行非法访问
也会造成程序的崩溃!
int *pa; //pa就是野指针
printf(“pa指向的0地址保存的数据为%#x\n”, *pa);
*pa = 250; //向0地址写入数据250

c)切记:实际开发代码的编程规范(公式)
如果定义一个指针变量,一开始不清楚它到底指向谁,千万不能不初始化,否则变成了野指针
所以此时要求初始化为空指针NULL,一旦初始化为NULL,将来程序后面使用时
时时刻刻要记得对指针变量进行安全的判断,判断它是否为NULL,如果为NULL,让程序结束
或者函数返回,如果为有效地址,程序才能继续通过指针变量进行操作
例如:
int *pa; //不建议这么写,危险
//安全做法:
int *pa = NULL; //赋值为空指针
if(NULL == pa) {
printf(“pa指向空指针,不能继续访问.\n”);
return -1 或者exit(0); //函数返回或者程序退出
} else {
printf(“pa指向一块有效内存,可以继续访问\n”);
printf("%d\n", *pa);
*pa = 250;
}

//安全做法:
int *pa = NULL; //赋值为空指针
int a = 250;
pa = &a; //让pa指向a
if(NULL == pa) {
printf(“pa指向空指针,不能继续访问.\n”);
return -1 或者exit(0); //函数返回或者程序退出
} else {
printf(“pa指向一块有效内存,可以继续访问\n”);
printf("%d\n", *pa);
*pa = 251;
}

8.6.指针运算(核心并且坑爹)
a)指针可以和一个整数做加减法运算,简称地址运算
切记:计算结果和指针指向的变量数据类型有关系
b)指针计算公式:
1.char型指针+1,表示实际地址+1
例如:
char *pa = 0x1000; //假设pa指向0x1000
pa++; //pa=0x1001
2.short型指针+1,表示实际地址+2
short *pa = 0x1000; //假设pa指向0x1000
pa++; //pa=0x1002
3.int/long型指针+1,表示实际地址+4
long *pa = 0x1000; //假设pa指向0x1000
pa++; //pa=0x1004

8.7.指针和数组的那点事儿(之前都是研究指针和变量的那点事儿)
a)回顾数组相关内容
定义数组:int a[4] = {‘A’, ‘B’, ‘C’, ‘D’};
内存分布图:参见指针和数组.png
结论:
1.数组名就是数组的首地址,同样遵循指针的运算公式
2.&a[2]就是第2个元素的首地址
3.a+2也是第2个元素的首地址
4.&a[2] - a = a + 2 - a = 2个元素,表示第2个元素和第0个元素之间差2个元素
实际的地址差8个字节=2个元素*int
5.目标:a,&a[2],a+2都是地址,干脆定义一个指针变量保存数组的首地址
将来利用指针变量和指针变量的计算公式很轻松即可访问元素

b)指针和数组关系公式:
int a[4] = {1,2,3,4,5};
int *pa = a; //定义指针变量保存整个数组的首地址
此时此刻脑子立马浮现内存图!
1.指针和数组的写法1:
//查看所有元素的值:
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
printf("%d\n", *(p+i));
//将所有元素乘10倍
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
*(p+i) *= 10;

2.指针和数组的写法2:
int len = sizeof(a)/sizeof(a[0]) = 5; //求数组的内存空间大小
//查看所有元素的值:
for(pa = a; pa < a + len; p++)
printf("%d\n", *p);

//将所有元素乘10倍
for(pa = a; pa < a + len; )
*p++ *= 10; //p++:先算p,后做p++

3.指针和数组的写法3:
//查看所有元素的值:
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
printf("%d\n", p[i]);
//将所有元素乘10倍
for(int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
p[i] = 10;
切记:"[]"运算符要经过两步运算:
例如:a[2]经过两步运算:
1.先算地址:a + 2
2.再取值:
(a+2)
所以:p[2]经过两步运算;
1.先算地址:p+2
2.再取值:*(p+2)

c)终极获取元素值的公式: a[i] = p[i] = *(a+i) = *(p+i)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值