1.指针的概念

https://blog.csdn.net/Lemon_jay/article/details/82917000

指针是一种特殊的数据类型,指针类型的变量一般都是指向某个内存的地址。

C语言的指针类型包括两方面的信息:一是地址,存放在指针变量中;二是类型信息,关乎于读写的长度,没有存储在指针变量中,位于用该指针读写时的mov指令中,不同的读写长度对应的mov指令不同。(https://www.jb51.net/article/41457.htm

2.指针变量的声明

数据类型* 指针变量名 = ...;
这里的“数据类型”可以是任何的数据类型,包括基本数据类型、数组、结构体、枚举,甚至还可以是指针(多级指针 )、函数名(表示格式和这个有点不一样)。总之,指针可以与各种数据类型排列组合,导致了它的复杂性。

还有其它的指针类型的变量,不一定要用*来表示,比如数组名,数组名本身就是指针。

3.*号的三种含义

\1. 代表乘法
\2. 代表指针数据类型。如果
号放到一种数据类型的后面 那么就代表这种数据类型的指针 比如 int float ,当然,类型还可以是更为复杂的数据类型,如结构体,枚举,联合体。 类型当然也可以是指针本身,这样就形成了多级指针。
3.代表取出指针变量的地址的数据。*号 如果放到指针变量的前面 代表取出该变量里面的地址的对应的数据

4.指针的类型和长度

不管什么类型的指针都是4个字节(32位操作系统),对于64位操作系统是8个字节。
C语言为了方便指针运算, 定义各种基本类型的指针, 每种类型的指针运算时所偏移量的值是根据类型的长度决定的
+1 移动的是一个单位

指针运算

1. 指针类型和指针所指向的类型不一致时,要注意++的使用

如果ptr指针类型声明为char *,ptr++之后的打印结果为b。

    #include "stdafx.h"    
    #include <stdlib.h> 

    int _tmain(int argc, _TCHAR* argv[])
    {
        char a[20]={'a','b','c','d','e','f'};  
        char *ptr= a; //强制类型转换并不会改变a 的类型  
        ptr++; 

        printf("%c",*ptr);
        system("pause");
        return 0;
    }

如果ptr指针类型声明为int *,ptr++之后的打印结果为e。

   #include "stdafx.h"
    #include <stdlib.h> 

    int _tmain(int argc, _TCHAR* argv[])
    {
        char a[20]={'a','b','c','d','e','f'};  
        int *ptr=(int *)a; //强制类型转换并不会改变a 的类型  
        ptr++; 

        printf("%c",*ptr);
        system("pause");
        return 0;
    }

所以当指针声明成不同的类型,++的效果是不一样的。++和指针的类型有关
如果指针类型指针所指向的类型不一致时,要注意++的使用。

2. 指针类型和指针所指向的类型不一致时,强转后输出的问题。

int test04_01()
{
    char a[20]="You_are_a_girl";
    int *ptr=(int *)a;
    ptr+=1;
    printf("%d\n",*ptr);
    printf("%c\n",*ptr);
    system("pause");
    return 0;
}

上面的方法输出:
1600483937
a

int test04_02()
{
    char a = 'a';
    char * ptr = &a;
    printf("%d\n",*ptr);
    printf("%c\n",*ptr);
    system("pause");
    return 0;
}

上面的方法输出:
97
a

上面的两个方法看似都是同样输出字符'a',但是实际使用%d输出的结果完全不一样。这是为啥,请高手解答?

5.指针与各种数据类型的关系

5-1.数组与指针

数组名就是指针,代表数组的首地址。可以采用地址来遍历数组

1.数组名是一个指针,但是不是一个变量。不可能对它进行++运算。++不可使用在常量上

  #include "stdafx.h"    
    #include <stdlib.h> 

    int _tmain(int argc, _TCHAR* argv[])
    {
    int array[10]={0,1,2,3,4,5,6,7,8,9},value;  
    array++;
    value=*array; 

        printf("%d\n",value); 
        system("pause");
        return 0;
    }

在codeblocks上编译就会报错:error: lvalue required as increment operand,错误解释:
https://blog.csdn.net/hou09tian/article/details/75332576 (变量是左值,常量是右值。)

对代码做一下改动,用指针指向数组后,再对指针进行++运算,而非直接对数组进行运算。

 int _tmain(int argc, _TCHAR* argv[])    
    {
    int array[10]={0,1,2,3,4,5,6,7,8,9},value;  
    int *ptr = array;
    ptr++;
    value=*ptr; 

        printf("%d\n",value); 
        system("pause");
        return 0;
    }

2.如何利用指向数组的指针得到数组元素个数?

https://www.cnblogs.com/jieliujas/p/8822193.html

2.数组元素的内存地址是连续的

for(int i = 0; i < 10; i++){
    printf("array[%d] address = %d\n",i,&array[i]); 
 }

img

3.数组与指针的相互表示

在C语言中,我们能 够用数组表示法来引用指针,同时我们也能用指针表示法来引用数组元素。

5-2.字符串与指针

1)字符串是常量数组

