【c语言跟练】笔记6-指针

文章详细介绍了C语言中的指针概念,包括地址、取地址运算符、指针变量的定义与使用,以及指针在数组、函数参数传递中的应用。同时,讲解了动态内存分配的malloc和free函数,强调了内存管理的重要性和注意事项。
摘要由CSDN通过智能技术生成

引言

地址和int是一个数据类型么?

int i;
// 不同数据类型占绝内存大小(64架构--编译器)
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof(double));//4
printf("%lu\n", sizeof(&i));//64位8,32位4

注意:不同架构下,地址的数据类型与int不一定相同,所占内存也不一定相同

int i;
int j;
j = (int)&i;
printf("0x%x\n", &i);
printf("0x%x\n", j);

变量的地址

格式化输入/输出

scanf("%d",&i);//将值输入i的地址中,即给变量i赋值
printf("0x%x\n", &i);//输出变量i的地址
printf("%p\n", &i); 
//输出地址的占位符,但是我的编译器输出和%x不一样

运算符&

取地址:&……必须是变量

注意:没有地址的东西不能取地址

&(a+b);//❌
&(a++);//❌
&(++a);//❌

相邻变量的地址

int i;
int j;
printf("i地址:0x%x\n", &i);
printf("j地址:0x%x\n", &j);

结果:

  • i地址:0xbd0ff544

  • j地址:0xbd0ff564

分析:

  • 先定义,地址小

  • 相邻差16(十进制),即一个字节

数组与地址

int a[5];
printf("0x%x\n", &a); //数组的地址
printf("0x%x\n", &a[0]); //数组第一个单元的地址
printf("0x%x\n", &a[1]); //数组第二个单元的地址

结果:

  • 0x677cf8d8

  • 0x677cf8d8

  • 0x677cf8dc

分析:

  • 数组地址即是第一个单元的地址

  • 同一数组相邻单元地址差4——一个字节??一个字节是8比特,8位二进制,2位四进制

第九章 指针

1.指针的定义

1.取地址

指针:用于保存地址的变量

格式:<int><*><变量名>,变量名一般用p(point)

  • 注意以下两种都是定义了一个指针变量p和一个整型变量q

  • *是跟着变量,有空格没有空格都行,不是跟着*

int*p,q;
int* p,q;

总结:

  • 整型变量 与 指针变量

定义变量

变量调用

变量作用

整型变量

int x

x

存储数值

指针变量

int *p 或 int* p

p

存储地址

  • 运算符 & 与 *

  • &:取变量的地址

  • *:单目运算符,用于定义指针变量

指针作为参数传递给函数

必须给地址,不能给变量!

#include <stdio.h>
void f(int* p);
int main()
{
    int i;
    printf("0x%x\n", &i);
    //验证函数参数的传递
    f(&i);
    int* p = &i;
    f(p);

    return 0;
}

void f(int *p)
{
    printf("0x%x\n", p);
}

2.根据地址取变量

int k = *p;
*p = k + 1;
int i = 6;
int* p = &i;
int k = *p;

printf("0x%x\n", &i); //访问到了地址0x808ff864
printf("0x%x\n", p); //访问到了地址0x808ff864 ※※※※※※
printf("0x%x\n", *p); //访问到了变量的值6 ※※※※※※
printf("0x%x\n", k); //访问到了变量的值6
  • 定义了一个指针变量 p,int *p

  • p值为地址,*p为从地址进入调取的变量的值

总结:
int i = 26;//定义了一个整型变量i,值为26
int*p;
p = &i;//定义了一个指针变量p,值为变量i的地址
int k = *p;//定义了一个整型变量,值为从p地址指向变量的值,即i的值
*p = 32;//变量i的值变为32

其他:

scanf("%d",i);//❌,但有时候不能报错!!!

2.指针的使用

课堂练习1:交换两个变量的值

