高级c语言(二)

计算机的内存长什么样子?

1、计算机中的内存就像一叠非常厚的“便签”,一张便签就相当于一个字节的内存,一个字节有8个二进制位

2、每一张“便签”都有自然排序的一个编号,计算机是根据便签的编号来访问、使用"便签"

3、CPU会有若干个金手指,每根金手指能感知高低电平,高电平转换成1,低电平转换成0,我们常说的32位CPU指的是CPU有32个金手指用于感知电平,并计算出“便签”的编号

便签的最小编号:
00000000 00000000 00000000 00000000 = 0
便签的最大编号:
11111111 11111111 11111111 11111111 = ‭4294967295‬
所以32位CPU最多能使用4Gb的内存

4、便签的编号就是内存的地址,是一种无符号的整数类型

什么是指针:

1、指针(pointer)是一种特殊的数据类型,使用它可以用于定义指针变量,简称指针

2、指针变量中存储的是内存的地址,是一种无符号的整数类型,

3、通过指针变量中记录的内存地址,我们可以读取对应的内存中所存储的数据、也可以向该内存写入数据

4、可以通过 %p 显示指针变量中存储的地址编号

如何使用指针:
定义指针变量

类型* 指针变量名;

int num;
char n;
double d;
int* nump;  //  访问4字节
char* p;    //  访问1字节
double* doublep;    //  访问8字节
long* lp;   //  访问4/8字节

1、一个指针变量冲只记录内存中某一个字节的地址,我们把它当做一块内存的首地址,当使用指针变量去访问内存时具体连续访问多少个字节,指针变量的类型来决定。

2、普通变量与指针变量的用法上有很大区别,为了避免混用,所以指针变量一般以p结尾,以示区分

3、指针变量不能连续定义,一个*只能定义一个指针变量

int n1,n2,n3;   //  n1 n2 n3都是int
int* p1,p2,p3;  //  int *p1,p2,p3  p1是int*  p2 p3是int
int *p1,*p2,*p3; // p1 p2 p3都是int*

4、指针变量与普通一样,默认值是随机的(野指针),为了安全尽量给指针变量初始化,如果不知道该初始化为多少,可以先初始化为NULL(空指针)

int* p; //  野指针
int* p = NULL;  //  空指针
给指针变量赋值:

指针变量 = 内存地址

所谓的给指针变量赋值,其实就是往指针变量中存储一个内存地址,如果该内存地址是非法的,当使用该指针变量去访问内存时会出现 段错误

//  存储堆内存地址
int* p = malloc(4);
​
//  存储指向num所在内存地址(stack\data\bss)
int num;    //  stack
int* p = #  // &num  类型是int*  
注意:num变量的类型必须与p类型相同
指针变量解引用:

*指针变量名;

给指针变量赋值就是让指针指向某一个内存,对指针变量解引用就是根据指针变量中存储的内存编号,去访问该内存,具体连续访问多少个字节由指针变量定义时的类型决定

 int num = 100;
​
    //  定义指针变量
    int* p = NULL;
​
    //  给指针变量赋值
    p = #
​
    //  查看指针变量的值
    printf("%p\n",p);                                        
    
    //  对指针变量解引用
    printf("%d\n",*p + 10);
    *p = 88; 
    printf("%d\n",num);

如果指针变量中存储的是非法的内存地址,当程序运行到该指针变量解引用时,会出现段错误

    //  定义指针变量
    int* p = NULL;
    
    *p = 100;   //  非法访问内存 会段错误
验证指针变量中存储的就是一个整数
#include <stdio.h>                                           
​
void func(unsigned long addr)
{
    *(int*)addr = 88; 
}
​
int main(int argc,const char* argv[])
{
    int num = 10; 
    
    func(&num);
​
    printf("num=%d\n",num);
}
​
为什么要使用指针:
1、函数之间需要共享变量

函数之间的命名空间是相互独立,并且是以赋值的方式进行单向值传递,所以无法通过普通类型形参传参来解决共享变量的问题

全局变量虽然可以在函数之间共享,但是过多地使用全局变量容易造成命名冲突和内存浪费

使用数组是可以共享,但是需要额外传递长度

因此,虽然函数之间的命名空间是相互独立的,但是所使用的是同一条内存,也就是说内存空间是同一个,所以使用指针可以解决函数之间共享变量的问题

#include <stdio.h>

void func(int* p)
{
    printf("func p=%p,*p=%d\n",p,*p);
    *p = 88; 
    printf("func p=%p,*p=%d\n",p,*p);
}

int main(int argc,const char* argv[])
{
    int num = 66; 
    func(&num);
    printf("main &num=%p ",&num);                            
    printf("main:%d\n",num);
}

当函数需要返回两个以上的数据时,光靠返回值满足不了,可以通过指针共享一个变量,借助该输出型参数,返回多个数据

//  put_p输出型参数
int func(int* put_p)
{
    *put_p = 20; 
    return 10; 
}

