✔指针基础知识

1. 指针的类型
从语法的角度看,你只要 把指针声明语句里的指针名字去掉,剩下的部分 就是这个指针的类型 。这是 指针本身所具有的类型
(1) int *ptr;  // 指针的类型是 int*
(2) char *ptr; // 指针的类型是 char*
(3) int **ptr;  // 指针的类型是 int**
(4) int (*ptr)[3];  // 指针的类型是 int(*)[3]
(5) int *(*ptr)[4];  // 指针的类型是 int*(*)[4]
#include <stdio.h>

int main() {
    int i;
    int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int (*p)[10] = &a;
    printf("%ld\n", sizeof(p)); // 8
    
    return 0;
}

2. 指针所指向的类型

当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了 编译器将把那片内存区里的内容当做什么来看待
从语法上看,你只须 把指针声明语句中的指针名字和名字左边的指针声 明符*去掉,剩下的就是指针所指向的类型。 例如:
(1) int *ptr;   // 指针所指向的类型是 int
(2) char *ptr;    // 指针所指向的的类型是 char
(3) int **ptr;    // 指针所指向的的类型是 int*
(4) int (*ptr)[3]; // 指针所指向的的类型是 int()[3]
(5) int *(*ptr)[4]; // 指针所指向的的类型是 int*()[4]
在指针的算术运算中,指针所指向的类型有很大的作用
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。 当你 对 C 越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成  "指针的类型" 和 "指针所指向的类型"两个概念,是精通指针的关键点之一
3. 指针的值----或者叫指针所指向的内存区或地址
指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而 不是一个一般的数值。 在 32(64) 位程序里,所有类型的指针的值都是一个 32(64) 位 整数, 因为 32(64) 位程序里内存地址全都是 32(64) 位长
指针所指向的内存区就 是从指针的值所代表的那个内存地址开始
32(64)位程序里,sizeof(指针变量) == 4(8)个字节
以后,我们说一个指针的值是 XXX,就相当于说该指针指 向了以 XXX 为首地址的一片内存区域;
我们说一个指针指向了某块内存区域, 就相当于说该指针的值是这块内存区域的 首地址
指针所指向的内存区和指针所指向的类型是两个完全不同的概念
指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向 的内存区是不存在的,或者说是无意义的
以后,每遇到一个指针,都应该问问: 这个指针的类型是什么?指针指 的类型是什么? 该指针指向了哪里?(重点注意)

指针变量:一个专门用来存放另一变量的地址的变量  ,指针变量的值是地址(即指针)                         int *p;      // p是一个指向整型数据的指针变量

                 p = &a ;   // *p表示“指针变量p所指向的变量”,也就是变量a     基类型:int                                               指向int的指针(简称int指针)  p的指针类型是 “ int * ”  

* 的作用:  1)指针变量的标志       2)解引用,取内容

 在程序运行过程中,变量的地址是不可被改变的,是一个地址常量,对变量的访问都是通过地址进行的,指针变量中存储的地址可以被改变

/******************************************************************
指针变量作为函数参数:不能企图通过改变指针形参的值而使指针实参的值改变,
但是可以改变实参指针变量所指变量的值(址传递)
******************************************************************/
#include <stdio.h>
void swap(int *p1, int *p2) {
    int *p = p1;
    p1 = p2;
    p2 = p;
}
int main() {
    int a, b;
    int *pointer_1, *pointer_2;
    printf("please enter two integer number:");
    scanf("%d%d", &a, &b); // 5 9
    pointer_1 = &a;
    pointer_2 = &b;
    if (a < b) {
        swap(pointer_1, pointer_2);
    }
    printf("max=%d,min=%d\n", *pointer_1, *pointer_2); // 5,9
    return 0;
}

数组元素的指针就是数组元素的地址,数组名a(不包括形参数组名)代表数组中首元素(即序号为0的元素的地址) ,a+n也是地址(代表第n+1个元素的地址)     

因此下面两个语句等价:                                                                                                                  p = &a[0];                 // p的值是a[0]的地址                                                                                p = a;                        // p的值是数组a的首地址(即a[0])的地址

在引用数组元素时指针的运算                                                                                                 1)如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,p - 1指向同一数组中的上一个元素(执行p+1时并不是将p的值(地址)简单地加1,而是加上一个数组元素所占的字节数)     

                                                                                      

 2)如果 p 的初值为&a[0],则 p + i 和 a + i 就是数组元素 a[ i ]的地址,或者说,它们指向 a 数组序号为 i 的元素。这里需要注意的是 a 代表数组首元素的地址,a+1也是地址,它的计算方法同 p+1,即它的实际地址为 a + 1 * d

3)  如果指针变量p1和p2都指向同一数组中的元素,如执行p2-p1(两个地址之差),再除以数组元素的长度,就可知道它们所指元素的相对距离

