C语言学习--指针

指针的的定义:

  • 使用指针的需求 将某地址保存下来
  • 指针使用的场景 传递与偏移

就是保存地址和传递参数

#include <stdio.h>

int main()
{
   int i=5;
   int* i_pointer=&i;
}
                                                                     

指针变量 int 型只能对应 int 变量(啥型号对应啥)

指针变量用来保存地址 取变量地址

 

指针的定义格式如下


基类型   *   指针变量名
前面是类型,后面是变量名

 

 

 

 

 

 

基本这样传递就可以了,然后*取值

 

关于 * 和 & 的一些疑问?

如果已经执行了 

pointer_1=&a ,给地址了

那么 &* pointer_1的含义是什么?

& 与 * 的优先级相同,但要按照从右往左的顺序

首先* pointer_1取值 ,相当于* &a 

取出来变量a的值(5),所以后面 & a(5) 

&* pointer_1 等价与 &a  都是 取地址 

那么 *& a的含义是什么?

首先&a 取出地址 ,也就是 pointer_1一样

在进行 *取值运算,取出来变量a(5)  

*&a 与 a 等价, 都是 取值

  • 总结: * 取值(解引用) & 取地址 (引用)

 

 

对于数组指针

比如

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

int main()
{
    int a[5]={1,2,3,4,5};
    int* p;//对一个指针变量进行取值,结果也是其基类型(这里是int)
    
    p=a; //刚开始是首地址

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

    getchar();
    system("pause");
    return 0;
}

这种情况

 

 

 

 这时候单指针指的是数组的起始位置 就是a[0]

如何想打印的话,使用循环

for (int i = 0; i < 5; i++)
    {
            printf("%d\n",*(p+i));
    }

 

注意要保证二边类型一样:a[]数组   p=a  a也是数组的初始位置

如果&a 就是取a数组的初始地址a[1] ,再取一次地址

所以不能 p=&a

 

 指针自增自减

 以前已经见过i++ 和 ++i  等

那么要是 *p++ ,*p--  *++p 这些呢

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

int main()
{
    int a[3]={2,7,8};
    int *p;
    int j;
    p=a;//让指针变量p指向数组的开头
    j=(*p)++;//

    printf("a[0]=%d,j=%d,*p=%d\n",a[0],j,*p);
    getchar();
    system("pause");
    return 0;
}

试着说出  a[0] *p 和 j 的值

 

解答:先是(*p) 访问到2数组起始的空间 

整体 j  (*p)++变化  指向下一个空间 值,即数字取值3的空间 

这时候数组初始位置 地址不变,这里面的值变为3

所以 a[0] 为3  *p整体变下来为3

 

这时候 说下j 的值 ,先看 如果 j=i++ ; i=1;结束后j等于几?

1、首先,单独拿出来说++i和i++,意思都是一样的,就是i=i+1。

2、如果当做运算符来说,就是a=i++或者a=++i这样的形式。情况就不一样了。

先说a=i++,这个运算的意思是先把i的值赋予a,然后在执行i=i+1;

而a=++i,这个的意思是先执行i=i+1,然后在把i的值赋予a;

举个例子来说,如果一开始i=4。

那么执行a=i++这条语句之后,a=4,i=5;

那么执行a=++i这条语句之后,i=5,a=5;

同理,i--和--i的用法也是一样的。

作者:顺其自然的活着
链接:https://www.zhihu.com/question/19811087/answer/207494860

就是单独拉出来就是+1,做运算符 得化 

j=i++;  先进行赋值, j=1; 再i++=2; i=2;

j=++i;  先i+1 ;i=2;再进行 赋值, j=2;

 

 

所以上面代码的j值

j=(*p)++;

(*p)++ 取值3 地址数组初始位置

但是j= 先赋值    j=(*P)= 刚开始就赋值,所以为 *p =2

后面再运算 ++ 

*p才是3

带着把数组a[0]地址的值换了

 

指针与一维数组

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

void change(char d[])
{
    d[0];
}

int main()
{
    char c[10]="hello";
    change(c);
getchar(); system(
"pause"); return 0; }

 

比如这种 函数调用就是

d[] 其实与 *d 一样 都是i指针数组

但是没做这种d[] 这种,所以函数的形参里面应该不能放 数组

放个指针变量就行

void change (char *d)

拿不到数组长度

 

 void change(char *d)

{
*d='H';
d[1]='E';
*(d+2)='L'

}

这种都可以改变值

d[1]就是 c[1]传给函数的

 

直接把c的地址传给函数形参指针d

等于d=c,地址相同

 

动态内存申请malloc

申请5*sizeof(int)=20字节

数组一开始定义好就确定下来了,数据是放在栈空间

 

 

进程地址空间:堆 栈 

解释:栈空间的大小在编译时是确定的。如果使用的空间大小不确定,那么就要使用堆空间。

 

 

 

 

 

    int i; //申请多大的空间
    scanf("%d",&i);
    char* p;
    p=(char*) malloc(i); //malloc动态申请空间
    //malloc申请空间的单位是字节
    //malloc返回的是void*类型指针
    //(char*) 强转
    
    int *p1;
    p1=(int*)malloc(20);//malloc 申请的是字节,这里申请了20字节
    //1个int 占4个字节,所以等于申请了5个int,等于a[0]-a[4]

总申请代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int i; //申请多大的空间
    scanf("%d",&i);
    char* p;
    p=(char*) malloc(i); //malloc动态申请空间
    //malloc申请空间的单位是字节
    //malloc返回的是void*类型指针
    //(char*) 强转
    
    strcpy(p,"malloc success");
    puts(p);


    getchar();
    system("pause");
    return 0;
}

 

 

 

