C语言之指针基础

内存的存储

1、是以一个字节为一个编号,也就是8位合在一起给一个编号,不是0,1就给编号。内存分为很多个单元,每个单元就会分配一个编号。

地址

内存单元的一个编号。而指针和地址一个概念的。也就是说指针就是地址。

- 内存单元的编号,是一个从0开始的非负整数 
- 范围:cpu对内存是通过控制、数据、地址三条总线来进行控制的。

控制总线

cup会先把内存中的数据读入,进行处理后,在返回给内存,然后内存在把数据写入硬盘。

数据总线:

用于数据的传输,不管是把内存中的数据发送给cpu,还是把cpu的数据写如内存条,都是由数据线来完成的,但是数据传输的方向则是由控制线来控制的。

地址总线:

地址线则是确定数据要写入内存中的那个单元。所谓的一个单元就是一个字节

一条地址总线能控制2的1次方,一般的机器有32个地址线,最终能够控制2的32个单元,而每个单元是八位,而最终我们的内存能够存储2的32次方*8位。

变量

1、普通变量:只能存放一个值。

2、指针变量:同样是一个变量,但是指针变量存放其他变量的地址

代码示例

#include <stdio.h>
int main() {
    int num = 10;
    //需求:定义一个指针变量p 保存num的地址编号
    //定义中:*修饰p为指针变量 指针变量名为p 而不是*p
    int *p;

    //将指针变量p 与num建立关系
    p = &num;

    char ch = 'a';
    //需求:定义一个指针变量p1 保存ch的地址编号
    char *p1;
    //ch与p1建立关系
    p1 = &ch;
    return 0;
}

图示:

指针变量的使用

代码示例1

# include <stdio.h>

