目录
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]);
}
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的第二个元素。下面就用指针内存图解释一下:
二级指针示意图
*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并非一定是一个指针。这样程序可能就会报错。
指针错误地运算
所以进行指针运算的变量不是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.深入理解指针
&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 条评论