假设,p2指向实型数组元素a[5],p2的值为2020;p1指向a[3],其值为2012,则p2-p1的结果是(2020-2012)/4=2。这个结果是有意义的,表示p2所指的元素与p1所指的元素之间差2个元素。这样,人们就不需要具体地知道p1和p2的值,然后去计算它们的相对位置,而是直接用p2-p1就可知道它们所指元素的相对距离
注意两个地址不能相加,如p1+p2是无实际意义的

#include <stdio.h>
int main() {
	int i, a[10], *p = a; // p的初值的是a,p指向a[0]
	printf("please enter 10 integer numbers:");
	for (i = 0; i < 10; i++) {
		scanf("%d", p++); // 输入10个整数给a[0]-a[9]
    }
	p = a; // 重新使p指向a[0]
	for (i = 0; i < 10; i++, p++) {
		printf("%d", *p);
    }
	printf("\n");
	return 0;
}

++ 和 * 同优先级,结合方向为自右向左,因此 *p++ 等价于 *p(++) , 即先引用p的值,实现*p的运算,然后再使p自增1。 *(p++)是先取*p的值,然后使p加1;*(++p)是先使p+1,然后再取*p 。++(*p) 表示p所指向的元素值加1

如果p当前指向a数组中的第 i 个元素a[i],则:                                                                      1)*(p--)相当于 a[i--],先对 p 进行 "*" 运算(求 p 所指向的元素的值),再使 p 自减
2)*(++p)相当于 a[++i],先使 p 自加,再进行"*"运算
3)*(--p)相当于 a[--i],先使 p 自减,再进行"*"运算

       

数组名作函数参数,实参数组名代表该数组首元素的地址,而形参是用来接收从实参传递过来的数组首元素地址的,因此,形参应该是一个指针变量(只有指针变量才能存放地址)

实际上,C编程都是将形参数组名作为指针变量来处理的                                                                           

数组名a代表数组首元素的地址,它是一个指针型常量,它的值在程序运行期间是固定不变的.    可以偏移改变指向,但不能赋值       

         eg:   for (int *p = a; a < (p + 10); a++) {

                     printf("%d", *a);  // a++无法实现   a++ <===> a=a+1   不能被赋值 

                 }

         eg:   char s[] = "abcde";

                  s += 2;  // error 不能被赋值

                  printf("%d\n", s[0]);                                                                                                但是形参数组名并不是一个固定的地址,而是按指针变量处理的,在函数调用进行虚实结合后,形参的值就是实参数组首元素的地址,在函数执行期间,它可以再被赋值                

1. 问题的引入

int a; // 定义了变量a,类型为int,实质是分配了一个4字节内存单元

a = 100; // 把数值100存放到变量a对应的存储单元中去
b = a; // 取出变量a的值,然后存放到变量b对应的存储单元中去

    ==> 在c语言中,任何一个变量名,都有两层含义:
       
(1) 代表该变量的存储单元     左值(lvalue)
        (2) 代表该变量的值                右值(rvalue)

    而且,我们对变量的访问只有两种情况:
        (1) 把一个值存到变量的存储空间中去  (write)
        (2) 从变量的存储空间中取值 (read)

我们知道系统已经把变量名和变量的地址相关联,实质上是通过地址来访问变量的。于是乎,有人提问是不是我们也可以直接通过地址来访问变量呢?
===> 指针

2. 指针的概念

地址:系统把内存以一个字节为单位(一个字节对应一个地址)划分成许多份进行编号,这个编号就是内存单元的地址
在C语言中,指针的概念与地址差不多,可以认为指针就是有类型的地址

一个变量的首地址,称为该变量的“指针”。它标志着该变量的内容从哪里开始

3. 指针变量

char *p1, p2;   // 定义了一个char类型的指针变量p1 和 一个char类型的变量p2
   

指针变量也是一个变量,它是用来保存一个对象的地址

指针变量的定义:
     
  类型 *指针变量名;
        “类型”:指针变量指向的类型
        “指向”:如果我保存了你的地址,那么就说我指向你
        eg:
            int a;
            int *ptr; // 定义了一个指针变量ptr,它的类型是:int *
            ptr = &a; // ptr是一个指针变量名,它指向的类型是 int

注意:在64位处理器里,地址都是64位的,即指针变量分配的空间是8个字节
        所以把指针变量强制转换为其他类型的指针类型也不会失真
       
因此 void* 也叫通用指针

⭐指针变量的类型决定了该指针变量与整型数据之间运算,使其实际地址变化的倍率关系  

eg:   
        
int a;
char b;
int *p1 = &a;
char *p2 = &b;
printf("%p\n", &a);
printf("%p\n", p1);
printf("%p\n", p1 + 1);

printf("%p\n", &b); 
printf("%p\n", p2);  
printf("%p\n", p2 + 1);  