int main(int argc,const char* argv[])
{
    int num = 0;
    int ret1 = func(&num);
    printf("ret1 = %d ret2=%d\n",ret1,num);                  
}
2、使用指针可以提高函数之间的传参效率

一个指针变量占内存 4 | 8 字节

函数之间传参是以内存拷贝的方式进行,当参数的内存字节数比较大(大于4字节时)的时候,传参的效率就会比较低下,此时使用指针传参可以提高传参效率

#include <stdio.h>                                           

void func(long double* f)
{
}

int main(int argc,const char* argv[])
{
    long double f = 3.14;
    for(int i=0; i<1000000000; i++)
    {   
        func(&f);
    }   
}
3、使用堆内存时,必须与指针变量配合

堆内存无法像栈、数据段、bss段那样给内存取名字,通过标准库、操作系统提供的管理堆内存的接口函数,来操作堆内存时,是直接返回堆内存的地址给调用者,因此必须使用指针变量配合才能访问堆内存

malloc 
realloc
calloc

学习建议:指针就是一种工具,目的是完成任务,而使用指针是有危险性,所以除了以上三种情况需要使用指针以外,不要轻易使用指针

使用指针需要注意的问题:
空指针:

指针变量中存储的NULL,那么它就是空指针

操作系统规定程序不能访问NULL指向的内存,只要访问必定段错误

当函数的返回值是指针类型时,函数执行出错时一般返回NULL,作为函数的错误标志

NULL也可以作为初始值给指针变量初始化

#include <stdio.h>

int* func(void)
{
    return xxx;
    return NULL;    	//表示执行出错                                  
}

int main(int argc,const char* argv[])
{
    int* p = NULL;
    int num= 10;
    p = &num;
    printf("%d\n,",*p);	//	必定段错误
}
如何避免空指针产生的段错误?

来历不明的指针进行解引用前先判断是否是空指针

1、当自己写的函数的参数中有指针类型时,在使用该参数时,需要先判断是否是空指针再使用

2、当使用别人提供的函数时,它的返回值类型是指针类型时,获取返回值后,也需要先判断是否是空指针再使用

int* p = malloc(4);
if(NULL == p)
    printf("内存申请失败\n");
else
    *p = 100;
if(NULL == p) // 正确写法
if(p == NULL)	// 容易漏写= 变成赋值 错误写法
if(!p)	//	绝大多数系统中 NULL 是0,少数系统中是1
{
    //	通用性不够强
}
注意:必须导入 stdio.h 后 NULL才可以使用
野指针:

指针变量中存储的地址,无法确定是哪个地址、是否是合法地址,此时该指针就称为野指针

对野指针解引用的后果:

1、一切正常,刚好指针变量中存储的是空闲且合法的地址

2、段错误,刚好指针变量中存储的是非法的地址

3、脏数据,存储的是其它变量的地址

野指针比空指针的危害性更大

1、空指针可以通过if(NULL==p)判断出来,但是野指针一旦产生,无法通过代码判断,只能通过经验人为判断

2、野指针就算暂时不暴露问题,不代表没有问题,后期可能随时暴露

如何避免产生野指针:

所有的野指针都是人为造成的,因此想要避免野指针的危害,只能通过不人为制造野指针

1、定义指针变量时一定初始化

2、函数不要返回局部变量、块变量的地址,因为当函数执行结束后,该地址指向的内存就会被自动销毁回收,如果非要接收,就接受到了一个野指针

3、与堆内存配合的指针,当堆内存手动释放后,该指针要及时置空

int* p = malloc(4);
*p = 100;
free(p);	// 释放了对堆内存4个字节的使用权,此时p就是野指针
p = NULL;
if(NULL==p)
作业:
1、实现一个函数,用于交换两个int变量的值,并调用它实现一个int类型数据排序函数
#include <stdio.h>

void swap_int(int* p1,int* p2) 
{
    printf("swap:%p %p\n",p1,p2);
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;                                                        
}

int main(int argc,const char* argv[])
{
    int n1 = 10,n2 = 20; 
    printf("main:%p %p\n",&n1,&n2);
    swap_int(&n1,&n2);
    printf("n1=%d,n2=%d\n",n1,n2);
}
2、实现一个函数,用于计算两个正整数的最大公约数和最小公倍数
#include <stdio.h>

int max_min(int n1,int n2,int* min)
{
    if(NULL == min)
    {
        printf("参数有误\n");
        return 0;
    }

    int max = 1;
    for(int i=2; i<=n1; i++)
    {
        if(0 == n1%i && 0 == n2%i)
        {
            max = i;
        }
    }

    for(int i=n1*n2; i>=n1; i--)
    {
        if(0 == i%n1 && 0 == i%n2)
        {
            *min = i;
        }
    }

    return max;
}

int main(int argc,const char* argv[])
{
    int n1 = 3,n2 = 6;
    int min = 0;
    int max = max_min(n1,n2,&min);
    printf("max=%d,min=%d\n",max,min);
}                                                                                                                                                      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值