字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量

2)字符串的创建形式

其实字符串有多种创建形式:
https://blog.csdn.net/u013812502/article/details/81196367
1)直接定义字符数组
char str[] = "hello world";//定义一个字符数组
char p = str;//定义一个指针变量,里面存放字符数组的首地址
2) 直接字符指针指向字符串常量
char
str = "www.dotcpp.com" ;
字符指针可以采用字符串常量来初始化
字符串指针指向字符串常量的首地址,*str打印出来是w,并不是整个字符串。

3)字符串的打印

char *ptr = "www.dotcpp.com" ;
printf("%s", ptr ); //会打印整个字符串
printf("%c", ptr ); //打印的不是w,可能是任意一个字符。

5-3.函数与指针

1) strcpy函数

strcpy函数,会将char *指针所指向的字符串复制,而不仅仅是一个字符。 参照strcpy源码:
https://blog.csdn.net/okawari_richi/article/details/57411796

2) printf函数

printf(“%s”,array); %s是从起始位置输出字符直到遇到\0为止,所以%s输出char[]数组,会输出整个串。

对于 printf("%s",指针变量),打印是指针变量所指向的内容。只有字符串是个特例不用加指针变量,就能打印内容。对于其他的,如果指针变量指向的不是字符串,则打印内容必须加上,即 printf("%类型",*指针变量)

对于 printf("%d",指针变量) 、 printf("%p",指针变量) 、 printf("%x",指针变量)等,则是打印的指针变量所指向的内存地址。

3.sizeof函数

sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。
sizeof(指针名称)测出的究竟是指针自身类型的大小而非指针所指向的类型的大小

4.C语言中函数返回的字符数组为null——函数中的局部字符数组

https://blog.csdn.net/weixin_44003177/article/details/118864102

https://blog.csdn.net/earbao/article/details/53401800?fps=1&locationNum=2

5-4.指针和结构体

 struct MyStruct 
    { 
        int a; 
        int b; 
        int c; 
    }; 
    struct MyStruct ss={20,30,40}; 
    //声明了结构对象ss,并把ss 的成员初始化为20,30 和40。 
    struct MyStruct *ptr=&ss; 
    //声明了一个指向结构对象ss 的指针。它的类型是 
    //MyStruct *,它指向的类型是MyStruct。 
    int *pstr=(int*)&ss; 
    //声明了一个指向结构对象ss 的指针。但是pstr 和 
    //它被指向的类型ptr 是不同的。 

结构体指针的访问方式

1.指针->成员或者(*指针).成员
成员调用的方法(->左边必须是指针,"."左边必须是实体)
2.指针++访问
2.结构体指针强转成int指针再访问(此种方式不规范)
所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的空隙。

一个颠覆三观的实验

struct MyStruct ss={20,'a',50};

