C语言----指针

本文深入探讨C语言中的指针概念,包括左值与右值的区别,指针变量的特性,数组与指针的关系,以及如何正确使用指针进行内存操作。通过实例讲解,帮助读者理解指针在字符串处理、数组遍历、函数参数传递等方面的应用。

在这里插入图片描述
在这里插入图片描述
注意 避免访问未初始化的指针
可能会修改其他位置的内存的代码

  1. 左值和右值
    左值指用于识别和定位一个存储位置的标识符。(注意:左值同时还必须是可以改变的)
#include <stdio.h>

int main()
{
        int a = 5;

        ++(a++);

        return 0;
}

这里(a++)是先将变量a的值(5)作为整个表达式的值返回,再将a自增1(类似于a=a+1)。
所以这里++(a++);相当于++(5),a=a+1;
这里5是一个常量并不是用于识别或定位存储位置的标识符
而右值应该是 表达式的值
2. 明确一点,指针变量只能存放地址
3. 取地址运算符不能作用于一个常数,因为取址运算符的作用对象应该是一个左值,而常数是右值
4. 虽然存放指针变量和存放整数变量所需的存储空间一样,但不是说他们可以相互取代

#include <stdio.h>

int main()
{
        int a, b;

        b = 110;
        a = &b;

        printf("%d\n", *a);

        return 0;
}

比如上述代码就会报错
5.数组和指针
!注意点
scanf中本来就是要写变量的地址,将输入流入到该地址,现在如果已经有一个指针,即可直接放指针变量在该位置

int a;
int *p=a;
scanf("%d",&a);
scanf("%d",p);//这两句话是等价的

这也是为什么数组名可以作为scanf的一个占位地址

char str[128];
scanf("%s",str);

数组名其实是数组第一个元素的地址

char str[128];
str
&str[0]//数组第一个元素取地址一致

在这里插入图片描述
*p,*(p+1),*(p+2) 这样来访问元素 也可以用*str,*(str+1),*(str+2)这样来访问元素

同理,也可以用指针来定义数组,同样用下标来进行访问

char *str="I love FishC.com!";
int i,length;
length=strlen(str);
for(i=0;i<length;i++)
{
printf("%c",str[i]);
}
return 0;

注:fgets 从指定文件中读取字符串

#include <stdio.h>

#define MAX 1024

int main()
{
        char str[MAX];

        printf("请输入一个字符串:");
        fgets(str, MAX, stdin);

        printf("您输入的内容是:%s", str);

        return 0;
}

指针例题
获取字符串长度–strlen函数

#include <stdio.h>

#define MAX 1024

int main()
{
        char str[MAX];
        char *target = str;
        int length = 0;

        printf("请输入一个字符串:");
        fgets(str, MAX, stdin);

        while (*target++ != '\0')
        {
                length++;
        }

        printf("您总共输入了 %d 个字符!\n", length - 1);

        return 0;
}

注意 这里一定要用指针指向数组,如果直接用数组名字去自增会报错
会报一个 lvalue required as increment operand 的错误 自增对象必须是一个左值 而数组名不是左值
因为数组名是不可修改的!!!所以数组名不是左值 是一个地址常量

指针数组和数组指针

int *p1[5]; //指针数组 数组下标[]优先级比*更高 所以是数组
int (*p1) [5];//数组指针

在这里插入图片描述
指针数组是一个数组,每个数组元素存放一个指针变量
//糟糕的代码

int *p[5]={&a,&b,&c,&d,&e};
int i;
for(i=0;i<5;i++)
{
printf("%d\n",*p[i]);
}

//正常代码 常用的思路 取代二维数组的方法

char *p1[5]={"22222","dhfakhg","dddfae","eeee","cccc"};//里面每个指针都指示一个数组的开头
int i;
for(i=0;i<5;i++)
{
printf("%s\n",p1[i]);//这里不能加*号,加了*号取出来的是第一个字符,而我们要的是字符的地址,取出那个字符串

}

//解释1:字符串赋的值就是地址,所以指针数组还是存的地址
//2.指针数组类似于指针的指针 而指针数组中的每个指针可以看做是一个数组名
为什么字符串可以赋值给字符指针变量
双引号做了3件事
1.申请了空间(在常量区),存放了字符串
2. 在字符串尾加上了’/0’
3.返回地址

char *p;
p="abcd"//就是将返回的地址赋值给了p
char *p="hello"//正确
char a[10];
a="hello";//错误 数组名是一个常量

字符串常量"hello"出现在一个表达式中时,"hello"表达式使用的值就是这些字符所存储的地址(在常量区),而不是这些字符本身。

难点

int temp[5]={1,2,3,4,5};
int *p=temp;//p指向的是一个变量!!并不是数组 