0x7ffc6548a8f4
0x7ffc6548a8f4
0x7ffc6548a8f8
0x7ffc6548a8f3
0x7ffc6548a8f3
0x7ffc6548a8f4

4. 如何获取地址

(1) 通过取地址符 &
        &对象名:表示取该对象的地址
                 对象:变量、数组元素...
(2) 有些对象的名字本身就表示地址
        如:数组名、函数名...
    

注意:这两种方式获取地址都是有类型的

5. 如何访问指针指向的空间

*:指向运算符


*(地址) <==> 地址对应的那个变量
即 *(地址)可作为左值也可作为右值,还可以取地址
        *&a <==> a
        *& 可直接约掉

                注意:要和乘法运算符和定义时的 * 相区别:单目、双目
                int *p, a = 3;
                p = &a;
                printf("%d\n", (*p) * 2); // 6

6. 数组和指针

数组元素与普通变量一样,也有自己的地址,并且元素间的地址是连续的

数组名a:

        1. 代表整个数组

                sizeof(a),typeof(a),a,&a(整个数组的地址)

        2. 当指针用,数组名代表一个数组的首地址(首个元素的地址)

                a+1 ===>&a[0] + 1 ===>&a[1]

    eg:
        int a[10];
        int *p;
        p = &a[0]; // p = a;
        把100赋值给 a[0],请问有几种写法
        a[0] = 100;
        *p = 100;
        *a = 100; // 数组名即使数组的首地址(首个元素的地址)

    那么 *a <==> a[0]
        *(a+1) <==> a[1]

⭐ *(a + i) <==> a[i],when i >= 0

7. 二维数组与指针

a[3][4] = {a[0], a[1], a[2]} ={{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};

二维m×n ---> m个一维数组 ---> 每个一维数组包含n个元素

int a[3][4];
    二维数组a可以看成元素是 int[3] 的一维数组,
    所以, *(a+i)<==>a[i] 指向的是该一维数组的第i个元素,该元素为int[4]类型
    因此,这里需要再指向一次才能指向二维数组的元素
        *(*(a+i)+j) <==> a[i][j],这时元素类型为 int

表达式(i>=0,j>=0)       类型/sizeof                        含义                                       值
a+i                               int (*)[4] / int[3][4] (i=0)   指向第i行的首地址                 &a[i]
*(a+i)+j                        int* / int[4] 
(j=0)               第i行第j列的元素的地址         &a[i][j]
*(*(a+i)+j)                         int                                第i行第j列的元素                    a[i][j]

#include <stdio.h>

int main() {
    char a[3] = {'a', 'b', 'c'};
    char b[3][3] = {{'1', '2', '3'}, {'4', '5', '6'}, {'7', '8', '\0'}};
    printf("%ld %ld %c\n", sizeof a, sizeof(a + 1), *(a + 1));
    printf("%ld %ld %ld %s\n", sizeof b, sizeof(b + 1), sizeof(*(b + 1)), *(b + 1));
    return 0;
}

// 3 8 b
// 9 8 3 45678

8. 字符串和指针

在C语言,并没有内置字符串的类型,C语言的字符串是通过 char* 指针来实现的

   

char *str = "ABCDEF"; // 把保存在rodata段里字符串的首地址赋值给 str
char str1[] = "ABCDEF"; // 定义了一个字符数组,并把它的内容初始化为“ABCDEF”
str:保存字符串的首地址,即字符 ‘A’ 的地址
str+1:指向字符 ‘B’的地址

*(str + 1) = '1'; // error,因为这里没有定义字符数组,没有分配空间
                         // 常量保存在 rodata段内,不能被改变
*(str1 + 1) = '2'; // right

“ABCDEF”在C语言中,把该字符串的首地址赋值给了 str,由于字符串肯定会有终止符,
系统可以把字符串的内容完整的访问

9. NULL指针和野指针

(1)NULL也叫空指针,其实是系统定义的一个宏,表示不指向任何空间
        #define NULL  ((void*)0)
        所有NULL在逻辑运算中表示假
        NULL不能被访问,否则就会报段错误

(2)野指针(定义指针变量的时候,要马上给指针变量赋值,防止出现野指针 )                            野指针不是NULL指针,而是指向非法分配空间的指针
          野指针的成因有两种:
                  1)指针变量未初始化
                  2)指针变量被free或delete后没有置为 NULL,让人误以为p还是个合法的指针

eg:

(1)
void swap(int *a, int *b) {
    int *temp = *a; // temp是一个野指针 没有指向
    *a = *b;           
     *b = *temp;
}

(2)
char *p = (char*)malloc(100);
strcpy(p, "hello");
free(p);
// p = NULL; // 防止产生野指针
//...
//...
if (p != NULL) {
    strcpy(p, "hello");
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值