#include <stdio.h>
void Swap(int* p, int* q)
{
    //printf("a = %d, b = %d", a, b); 
    //↑ 编译报错,因为a,b不进入这个变量空间
    int ret = *p;
    // 根据地址交叉赋值
    *p = *q;
    *q = ret;
}
int main()
{
    int a = 5;
    int b = 6;
    printf("a = %d, b = %d\n", a, b);
    Swap(&a, &b);
    printf("a = %d, b = %d\n", a, b);

    return 0;
}

课堂练习2:有多个返回值——判断数组单元的最大值和最小值

#include <stdio.h>
void MinMax(int home[], int len, int* min,int* max);

int main()
{
    int a[10] = { 1,2,3,4,5,6,7,8,9,10 }; //①注意这里数组要设置范围即a[number],不然运行会一直跑到天荒地老 修正!!这里可以用int a[]或int a[ ],直接指明number运行速度会更快
    int i = 0;
    int j = 1;
    int* p = &i; //最小值的位置 //②注意这里要先给指针变量一个地址,而不是随便就定义了。有点像int i;但此时i的数值不定
    int* q = &j; //最大值的位置
    MinMax(a, sizeof(a) / sizeof(a[0]), p, q); //③注意这里数组作为参数,用a,而不是a[];另外指针要传递p,而不是*p,因为*p是变量值,p是地址,要传递地址
    printf("min = %d, max = %d", *p, *q);

    return 0;
}

// 函数思路是不断刷新某个地址的值,直到所有数遍历完
// 偏个题,有点像遍历循环构建素数表,但比那个简单
void MinMax(int home[], int len, int* min, int* max)
{
    *min = home[0];
    *max = home[0];
    int i;
    for (i = 0; i < len; i++)
    {
        if (home[i] < *min) {
            *min = home[i];
        }

        if (home[i] > *max) {//④这里不能接上面用else,因为还有个区间范围是min~max
            *max = home[i];
        }
    }
}
// 老师程序的简洁性
// ①main*max =中定义指针变量
int min,max;
MinMax(a, sizeof(a) / sizeof(a[0]), *min, *max); 

// ②Swap函数中定义两个地址最初的值
*min = *max = home[0];

课堂练习3:不仅返回值,还返回状态——判断除法是否成功

  • 具体分工是:函数返回值返回运算状态,结果由指针完成

  • 返回值一般不在有效范围【有效指什么】内,通常为0或-1

  • 当任何返回值都有效【有效指什么】时,可能要分开返回【分开返回什么】

  • 后续语言,可以通过异常机制解决

#include <stdio.h>
int Judge(int a, int b, int* point);

int main()
{
    int a = 65;
    int b = 67;
    int result;
    if (Judge(a, b, &result)) {
        printf("可以作除法。\n");
        printf("%d / %d = %d\n", a, b, result);
    }

    return 0;
}

int Judge(int a, int b, int* point)
{
    int ret = 1;
    if (b == 0) {
        ret = 0;
    }
    else {
        *point = a / b;
    }
    return ret;
}

常见错误:

定义了一个指针变量,却没有指定让它志向哪个变量

// 以下是正确程序:
int i = 0; //定义了一个整型变量,计算机自动给它分配了一个地址
int *p;
p = &i; //指针变量的值为i的地址

// 以下是错误程序:❌❌,没有指定地址
int *q;
*q = 6;

3.指针与数组

1.数组作为函数传递参数

  • 普通变量int x:传递进值

  • 指针变量int *p 或 int* p:传递进值(地址)

  • 数组int a[]或 int *a: 传递进一串值,注意必须同时给出长度

为什么不能在函数体内算出数组的长度,而要同时给出?

为什么要在函数参数定义时写空的方括号,写数字也没用?

原因:int a[] ,数组是特殊的指针!

#include <stdio.h>
void MinMax(int home[], int len);

int main()
{
    int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int i,  j;

    printf("main sizeof(a) = %d\n", sizeof(a));
    printf("main sizeof(a[0]) = %d\n", sizeof(a[0]));
    printf("main a = 0x%x\n", a);

    MinMax(a, sizeof(a) / sizeof(a[0]));
    printf("main a[2] = %d\n", a[2]);

    return 0;
}