只有下面所说的数组指针才是名正言顺的指向数组的指针
所以 以下代码才会出错

int temp[5]={1,2,3,4,5};
int (*p2)[5]=temp;//这里把一个变量赋值给数组指针是有问题的 数组指针需要的是一个真正的数组地址

正确写法

int temp[5]={1,2,3,4,5};
int (*p2)[5]=&temp;// 把数组当做一个整体,取出数组的地址给他,才是数组指针想要的内容
int i;
for (i=0;i<5;i++)
{
printf("%d\n",*(*p2+i));
}
return 0;

*(*p2+i) 的解释:
1.数组指针指向的是这个数组的本身! 这个数组指针指向的内存空间中,含有元素的地址
2.元素的地址在数组地址指向的内存空间里面
*p2 表示的是这个数组的地址(对应的也是&temp 这里把这个数组的地址复制给了p2) +i 再取出他的值

数组指针

int (*p2)[5];//()和【】优先级一样 先定义p2为一个指针 指向的是一个由5个元素的数组

在这里插入图片描述
典型错误

int (*p2)[5]={1,2,3,4,5};
int i;
for (i=0;i<5;i++)
{
printf("%d\n",*(p2+i));
}
return 0;

1.数组名和对数组名取地址虽然值相同,但是含义是不一样的。array表示数组第一个元素的位置,而&array表示的是整个数组的位置(这里将数组看做一个整体)

int array[10]={0,1,2,3,4,5}
array==&array//光从值上来说是一样的

典型例子

#include<stdio.h>
int main()
{
int array[10]={0,1,2,3,4,5};
int *p=(int*)(&array+1);
printf("%d\n",*(p-6));
return 0;
}

这里虽然array和&array的值相同,但含义是不一样的。array表示数组第一个元素的位置,而&array表示的是整个数组的位置(这里将数组看做一个整体)。
因此,&array+1指向的就是整个数组最后的位置(第二个array数组的起始位置),然后(int*)将其强制转换为一个整型地址(指针),所以指针变量p初始化后,指向的地址应该是array[10](第11个元素),所以*(p-6)==array[10-6]==array[4]==4。
重点强调:array是数组第一个元素的地址,所以array+1指向数组第二个元素;&array是整个数组的地址,所以&array+1指向整个数组最后的位置。

2.不能使用 if(str1==str2)这样的形式来比较两个字符串
因为这样比较的是指向两个字符串的指针,而不是字符串本身

main 函数可以带参数

main函数参数只能有两个,习惯上把这两个参数写为argc和argv。
argc(第一个形参)必须是整型变量,argv(第二个形参)必须是指向字符指针的指针(注意:它不是指针数组,但可以理解为一个字符串数组)

int main(int argv,char *argv[])
{
...
}

例子

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
        int result = 0;

        while (argc-- != 1)
        {
                result += atoi(argv[argc]);
        }

        printf("sum = %d\n", result);

        return 0;
}

指针和二维数组

  1. 二维数组中array表示的是 指向包含五个元素数组的指针
  2. 二维数组中 (array+1)表示的是什么
    在这里插入图片描述
    array[1] 其实也就是指向第二行子数组的第一个元素的地址
    所以
    (array+1) 依旧是一个地址
    如果是array[1]+0 则不需要&,相当于*(array+1)+0 ,array[1][0]相当于 ((array+1)+0)

##一维和二维等价
在这里插入图片描述

数组指针和二维数组

在这里插入图片描述

int(*p)[3]
int array[][3]={{0,1,2},{3,4,5}};//形式极为相似

已知array是指向第一个三个元素子数组的指针 所以可以赋值给p

void指针和NULL指针

在这里插入图片描述

  1. 要将其他类型的指针转换为void指针需要强制类型转化
  2. 不要对void类型指针进行解引用,因为void是无类型,编译器不知道void指针指向的数据跨度是多少
  3. 如果一定要进行void解引用要进行强制类型转换 *(int*)pi
int num=1024;
int *pi=&num;
char *ps="FishC";
void *pv;
pv=pi;
printf("pi:%p,pv:%p\n",pi,pv);
printf("*pv:%d\n",*(int*)pv);

void指针典型错误
void指针自加1并不会 跨越一个类型的长度 因为void指针没有跨度

#include <stdio.h>

int main()
{
        int array[5] = {1, 2, 3, 4, 5};
        int *pi = &array[2];
        void *pv;

        pv = pi;
        pv++;
        pi = pv;

        printf("%d\n", *pi);

        return 0;
}

比如上述代码 打印结果并不是4
因为 pv是void类型指针,所以编译器并不知道其“跨度”是多少,因此pv++只是简单地将地址加1.
那么地址不正确,打印出来的值肯定就是错误的