如果将MyStruct的成员b定义为char类型,初始化为'a',通过(pstr+1)或者(pstr+1)访问,
通过printf %d或者%c打印,不同的操作系统、不同的编译器上出的结果五花八门,有的竟然还是正确的。

所以我得出一个结论:数组名与结构体名有完全不一样的特性,结构体名不能当成指针使用,结构体名不是指针常量

关于结构体指针的更多详情,可以参照我另外一篇博客:https://blog.51cto.com/4259297/2316321

------------------------------------------------------------------

结构体与数组的共性

| 1)结构体实体的地址和结构体第一个元素的地址一样, 各元素的地址也是连续的。(特殊情况也有不连续的)

| 数组实体的地址和数组第一个元素的地址一样, 各元素的地址也是连续的。

| 2)数组和结构体自身的实体(变量)并不占据独立的内存(和首元素地址一样),只有元素才占用真正的内存地址,

| 它只是标明自身的元素是一组特殊的数据结构, 通过实体封装,便于元素的访问。

​ |

结构体与数组的区别

| 1)数组名是指针常量,但是结构体名和指针没有任何的关系。

---------------------------------------------------------------

6.多级指针

在一种数据类型(非指针类型)的后面 有几个* 就代表几级指针

int *p :p表示指向int型的指针,是一级指针。
int *p: p表示指向int (指针)的指针,即指向指针的指针,是二级指针。p指向的数据类型为int *指针。

1)二级指针示例1

int test05()
    {
          char a[20]="You_are_a_girl";
            char *p=a;
            char **ptr=&p;
            printf("**ptr=%c\n",**ptr);
            ptr++;
            printf("**ptr=%c\n",**ptr);
        system("pause");
        return 0;
   }

上面输出结果:
ptr=Y
ptr=?

不懂指针的人可能就会误以为第二次ptr会输出'o',也就是数组a的第二个元素。下面就用指针内存图解释一下:

img二级指针示意图

*ptr:分解开就是(*ptr)

*ptr取指ptr所指向的内容,ptr指向的地址是指针变量p所占用的内存地址,ptr所指向的内容就是指针p变量。

(ptr) 就是*p,也就是取p所指向的a的首地址的值,所以第一次打印ptr的值是Y。

ptr++,就是得明白指针的++与普通的运算符是不一样的(指针变量的++,代表它所指向的个体的内存地址的递增,且增长单位为指针类型所占字节个数)。

ptr++,也就是 ptr所指向的地址+4(注意是p的地址+4,并非a的地址+4).

ptr++ , 就是ptr所指向的内存地址是指针p的内存地址加4个字节的地址,至于这个地址里存储的对象是啥,谁也不知道。

ptr,ptr新指向内存地址就是下面的这个蓝牙框。 (ptr),就是把ptr当作是一个指针,而*ptr并非一定是一个指针。这样程序可能就会报错。

img指针错误地运算

所以进行指针运算的变量不是ptr,而应该是p(也就是*ptr),所以正确打印第二个字母的方法如下:

    int test05_02()
    {
          char a[20]="You_are_a_girl";
            char *p=a;
            char **ptr=&p;
            printf("**ptr=%c\n",**ptr);
            (*ptr)++;
            printf("**ptr=%c\n",**ptr);
        system("pause");
        return 0;
    }

7.函数指针

和函数头一样,其它不变,只要将函数名改为(*函数别名)即可。如:

定义一个函数
int add(int x ,int y)
{
return x + y;
}
定义上面这种函数的指针
int (*func) (int x ,int y) = add;

8.指针的常见错误

(1) 定义的数据类型 要和你定义的指针类型要相对应
(2) 指针变量未经赋值 不可以直接使用 野指针
(3) 指向不明
看个例子:

int i;
char *result = {'*','*','*','*','*','*','*','*','*','*','*','*','*','*','*','*'};
for (i = 0; i < len; i++) {
        printf("result[%d]=%d ",i,result[i]);
}

上述代码运行后,控制台直接报错:Process finished with exit code -1073741819 (0xC0000005)