void MinMax(int home[], int len)
{
    printf("MinMax sizeof(a) = %d\n", sizeof(home));
    printf("main sizeof(a[0]) = %d\n", sizeof(home[0]));
    printf("MinMax a =0x%x\n", home);
    home[2] = 8;
    printf("MinMax a[2] = 0x%d\n", home[2]);
}

结果:

分析:

  • 由于变量只能存在于变量空间,推测函数中数组作为参数传递的是地址,给出长度,挨个读取

  • 主函数中数组a占据40个字节,可推测有10个单元

  • 自定函数中数组a占据8个字节,可推测有2个单元??

SUM1 数组/数组单元的格式化输出

// 检查当前编译器int和地址占绝内存大小
printf("sizeof(int) = %d\n", sizeof(int));
//结果4
printf("sizeof(&i) = %d\n", sizeof(&i));
//结果8

// 输出数组和数组单元占据内存
printf("main sizeof(a) = %d\n", sizeof(a)); // %d 输出数组a占据的内存
//结果40
printf("main sizeof(a[0]) = %d\n", sizeof(a[0]));// %d 输出数组a中单元a[0]占据的内存
//结果4

// 输出数组的地址/值
printf("main a = 0x%x\n", a); // %x 输出数组a的地址
//结果0x3b78f7e8
printf("MinMax a[0] =0x%x\n", a[0]);// 输出的是数组a中单元a[0]的值
//结果0
printf("MinMax a[0] =0x%x\n", &a[0]);// %x 输出数组a中单元a[0]的地址
//结果0x3b78f7e8
printf("MinMax a[1] =0x%x\n", &a[1]);// %x 输出数组a中单元a[0]的地址
//结果0x3b78f7ec 差了4,???这不是4个字节呀老师为什么之前说4个字节

SUM2 数组/指针的定义和使用

// 函数调用
MinMax(a, sizeof(a) / sizeof(a[0]),&i);
//注意数组作为参数输入,是a,不是a[]
//注意指针作为参数输入,是地址p,不是*p(指向变量的值)

// 函数定义
void defFunc(int home[], int len, int *p);
//注意数组作为参数输入,定义写a[]
//注意指针作为参数输入,定义写*p

// 注意
// 数组定义 
int a[]; // ❌错误
// 数组作参
void f(int a[num]); // ❌错误

SUM3 数组/整数作为函数原型的多种等价方式:

//数组做参数
void defFunc(int a[], int len);
void defFunc(int *a,int len);
void defFunc(int [],int len);
void defFunc(int *,int len);

//整数做参数
void defFunc(int *,int len);
void defFunc(int *,int);

//疑————惑
void defFunc(int *,int len);
//这种情况,如果直接使用比如home[],会报错,不知道home是谁
//所以我的问题是:这种情况如何调用传递的参数???
//以上问题的解答:注意是原型中使用等价,而不是函数定义!!!!

2.指针作为数组

int min= 0;
int* p;
p = &min;
printf("*p = %d\n", *p);//p[0]就是min
printf("p[0] = %d\n", p[0]);//p[0]就是min

SUM4 数组与指针

  • 数组变量本身表达地址:a

int a[10];
int *p = a;
  • 数组的单元表达的是变量:a[0]

  • 地址:&a[0] = a

  • 值: *a = a[0]

int a[10];
a = &a[0];
a[1] = 2;
  • []运算符可以对数组做,也可以对指针做

//[]--数组
int a[10];
//[]--指针
int *p;
printf("%d",p[0]);//指针只有角标为0的单元
  • *运算符可以对指针做,也可以对数组做

//*--指针
int *p;
//*--数组
int a[10];
*a = 25;//表示数组a,哪个变量啊???????
  • 数组变量是const 的指针,不能再被赋值

int a[10];
int *q = a;// 可以
int b[] = a;// ❌不可以

其他:

int a[];// ❌不能直接定义这样一个数组
int a[10];
int a[] = {1,5,8,7,8,9,4};

