头文件
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdlib.h> #include <time.h> #include <string.h> #include <stdio.h> #include <limits.h> #include <ctype.h> #include <math.h>
//**********************************指针***********************************/
1.指针是一个地址 指针是内存中的一个最小单元的编号,也就是地址
2.口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
内存单元编号 == 地址 == 指针
指针就是地址,但是口头语中的指针一般是指针变量
int main() { int* p;//创建一个指针变量 int a = 10;//创建一个a变量 向内存中的栈区空间申请4个字节的空间 这4个字节用来存放10这个数值 //在内存中开辟一块空间 //内存中每个字节都是地址 int *pa = &a;//这里我们对变量a,取出它的地址,可以使用&操作符,表示pa指向的是a,a的类型是int //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个指针变量 //存放在指针变量中的值都会被当作地址处理 *pa = 0x00112233; return 0; }
存放在指针中的任何值在未来使用时都被当成地址处理,每个字节都有自己的地址
一个内存单元的大小是1个字节 产生二进制序列的地址
32位机器-->32根地址线
1/0
00000000000000000000000000000000
00000000000000000000000000000001
00000000000000000000000000000010
00000000000000000000000000000011
.............
01111111111111111111111111111111
10000000000000000000000000000000
10000000000000000000000000000001
.............
11111111111111111111111111111111
有2的32个次方字节的空间 等于4个gb
429967296字节 / 1024 = KB
194304KB / 1024 = 4GB
早期电脑上内存有几个GB就足够储存了
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储
所以一个指针变量的大小就应该是4个字节
在64位的机器上,地址是64个0或者1组成二进制序列,那地址就得用8个字节的空间来存储
所以一个指针变量的大小就应该是8个字节,才能存放一个地址
1.内存被划分为一个个的内存单元,每一个内存单元的大小是一个字节
2.每一个字节的内存单元都有一个编号,这个编号就是地址,地址在C语言中称为指针
3.地址要存储的话 ,存放在指针变量中
4.每个内存单元都有一个唯一的地址来标识
在32位机器上地址的大小是4个字节,所以指针变量的大小也是4个字节
在64位机器上地址的大小是8个字节,所以指针变量的大小也是8个字节
产生地址不需要存起来,只需要存储成为指针的地址
写法:int *p,*q; / int* p,q; 都可以
int main() { int a = 10; int* pa = &a; *pa = 20; printf("a = %d\n", a); return 0; }
//*****************************指针和指针类型*****************************/
int main() { printf("%d\n", sizeof(char*)); printf("%d\n", sizeof(short*)); printf("%d\n", sizeof(int*)); printf("%d\n", sizeof(double*)); //只要是指针变量,大小都只能是:x86是4,x64是8 //指针的类型不同类型的指针用于存放不同类型的变量 return 0; }
int main() { int a = 0x11223344; int* pa = &a; printf("%d\n", *pa); *pa = 0; printf("%d\n", *pa); return 0; }
int main() { int a = 0x11223344; char* p = &a;//类型有警告 char* p = (char*) & a; //*p = 0; printf("%d", *p); return 0; } //指针类型是有意义的,指针类型决定了指针进行解引用操作的时候,访问几个字节 int* 4个字节 char* 1个字节 short* 2个字节
int main() { int a = 0; int* pa = &a; char* pc = (char*) & a; printf("pa = %p\n", pa); printf("pa+1=%p\n", pa + 1); //int *+1类型跳过4个字节 因为是int*类型 printf("pc = %p\n", pc); printf("pc+1=%p\n", pc + 1);//char *类型+1跳过1个字节 因为是char*类型 return 0; }
总结:1.指针的类型决定了指针向前或者向后走一步有多大的距离,指针类型不同,指针+-1向前或向后距离不同
2.指针的类型决定了对指针解引用的时候有多大的权限(能操作几个字节)
eg、char*的指针解引用只能访问一个字节,int*的指针解引用能访问四个字节
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("%d\n", arr[5]); double l = 11.4; double* pa = &l; printf("%p\n", *pa); printf("%p\n", *pa + 1); return 0; }
int main() { int arr[10] = { 0x11223344,0x11223344,0x11223344,0x11223344,0x11223344, 0x11223344,0x11223344,0x11223344,0x11223344,0x11223344 }; int* p = arr; int i = 0; for (i = 0; i < 10; i++) { *p = 0; p++; } return 0; }
char*类型指针一次只修改1个字节
short*类型指针一次修改2个字节
int*类型指针一次修改4个字节
10次循环改变了4*字节数
指针类型 用什么类型的指针访问什么样类型的数据 怎样变化 怎样访问
野指针:指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)
int main() { int* p = (int*)0x11223344; *p;//随机找了个地址,没有确定目标对象 return 0; }
野指针成因:
1.指针未初始化
int main() { int* p;//使用了未初始化的指针 当一个变量不进行初始化的时候 给的地址是随机值 随机值不能作为一个地址 *p = 10; return 0; }
2.指针越界访问 无法读取内存 局部变量arr的内存被破坏了
int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i < 11; i++) { *p = -1; p++; } return 0; }
3.指针指向的空间释放
int* test() { int a = 48; //局部变量,有自己的地址 return &a; } int main() { //0x0012ff40 int* p = test(); //*p是野指针 非法访问 printf("%d\n", *p);//返回了地址,但是占空间回收了,所以p是野指针,一旦访问就是非法访问 printf("%p\n", &p); return 0; }
如何避免野指针
1.指针初始化 如果明确指针应该只想哪里,就初始化正确的地址
不知道指针初始化什么值,为了安全放NULL——0
放一个空指针,不会让指针乱跑
2.小心指针越界
int main
{
int *p = NULL;//养成好的习惯
return 0;
}
3.指针指向空间释放,及时置NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性
指针没有明确对象,如果不是一个空指针,就大概率合法,NULL本质是0,0作为地址时,这个地址用户程序不能访问
如果p不是空指针,大概率可以使用,c++中NULL可以为0,NULL专门赋值指针
指针运算
1.指针+-整数
int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; //使用指针打印数组的内容 int* p = arr;//数组首元素的地址 //arr -> p //arr == p //arr+i == p+i //*(arr+i) == *(p+i) == arr[i] //*(arr+i) == arr[i] //*(i+arr) == i[arr] 数组括号内括号外两元素可以省略 for (int i = 0; i < 10; i++) { printf("%d ", *(p + i));//整形指针一次跳过一个元素 printf("%d ", i[arr]);//数组名是首元素的地址,当知道首元素地址时,可以访问数组所有元素 printf("%d ", arr[i]); printf("%d ", *(arr + i)); printf("%d ", *(p + i)); //以上五种方法,本质是通过首元素的地址加上偏移量,找到数组中的内容 //指针+-整数:通过首元素地址加减偏移量,找到数组中的元素 //指针+-整数:指针的关系运算 //p指向的是数组首元素的地址 //p+i指向的是数组中下标为i的元素的地址 } return 0; }
指针+-整数:指针的关系运算
int main() { float values[N_VALUES]; float* vp; for (vp = &values[0]; vp < &values[N_VALUES];)//指针的关系运算 { *vp++ = 0;//指针加减整数 } for (vp = &values[N_VALUES]; vp > &values[0];)//指针的关系运算 { --vp = 0;//指针加减整数 } return 0; }
C语言标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较
但是不允许与指向前一个元素之前的那个内存位置的指针进行比较
指针-指针
int main() { int arr[10] = { 0 }; //指针-指针的前提是两个指针类型必须相同,两个指针指向同一块区域 //指针-指针得到的是指针和指针之间的元素个数 printf("%d\n", &arr[9] - &arr[0]);//指针-指针 printf("%d\n", &arr[0] - &arr[9]);//小地址减去大地址 return 0; }
模拟实现了strlen
1.计数器
2.递归
size_t my_strlen(char* str) { char* start = str; while (*str != '\0')//'\0'是一个字符 while(*str) { str++; } return str - start;//指针-指针 } int main() { char arr[] = "abcdef"; size_t len = my_strlen(arr); printf("%zd\n", len); return 0; }
指针和数组
指针就是指针,指针变量是一个变量,存放的地址,指针变量的大小是4/8
数组就是数组,可以存放一组数,数组的大小是取决于元素的类型和个数
数组的数组名是数组首元素的地址,地址可以访问指针变量中
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("%p\n", arr); printf("%p\n", &arr[0]); return 0; }
绝大多数情况,数组名表示数组首元素的地址
两个例外:
1.sizeof(数组名),数组名单独放在sizeof内部,数组名表示整个数组,计算的是数组的大小,单位是字节
2.&数组名,数组名表示整个数组,取出的是数组的地址,数组的地址和数组首元素的地址,值是一样的,但是类型和意义不一样
int main() { int arr[10] = { 0 }; printf("%p\n", arr);//int* printf("%p\n", arr + 1);//跳过4个字节 printf("%p\n", &arr[0]);//int* printf("%p\n", &arr[0] + 1);//跳过4个字节 printf("%p\n", &arr);//类型不同,所以+1操作跳过字节数不同 printf("%p\n", &arr + 1); return 0; }
int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i < 10; i++) { printf("%p === %p\n", arr + i, p + i);//两地址相同 } return 0; }
因为数组在内存中连续存放,所以通过指针访问数组名进行偏移,可以访问数组的元素
int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } for (i = 0; i < 10; i++) { printf("%d ", *(arr + i)); } return 0; }
指针是常量的地址,指针可以++,数组不能++
int main() { int arr[10] = { 0 }; int* p = arr; p++; return 0; }
二级指针
int main() { int a = 10; int* p = &a;//p是指针变量,也叫一级指针变量 int** pp = &p;//pp是指针变量,也叫二级指针变量 //int *是类型 p是指针变量 int*** ppp = &pp;//ppp是指针变量,也叫三级指针变量 printf("%p\n", p); printf("%p\n", *p); printf("%p\n", pp); printf("%p\n", *(*pp)); printf("%p\n", ppp); printf("%p\n", *(*(*ppp)));//解引用a printf("%d\n", *(*(*ppp)));//a = 10; return 0; }
7.指针数组
是数组,是存放指针的数组
字符数组:存放字符 char arr[7];
整形数组:存放整形 int arr[6];
指针数组:存放指针 char* arr[5];double* arr2[5];
int main() { //使用指针数组,模拟一个二维数组 int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 2,3,4,5,6 }; int arr3[] = { 3,4,5,6,7 }; //指针数组 int* arr[] = { arr1,arr2,arr3 }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5;j++) { printf("%d ", arr[i][j]); } printf("\n"); } return 0; }