NULL指针

其实NULL指针是一个宏定义 把地址0的void指针定义为NULL

#define NULL ((void *)0)

意味着该指针不指向任何东西
如果你的指针的指针不知道指向哪里的时候,就把指针初始化为NULL

int *p1;
int *p2=NULL;

p1的默认值是随机的 是野指针,悬空指针,后面的代码一旦使用就会出现不可预知的问题
注意 NULL指针也不能解引用
在这里插入图片描述

指向指针的指针(及其重要)

在这里插入图片描述

指针数组和指向指针的指针的异同

例子

char *cBooks[ ]={"c_design","c_profession","c_pointer","c_trap"};//指针数组 字符串就是指向字符的指针
//为什么用指向指针的指针,这里我们需要对书进行归类,这里不应该重新创建两个指针数组
//指向指针的指针就会显得很渐变
char **byFishC;//是一个指向字符指针的变量
char **loves[4];//数组中每个元素都是指向指针的指针
byFishC=&cBooks[4];//cbooks[4] 就是字符串"c_trap" 对这个字符串取值 也就是一个指向字符指针的指针
loves[0]=&cBooks[0];
loves[1]=&cBooks[1];
loves[2]=&cBooks[2];
loves[3]=&cBooks[3];

在这里插入图片描述
代码的灵活性和安全性都有显著的提高

用数组指针来访问二维数组

错误案例 打印二维数组

#include <stdio.h>

int main()
{


    int array[3][4]={{0,1,2,3},
    {4,5,6,7},{8,9,10,11}};
    int **p=array;
    int i,j;
    for (i=0;i<3;i++)
    {
        for(int j=0;j<4;j++)

        {printf("%2d\n",*(*(p+i)+j));}//p只是一个指向指针的地址,跨度始终是4,一行有多少个元素p是不知道的
    }
return 0;
}

正确修改

#include <stdio.h>

int main()
{


    int array[3][4]={{0,1,2,3},
    {4,5,6,7},{8,9,10,11}};
    //int **p=array;
    int i,j;
    for (i=0;i<3;i++)
    {
        for(int j=0;j<4;j++)

        {printf("%2d\n",*(*(array+i)+j));}
    }
return 0;
}

从编译器的角度思考原因
p的跨度和array的跨度不同
在这里插入图片描述
在这里插入图片描述
p和array本身地址相同,但是+1之后地址不同,p是加了4个字节,array是加了16个字节。
编译器能读懂array却读不懂p
这也是为什么之前二维数组定义可以忽略行,但是绝对不能忽略列
因为指向指针的指针无法自动推断

??如何利用指针来指向二维数组 而不是用数组名字
这个时候就要用数组指针

#include <stdio.h>

int main()
{


    int array[3][4]={{0,1,2,3},
    {4,5,6,7},{8,9,10,11}};
    int (*p)[4]=array;//一维的数组指针 要对array取地址 而这里不用??
    //因为对于二维数组来说 array是第一个元素的地址 赋值给了p,p+1之后就是第二行的地址,而这里[4] 的存在说明了p指向的是一个4个元素的一维数组
    
    int i,j;
    for (i=0;i<3;i++)
    {
        for(int j=0;j<4;j++)

        {printf("%2d\n",*(*(p+i)+j));}
    }
return 0;
}

如前面说明的二维数组的情况
在这里插入图片描述
更进一步的写法,模仿一维数组的情况

#include <stdio.h>

int main()
{


    int array[3][4]={{0,1,2,3},
    {4,5,6,7},{8,9,10,11}};
    int (*p)[3][4]=&array;
    int i,j;
    for (i=0;i<3;i++)
    {
        for(int j=0;j<4;j++)

        {printf("%2d\n",*(*(*p+i)+j));}
    }
return 0;
}

课后作业的思考和总结
1.什么时候*&p和p等价,什么时候&*p和p等价
解答:& 运算符的操作数必须是左值(什么是左值 -> 传送门),因为只有左值才表示一个内存单元,才会有地址,运算结果是指针类型(地址);* 运算符的操作数必须是指针类型(地址),运算结果可以做左值。
结论:如果表达式 p 可以做左值,那么 *&p 和 p 等价;如果表达式 p 是指针类型(地址),那么 &*p 和 p 等价。

2.如果有 int a[10];,请问 &a 和 &a[0] 表达式的类型有何不同?
答:a 是一个数组,在 &a 这个表达式中,数组类型做左值,&a 表示取整个数组的首地址,类型是 int (*)[10];&a[0] 则表示数组 a 的第一个元素的首地址,虽然两个地址的数值相同,但后者的类型是 int *。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值