申请100个字节放 "malloc success" ,如图:

 

 

但是还要free 释放内存

这里记录下指针p偏移错误

    int i; //申请多大的空间
    scanf("%d",&i);
    char* p;
    p=(char*) malloc(i); //malloc动态申请空间
    p++;
    strcpy(p,"malloc success");
puts(p);

这里p++只想打印后面的 alloc success 但是这样free(p)会报错

 

 

 可以在写一个 *p1

char* p;
    char *p1;
    p=(char*) malloc(i); //malloc动态申请空间
    //malloc申请空间的单位是字节
    //malloc返回的是void*类型指针
    //(char*) 强转
    p1++;
    strcpy(p,"malloc success");
    strcpy(p1,p);
    puts(p);
    free(p);

 

 

最后

 free(p) ; p=NULL ; 释放操作

释放后,这时候p是野指针,就没有指向的

所以要 p=NULL;

如果不把p值为NULL,被称为野指针

 

注意:堆空间不会随子函数的结束而释放,必须自己free

 

 

栈空间与堆空间差异

栈空间:

看一个例子:

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


char* printf_stack() //char* 返回char* 类型
{
char c[17]="i am print_stack";
puts(c); //能正常打印
return c;  //返回 char* c
}

int main()
{
   char* p;
   p=printf_stack();//p=c;
   puts(p);
    
    getchar();
    system("pause");
    return 0;
}

 

 

 

 函数里面的正常打印,下面main函数里面的却不能正常打印

原因是上面是栈(自动分配空间) 函数结束后会自动释放

打印出来不正常

因为puts(p)这时候占用了栈空间

 

堆空间:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char* printf_stack() //char* 返回char* 类型
{
char c[17]="i am print_stack";
puts(c); //能正常打印
return c;  //返回 char* c
}

char* printf_malloc() //char* 返回char* 类型
{
       char* p=(char*)malloc(30);//申请堆空间内存
       strcpy(p,"I  am print_malloc");
       puts(p);
       return p;  //返回 char* p
}

int main()
{
   char* p;
   p=printf_stack();
   //puts(p);
   p=printf_malloc();
   puts(p);
   
    getchar();
    system("pause");
    return 0;
}

 

 

这是因为:申请堆空间不会因为函数结束结束,除非free否则一直能用

 

来做个例子吧:

输入一个整型数,然后申请对应大小空间内存,然后读取一个字符串,字符串的输入长度小于最初输入的整型数大小,最后输出输入的字符串即可(无需考虑输入的字符串过长,超过了内存大小);

 

 

 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    int n; //申请多大的空间
    scanf("%d",&n);
    char* p;
    p=(char*)malloc(n);
    char c;
    scanf("%c",&c);
    gets(p);
    puts(p);
    
 
    getchar();
    system("pause");
    return 0;
}

 

主要讲下这里  char c;   scanf("%c",&c);’

puts和 gets用的都是缓冲区

因为前面有scanf("%d",i) ,scanf在缓冲区里里面输入一个 数字 

比如 10\n   \n是结束符

此时缓冲区为

 

 

 scanf读取后 \n还在缓冲区里面

 

所以 char c;   scanf("%c",&c); 是为了去除缓冲区里面的\n

 

 

字符指针与字符数组的初始化

#include <string.h>

int main()
{
    char* p="hello";//把字符串型常量"hello"的首地址赋给p
    char c[10]="hello";//等价与strcpy(c,"hello");
    //c[0]='H';
    printf("c[0]=&c\n",c[0]);
    printf("p[0]=&c\n",p[0]);
    //p[0]='H'; //不可以对常量区数据进行修改
    //p="world";//将字符串world的地址赋给p
    //c="world";//非法

    getchar();
    system("pause");
    return 0;
}
 //p[0]='H'; //不可以对常量区数据进行修改

内存是有权限的: r(可读) ,w(可写), rw  

这时候p[0]的地址里面内存不可写

 

 

字符串 常量存在 数据区,不是堆也不是栈,需要内存申请读写

 

 

 char c[10] =“hello world”

把字符串变量 hello world 从数据区拿出来到栈

所以是可读可写的

这样 c[0]可以从 数据区拿'H'

而指针p[0]:

 p[0]='H'; //不可以对常量区数据进行修改

这时候‘H’,在数据区,不能读取变量,也不能取起始地址,

 

再来看

p="world";//将字符串world的地址赋给p

”world“  是字符串型  数据区 起始地址有

这样可以把它的起始地址赋给p

 

//c="world";//非法

c是数组,里面的地址不会更改,不能直接赋值

 

二级指针

  • 二级指针只服务于一级指针的传递与偏移

  • 要想在子函数中改变一个变量的值,必须把该变量的地址传进去

  • 要想在子函数中改变一个指针变量的值,必须把该指针变量的地址传进去

 

 类似这样,二级指针就是  p是一个指针, **p2=&p ;把p的原来的地址取过来

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void change(int** p,int *pj)
{
    *p=pj; //*p等价于pi
}

int main()
{
    int i=10;
    int j=5;
    int* pi;
    int* pj;
    pi=&i;
    pj=&j;
    printf("i=%d,*pi=%d,*pj=%d\n",i,*pi,*pj);//等于10,10,5 
    
    change(&pi,pj);
    printf("after change i=%d,*pi=%d,*pj=%d\n",i,*pi,*pj);
    //目标是让*pi 的值为5
 
    getchar();
    system("pause");
    return 0;
}

 

 

posted @ 2022-01-17 09:28  halfup  阅读(264)  评论(4编辑  收藏  举报