指针是我们C语言中重要的组成部分,很多小伙伴们都觉得指针很难,指针确实不容易,但只要我们清楚的认识到指针的定义,经常拿出来看,多练习指针代码,指针也就变得很容易了。接下来让我们学习指针。
目录
1.什么是指针
1.1指针的定义
指针就是地址,是内存单元中最小的编号;
我们平时口语中所说的指针,通常说的是指针变量,是用来存放内存地址的变量。
例如:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int a = 0;//其中a是一个整型变量,占4个字节,每个字节都有地址 &a;//&取出的是第一个字节的地址,即首地址,较小的地址。 int* pa = &a;//pa就是指针变量,是一种变量,是专门用来存放地址的 printf("%p\n", pa); printf("%p\n", &a); return 0; }
运行出来我们会发现,它们的地址相同,这就是把a的地址存放到了指针变量pa中。
结果如图:
1.2指针的大小
在不同机器上指针的大小是不同的,我们平时所用的是32位和64位机器,这里只说明这俩种机器。
1.32位机器上,地址是32个0或1组成的二进制序列,即32个比特位,所以地址得用4个字节的空间来存储,所以一个指针变量大小是4个字节;
2.在64位机器上,对应64个比特位,一个指针变量大小是8个字节。
2.指针和指针类型
2.1指针类型的意义
指针类型决定了指针进行解引用操作的时候,一次性访问几个字节,决定指针访问的权限大小。
char*指针,解引用一次访问1个字节;
int*指针,解引用一次访问4个字节;
float*指针,解引用一次访问4个字节;
short*指针,解引用一次访问2个字节。
我们已char*为例:
#include<stdio.h> int main() { int a = 0x11223344; char* pa = (char*) & a;//a是int型,需要强制类型转换 int i = 0; for (i = 0; i < 4; i++) { *pa = 0; pa++; } return 0; }
此时我们看到的是a在内存中的地址,然后把a的地址给到pa后,在对pa解引用,我们观察char类型指针一次性访问一个字节,pa++后跳过几个字节。
2.2指针步长
指针类型决定指针的步长,即指针+1到跳过几个字节的探讨。
char*+1 一次跳过一个字节;
int*+1 一次跳过4个字节;
float*+1 一次跳过4个字节;
short*+1 一次跳过2个字节
例如:我们以char*为例
#include<stdio.h> int main() { int b = 0x11223344; char* pc = (char*)&b; int i = 0; for (i = 0; i < 2; i ++) { *pc = 0; pc += 2; } return 0; }
此时我们看到的是b在内存中的地址,然后把b的地址给到pc后,在对pc解引用,pc+1后跳过1个字节。
3.野指针
3.1什么是野指针
野指针即指针指向的位置是不可知的,随机的,不正确的,没有明确限制的指针。
3.2什么会造成野指针
1.指针未初始化
例如:
#include<stdio.h> int main() { int * p;//未初始化,里面是随机值,此时p是野指针 *p=20; return 0; }
2.指针越界访问
例如:
#include<stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i <= sz; i++)//访问11位 { printf("%d ", *p);//通过指针,不会造成数组越界 p++; } printf("\n"); return 0; }
我们可以看到在原有的10个数后,又有一串数字,这就是指针越界访问了,数组只有10个数,而指针却要访问数组的第11位,自然超出范围,多出来的这一位就是野指针。
3.指针指向的空间释放
例如:
#include<stdio.h> int* text() { int a = 10;//先创建了一块空间是局部变量的空间 return &a;//把a的地址给到p,但无法通过p找到&a了,因为return已经把a的这块空间释放了 } int main() { int* p = text();//无法查找得到&a的值,此时p是野指针 printf("%d\n", *p); return 0; }
3.3如何规避野指针
1.指针初始化,即给指针一个明确的指向;
例如:
#include<stdio.h> int main() { int* p = NULL;//NULL空指针,专门用来初始化指针,不知道指针要初始化为何值时,可初始化为NULL *p = 20;//不允许这样直接解引用使用,存在危险 } 我们应该这样写 int main() { int* p = NULL; if (p != NULL) { *p = 20; } return 0; }
2.小心指针越界;
3.指针指向空间释放,及时置为NULL;
4.避免返回局部变量的地址;
5.指针使用前检查有效性。
4.指针运算
4.1指针整数运算
指针整数运算即指针的加减运算,我们看一个例题来理解它,例如求字符串长度;
#include<stdio.h> int my_strlen(char arr[]) { int count = 0; char* start = arr; while (*start != '\0') { count++; start++;//指针进行整数加法运算,每循环一次,地址加一 } return count; } int main() { char arr[] = "abcdefg"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
我们可以再看*vp++=0;这行代码中,因为++优先级高于*,所以先进行++运算,但又因为是后置++,先使用再++,所以代码又可以变为*vp=0;vp=vp+1;这也是指针加整数运算。
指针减指针运算必须满足两个指针在同一块空间,指针-指针得到的是指针之间的元素个数的绝对值。还是求字符串长度的例子,我们换一种求法。
int my_strlen(char *str) { int count = 0; char* start = str; while (*start != '\0') { start++; } return start-str;//得到数组之间的所有元素个数 } int main() { char arr[] = "abcdefg"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
4.2指针关系运算
指针的关系运算就是比较指针的大小
例如:
//我们让N_VALUES=5,然后我们分析下面的代码 for(vp=&values[N_VALUES];vp>&values[0]; ) { *--vp=0; }
我们会发现通过上面vp>%values[0]这个条件,数组中5个位置的值依次被改为0.
这里我们还要注意一点,C语言标准规定,允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较,即允许向后,不允许向前。
例如:
for(vp=&values[N_VALUES-1];vp>=&values[0];vp--) { *vp=0; } //这样会造成vp多向前访问一位,但是没有进行更改超出数组首位的值。更改了才算真正的违反C语言标准
5.指针和数组
区别:
指针和数组是不同对象,指针是一种变量,用来存放地址,大小是4 个字节或者8个字节;
数组是一组类型相同的元素的集合,是可以放多个元素的。
联系:
数组的数组名是数组首元素的地址,地址是可以放在指针变量中的,可以通过指针访问数组。
6.二级指针
二级指针是存放一级指针的地址,例如:
#include<stdio.h> int main() { int a = 10; int* pa = &a;//pa是一级指针变量,存放a的地址 int** ppa = &pa;//ppa是二级指针变量,用来存放一级指针pa的地址; printf("%d\n", *pa); printf("%d\n", **ppa); return 0; }
7.指针数组
指针数组是一种数组,而不是指针;就是把数组的地址存到指针中,例如:
#include<stdio.h> int main() { int a = 10; int b = 20; int c = 30; int d = 40; int f = 50; int* arr[5] = { &a,&b,&c,&d,&f };//这就是指针数组 int i = 0; for (i = 0; i < 5; i++) { printf("%d ", *(arr[i])); } return 0; }
结果如下图:
OK,数组初级内容大题就是这些了,还是要靠大家自己多看多练,拜拜喽,下篇博客见!!!