int main(void)
{
    int * p; //p是变量的名字, int * 表示p变量存放的是int类型变量的地址
    /*
     int * p; 不表示定义了一个名字叫做*p的变量
     int * p; 应该这样理解: p是变量名, p变量的数据类型是 int *类型
     所谓int * 类型 实际就是存放int变量地址的类型
     */

    int i = 3;
    int j;

    /*
	1. p保存了i的地址, 因此p指向i
	2. p不是i,i也不是p,更准确的说: 修改p的值不影响i的值,修改i的值也不会影响p的值.
	3. 如果一个指针变量指向了某个普通变量, 则*指针变量就完全等同于普通变量
    例子:
      如果p是个指针变量,并且p存放了普通变量i的地址,则p指向了普通变量i。
      *p 就完全等同于i,或者说:在所有出现*p的地方都可以替换成i。
       在所有出现i的地方都可以替换成*p,*p 就是以p的内容为地址的变量。
    */
    p = &i;

    j = *p;  //等价于 j = i;
    
    printf("i = %d, j = %d
", i, j);

    return 0;
}

解析

p的内容是一个地址,在代码中,p的内容就是i的地址,*p其指向的变量当然就是i变量了。

注意

1、*p代表的是p所指向的那个变量。在上代码中*p和i是同一个东西,但是*p和p不是同一个东西。
2、int* p是一个声明,开头的int*是他的数据类型。P是变量的名字。不能理解我定义了一个整形变量,这个整形变量的名字叫做*p。所谓int*类型,实际就是存放int变量地址的类型。
3、*p代表的是以p的内容为地址的变量。

代码示例2

# include <stdio.h>

int main(void) {
    // 1.定义num变量
    int num = 10;
    // 2.定义指针变量
    int *p;
    // 指针变量指向num的地址
    p = &num;

    //p的值 与 &num是同一个值
    printf("p = %p
", p);
    printf("&num = %p
", &num);

    // *p 代表的是 取p所保存的地址编号 对应空间的内容
    // *p == num
    *p = 100;//*p == num, *p = 100 --> num = 100
    printf("num = %d
", num);
    printf("*p = %d
", *p);//*p == num == 100

    /*
     *p = *p +1
     *p = num,所有(*p)++等价于num++
     */
    (*p)++;
    printf("*p = %d
", *p);


    scanf("%d", p);
    printf("*p = %d
", *p);//*p == num
    return 0;
}

程序运行结果

指针的定义

  • 指针就是地址,地址就是指针。

  • 地址就是内存单元的编号。

区别

指针变量:就是存放内存单元编号的变量。而指针只是一个值,这个值是内存单元的一个编号。指针变量才是一个变量,他里面才可以存放数据。

指针变量初始化

代码示例

#include <stdio.h>

int main() {
    int num = 0;
    int *p = NULL; // int *p; p = NULL;
    
    int *p1 = &num; // int *p1; p1 = &num;
    return 0;
}

指针的本质就是一个操作受限的非负整数。指针不能进行算术运算-相加 乘 除。但是能相减。

指针和指针变量是两个不同的概念,但是需要注意的是,通常叙述时会把指针变量简称为指针,实际他们含义并不一样。

代码示例1

# include <stdio.h>
/*
int *p:p只能存放int类型的地址。
P = & i:把i的地址赋给p。然后p就指向了i,*p就等于i。其实就是
    1:该语句保存了i的地址。
    2:p保存了i的地址,所以p指向i。
    3:p既然指向i,*p就是i。
*/
int main(void)
{
    int * p;  //*p 表示以p的内容为地址的变量。
    int i = 5;

    /*
    *p:表示以p的内容为地址的变量。p是有指向的,p里面是个垃圾值,
    *p则是说以p的内容为地址的变量。因为不知道p的值是多少,所以不知道*p到底代表的是那个变量。
     而*p = i,i=5,最终的结果就是把5赋给了一个所不知道的单元。
    */
    *p = i;
    printf("%d
", *p);

    return 0;
}

代码示例2

# include <stdio.h>
/*
1、q的空间是属于本程序的,所以本程序可以读写q的内容,
   但是如果q内部是垃圾值,则本程序不能读写*q的内容。
2、因为此时*q所代表的内存单元的控制权限并没有分配给本程序所以本程序运行到13行时就会立即出错
*/
int main(void)
{
    int i = 5;
    int * p;
    int * q;

    p = &i;
    //*q = p; //error 语法编译会出错
    //*q = *p;  //error
    p = q;  //q是垃圾值,q赋给p, p也变成垃圾值
    printf("%d
", *q);   //13行

    return 0;
}

注意

第一个error是数据类型不符合,不能相互转换。*q代表的是整形,因为*q代表的是以q的地址为内容的变量。
而p是地址(int *)类型。第二个error同样有错,因为q没有赋值。

*与&的关系

代码示例

#include <stdio.h>

int main() {

    /*
     num 是int类型
     &num 是int *类型
     对变量取地址 & 整个表达式类型 + *
     */
    int num = 0;
    int *p;

    /*
    p 是int *类型
    *p==num 是 int 类型
    总结:在使用中 对指针变量取* 整个表达式 -*
     */
    p = &num;
    
    /*
    注意:在使用中,当*与&同时出现的时候 从右往左 依次抵消
    &*&*&num == &num &*&*&*p == p
    *p = *&num == num;
     */
    
   *p == num;
    return 0;
}

指针变量的类型

  • 指针变量自身类型
  • 指针变量所指向的类型
#include <stdio.h>

int main() {
    /*
     自身的类型:只将变量名拖黑,剩下什么类型,指针变量自身就是什么类型。
     p自身的类型就是int *
     */
    int *p;

    /*
        指针变量指向的类型:将变量名以及离它最近的一个*一起拖黑,剩下的是什么类型。
        指针变量指向就是什么类型。 p指向的类型是int
     */

    return 0;
}

指针变量取值宽度

指针变量取值宽度由指向的类型长度决定

代码示例

#include <stdio.h>

int main() {
    // 定义变量
    int num = 0x01020304;
    // 定义指针变量
    int *p = &num;
    printf("*p=%#x
", *p);

    short *p1 = &num;
    printf("*p1=%#x
", *p1);

    char *p2 = &num;
    printf("*p2=%#x
", *p2);
    return 0;
}

图示

指针变量的跨度

指针变量的跨度由指向类型的大小确定。

#include <stdio.h>

int main() {
    // 定义char*类型的指针变量
    char *p1 = NULL;  // 0
    printf("p1=%d
", p1);
    printf("p1+1=%d
", p1+1);

    // 定义char*类型的指针变量
    short *p2 = NULL;  // 0
    printf("p2=%d
", p2);
    printf("p2+1=%d
", p2+1);

    // 定义char*类型的指针变量
    int *p3 = NULL;  // 0
    printf("p3=%d
", p3);
    printf("p3+1=%d
", p3+1);
    return 0;
}

图示

void修饰指针变量

#include <stdio.h>

int main() {
    /*
     不允许void定义普通变量(系统不能通过void判断i的大小)
     */
    // void i = 10;

    /*
     可以使用void *定义指针变量
     */
    void *p;
    int i = 10;
    p = &i;

    /*
    对于void *指针变量,不要取*。
    p指向的类型为void,判断不出宽度
     */
    // printf("*p=%d
", *p);

    // 要想通过void *p取出空间内容,必须对p做强制类型转换
    printf("*p = %d
", *(int *)p);
    return 0;
}

注意:

void *p 可以保存任意类型的地址。(万能指针)
void 主要作为函数的形参(让函数通过)

const修饰指针变量

#include <stdio.h>

int main() {
    int num = 10;
    // 第一种:const在*的左边 修饰的是* (*p1只读,p1可读可写)
    const int *p1 = &num;
    // *p1 = 100; // error *p1只可读
    int data = 30;
    p1 = &data;
    printf("*p1=%d
", *p1);
    
    // 第二种:const在*的右边,修饰的是p2 (*p2可读可写, p2只读)
    int * const p2 = &num;
    *p2 = 100;
    printf("num=%d
", num);
    // p2 = &data; // error p2只读
    
    // 第三种:const在*的左右两边,即修饰*也是指针变量。(*p3只读, p3只读)
    const int* const p3 = &num;
    
    /**
     * *p3 只读操作
     * p3只读
     */
//    *p3 = 200;
//    p3 = &data;
    return 0;
}

总结

当const出现在*号左边时候,指针指向的数据为常量。当const出现在*后右边的时候指针本身为常量。

指针操作注意点

void test1()
{
    // 不要操作未初始化的指针变量(野指针)
    int *p;
    *p = 100; // 段错误
        
     // 不要操作NULL指针
     int *p1 = NULL;
     *p1 = 100;
    
    // 不要操作自定义地址
    int *p3 = 2000;
    *p3 = 100;
}
原文地址:https://www.cnblogs.com/Guard9/p/12905167.html