int a[][5];// ❌不能直接定义这样一个数组
int a[3][5];
//int a[][5] = {????; 挖个坑:可以不知名行数,但要指明列数是什么来着???????

4.指针与const

1.指针是const:p值不能改,其他都可以

表示一旦得到某个地址,不可再被更改

int *const p = &i;//p的值就是i的地址了,是此时的地址,还是时时刻刻i的地址呢??????
*p = 25; // OK:对p指向的变量改变了值,也就是i的值改了
p++;// ❌因为p的值不可再更改

2.所指是const:*p不能从p去改对应得值,其他都可以

表示不能通过指针,去需改指向的值

注意:没有说这样会导致i本身是不可修改得,除非i是const

const int *p = &i;
*p = 25;// ❌不可以通过指向去改值
i = 25;// OK:i值可以改,因为i不是const
p = &j;// OK:p值也可以改
*p = 25;// 这回呢,从p改j??????
int a = 15;
f(&a); //OK

const int b = a;
f(&b); //OK

SUM1 判断以下表述的意思

int i;
int const *p;
const int *p;
int *const p;

技巧:观察*与const的位置,const在前说明所指是const,const在后说明指针是const

3.类型转换

应用:函数指针参数保护

void f(const int* p); // 函数声明:声明指针传进函数,函数体内的代码不会改变指向变量的值
void h(const int a[]);

4.数组与const

数组:int a[]; --> int const a,其中a是const,应用就是不能两个数组直接赋值

const int a[] = {1,2,3,5,4};
// 说明每个单元都是const,必须通过初始化赋值

5.指针运算

    • 指针+ -

// 数组:同一数组指针变量+1时的地址变化
int a[] = { 0,1,2,3,4,5 };
printf("0x%x\n", &a[0]);//&a[1]
// 结果0xec4ffbb8
printf("0x%x\n", &a[1]);//&a[1]
// 结果0xec4ffbbc

int* p = a;
printf("%d\n", *p);// 指针p指向a[0]
// 结果0
printf("0x%x\n", p); //p指为&a[0]
// 结果0xec4ffbb8

p++;
printf("%d\n", *(p+1));// 指针p指向a[1]
// 结果1
printf("0x%x\n", (p+1));//p指为&a[1]
// 结果0xec4ffbbc
// 数组:不同数组指针变量+1时的地址变化(值变化:+4/1,含义变化:指向下一个单元)
char ac[] = { 0,1,2,3,4,5 };
char* p = ac;
printf("0x%x\n", p); // 0x64effad4
printf("0x%x\n", p+1); // 0x64effad5
// char数组相邻单元相差1(十进制) char 1,差一个char??????

int ai[] = { 0,1,2,3,4,5 };
int* q = ai;
printf("0x%x\n", q); // 0x64effb18
printf("0x%x\n", q + 1); // 0x64effb1c
// int数组相邻单元相差4(十进制) int 4,差一个int??????
// 数组:同一数组指针变量相减的结果(值:是变量做差,含义:代表指向两个变量作差)
char ac[] = { 0,1,2,3,4,5 };
char* p = ac;
char* p1 = &ac[3];
printf("%d\n", p1-p); //输出3

int ai[] = { 0,1,2,3,4,5 };
int* q = ai;
int* q1 = &ai[4];
printf("%d\n", q1 - q); //输出4

SUM1 地址的加法与减法:
  • 两个地址作减法,p2 - p1,结果是指向的变量作差????

  • 地址+1或-1,结果是新地址p+1或p-1。两个地址值差一个基础类型所占内存(比如int4,char1)

SUM2 数组与地址:
  • 数组a[]的地址a就是a[0]的地址,即a = &a[0]

  • 一个数组相邻单元的地址也是相邻的,*p = a[0],那么*(p+1) = a[1],也就是说数组中单元的地址是线性递增的

  • 值得注意的是写法相差1,p和p+1,格式化输出发现相差1个基础类型所占内存。

2.指针++ --

