指针基础知识

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]
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) 位长
指针所指向的内存区就 是从指针的值所代表的那个内存地址开始, 长度为 sizeof(指针所指向的类 型) 的一片内存区
以后,我们说一个指针的值是 XX,就相当于说该指针指 向了以 XX 为首地址的一片内存区域;
我们说一个指针指向了某块内存区域, 就相当于说该指针的值是这块内存区域的 首地址
指针所指向的内存区和指针所指向的类型是两个完全不同的概念
指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向 的内存区是不存在的,或者说是无意义的
以后,每遇到一个指针,都应该问问: 这个指针的类型是什么?指针指
的类型是什么? 该指针指向了哪里?(重点注意)

指针变量:一个专门用来存放另一变量的地址的变量  ,指针变量的值是地址(即指针)                         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。例如, p+ 9和 a+ 9的值&a[9], 它指向a[9]  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],最后指向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);  

4. 如何获取地址

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

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

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

*:指向运算符


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

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

6. 数组和指针

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

⭐数组和指针的关系:
我们可以用指针来表示一个已存在的数组,且数组名就是数组的首地址(首个元素的地址)

    eg:
        int a[10];
        int *p;
        p = &a[0];
        把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)     指向第i行的首地址                 &a[i]
*(a+i)+j                        int* / int[4] 
(没有j)               第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");
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是C语言指针基础知识点: 1.指针的定义和初始化 指针是一个变量,其值为另一个变量的地址。定义指针变量时需要指定指针指向变量类型,然后使用*运算符来声明指针变量指针变量的初始化可以为其赋值为另一个变量的地址,也可以将其初始化为NULL。 2.指针的运算 指针可以进行加、减、自增、自减等运算。指针加上一个整数n时,指向的地址会增加n个存储单元的大小,减去一个整数n时,指向的地址会减少n个存储单元的大小。 3.指针的比较 指针可以进行相等、大于、小于等比较运算。当两个指针指向同一个变量时,它们相等;当两个指针指向同一数组中的不同元素时,它们可以进行大小比较。 4.指针类型转换 指针可以进行类型转换,但需要注意转换后的指针类型必须与指向变量类型相同,否则可能会导致程序出错。 5.指针和数组 数组名本身就是一个指针指向数组的第一个元素。可以使用指针来访问数组中的元素,也可以将指针作为函数参数来传递数组。 以下是一个例子,演示了指针的定义、初始化、运算和比较: ```c #include <stdio.h> int main() { int a = 10; int *p = &a; // 定义指向整型变量a的指针p printf("a的值为:%d\n", a); // 输出:a的值为:10 printf("p指向变量的值为:%d\n", *p); // 输出:p指向变量的值为:10 printf("p的值为:%p\n", p); // 输出:p的值为:0x7ffeeb5f8a2c printf("p+1的值为:%p\n", p+1); // 输出:p+1的值为:0x7ffeeb5f8a30 printf("p的地址为:%p\n", &p); // 输出:p的地址为:0x7ffeeb5f8a28 printf("p和&a的比较结果为:%d\n", p == &a); // 输出:p和&a的比较结果为:1 return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值