然后改进一下代码:

int i;
char *result = {'*','*','*','*','*','*','*','*','*','*','*','*','*','*','*','*'};
char result2[16] = {'*','*','*','*','*','*','*','*','*','*','*','*','*','*','*','*'};
result = result2;
for (i = 0; i < len; i++) {
   printf("result[%d]=%d ",i,result[i]);
}

代码正常运行输出,然后再改动一下代码:

int i;
char * result = malloc(len*sizeof(char));
for (i = 0; i < len; i++) {
   result[i] = '*';
   printf("result[%d]=%d ",i,result[i]);
}

代码正常运行输出
所以使用char *result = {}这句代码是有问题的,指针指向的并且是一个数组,引用指针就会报错。
(4)指针强转
https://www.it1352.com/728643.html
char *to = dest; // dest是 void * 类型 在c++编译会报错error: cannot initialize a variable of type 'char *' with an lvalue of type 'void *',通过显示转换char* to=(char*)dest;

9.深入理解指针

img

&i:表示取变量i的内存地址
int p = &i:表示将变量i的内存地址用int类型指针p存储起来
p:表示指针p所指向的内存地址对应的变量(注意不要理解成变量的值),也就是p=(&i)=i,就把*与&理解成可以相互抵消的意思(虽然这么理解不太准确)

所以对于下面的"修改i的值,会不会影响p"和"修改p的值,会不会影响i",答案是两个都会,因为*p就指向了i,它们是同一块内存地址。

main()
{
      int i = 8;

      int* p = &i;  //p 是用来 存地址 

      //(1)我修改i的值 会不会影响p的值   不会 
      /**
      printf("p的值修改前为%#x\n",p);
       i = 100;
      printf("p的值修改后为%#x\n",p);  **/

      //(2)我修改p的值  会不会影响i       不会 
      /**
       printf("i的值修改前为%d\n",i);
       int j = 100; 
       p = &j; 
       printf("i的值修改后为%d\n",i);  **/

      //(3)我修改i的值 会不会影响 *p      会 
       /**
         printf("*p的值修改前为%d\n",*p);
         i = 200; 
         printf("*p的值修改后为%d\n",*p); **/

      //(4)我修改*p 会不会影响i           会 
        printf("i的值修改前为%d\n",i);
        *p = 100;

        printf("i的值修改后为%d\n",i);
      system("pause");            //执行一个windos系统的外部命令  让dos窗体暂停退出 
}

10.复杂指针类型判断

https://blog.csdn.net/liu100m/article/details/90731422

复杂指针类型判断的基本原则

技巧1:注意*与[与变量结合的优先顺序,p有可能是指针,也有可能是数组,也有可能是函数名。

技巧2:关于“指针类型”的判定:

            1)首先,要确保变量就是一个指针类型。

            2)去掉变量名,剩下的就是指针类型。

技巧3:关于“指针所指向的类型”的判定:

            1)首先,要确保变量就是一个指针类型。

            2)去掉变量名指针的声明符*,剩下的就是指针指向的类型。

1. 关于【int (p)[3]】与【int p[3]】两个的区别

https://blog.csdn.net/sayesan/article/details/39001609
https://blog.csdn.net/ywb201314/article/details/52062059

2.int (p)[3]与 Int (p)( int )

根据上面的判断原则,int(p)[3]与 int(p)(int)都是声明的指针变量。
int(p)[3] 去掉 (p)为 int [3] ,所以指针指向的地址存储的数据类型为数组。
int(p) (int) 去掉 (p)为 int (int) ,所以指针指向的地址存储的数据类型为函数。

11. 指针类型转换

强制类型转换不会改变真实 指针,它们 只是告诉编译器以新的数据类型来看待被指向的数据

12.指针安全问题

使用*指针 = value,给指针所指向的内存区域赋值时,如果这个指向的内存指错了,可能会将一块重要的内存的数据抹掉!

分类: 基础

0 条评论

发表回复

您的电子邮箱地址不会被公开。