SUM1 *p++
  • 含义:取p指向变量的值,并使p = p + 1,即这个动作结束后p指向下一个地址

  • 注意1:回顾++a和a++都是让a+1,不过此时前者值为a+1,后者仍为啊

  • 注意2:运算符*的优先级没有++高

课堂练习:遍历数组的方法
int a[] = {0,1,2,3,4,5,6,7,8,9};
int i;
// 遍历方法① 根据数组长循环
for (i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
    printf("%d", a[i]);
    printf("%d", *p++);// 效果相同
}
printf("\n");

// 或
int* q;
for (q = a; q <= &a[sizeof(a) / sizeof(a[0]) - 1]; q++) {
    printf("%d", *q);
}
printf("\n");

// 遍历方法②根据数组设定的截止符号循环,比如-1  while *p++
int b[] = { 0,1,2,3,4,5,6,7,8,9,-1 };
int* r = b;
while (*r != -1) {
    printf("%d", *r++);
}
printf("\n");

3.指针的关系比较

  • 关系运算符4+2都可以用

4.地址0

  • 声明:是有为0的地址的,但少年你不要去碰,意思就是别指定一个指针为0

  • 但,发现有些程序初始化为0了,实际上不是0,计算机自作主张了

  • 因此,可以用0地址做特殊的事:①返回指针无效【不懂】;②指针没有被真正初始化(先初始化为0)

  • 注意,可以用NULL来做②【可以这么理解么】

5.指针的类型

  • 无论指向什么类型,所有指针的值即大小都是一样的,因为都是地址

  • 但是指向不同类型的指针是不能直接互相赋值的,不同类型是有壁的!

  • 计算机被这样设定的初衷是为了避免用错指针

6.指针的类型转换

  • 万金油指针 void*

表示不知道指向什么东西的指针

void* 计算时可与char*相同(但不相通)【意思是不能直接p = q,p是void,q是char】

适用于底层,访问控制设备、访问外部寄存器等

  • 指针也可以转换类型

但实际上指向变量的类型并没有变,只是人们看待它的眼光变了(这不是掩耳盗铃【能举个栗子么】

// 指针类型的强制转换
int* p = &i;
void* q = (void*)p;

// 数据类型的强制转换
char a = 8;
int b = (int)a;

等一下:现在输出地址和输出值有些混,特指很多位那种,不是%d【回顾一下,char和int占据空间比较那块的知识点】

SUM1 指针可以用来做什么
  • 需要传入较大的数据时做参数

  • 传入数组,对数组才做

  • 函数返回值不止一个结果

  • 需要用函数修改不只一个变量

  • 动态内存的申请

6.动态内存分配

1.malloc函数——申请一块内存空间

  • c99用数组,c99前用malloc函数

从man malloc可知:

#include <stdlib.h>;//必须的头文件
void* malloc(size_t sizeof(int)); //原型声明,不用打出来,知道就行
int *p;
p = (int*)malloc(n*sizeof(int)); 
  • 注意返回结果类型时void,要转换成想要的类型(实际上不同类型内存占用,只是为了人类好理解,几个基础类型的空间

  • 注意要转换成和指针指向变量类型一致的内存计算,比如

int *p;
p = (int*)malloc(n*sizeof(int));

char q*;
q = (char*)malloc(n*sizeof(char));

2.free函数——释放借的内存,和malloc搭配使用

  • 出来借,总要还的

  • 只能还借的部分

int *p;
p = (int*)malloc(n*sizeof(int)); // 此时p的地址,就是 0+申请的内存
free(p);
p = NULL;//还完记得把指针指向NULL,避免误操作。【是因为现在指向真正的地址0么?????】

注意事项:

  • 借不到空间:会返回0,或者叫做NULL

  • 还过了,又还一遍 ❌

  • 归还不是借的空间

free(p+1); // ❌
free(NULL);// OK
  • 不及时free,导致内存占满

新手忘了,老手没法放——多调试,多学习,多总结

上面演示分配了几兆的内存,为什么定义后,循环的条件是p被分配了1兆内存,驱使这个循环进行下去的动力是什么

其他:

定义指针变量的好习惯——初始化

int* p = 0;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值