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"); }