文章目录
- 第六章指针
- 1.指针是什么?
- 这里我们总结一下:
- 问题:
- 省流版:
- 2.指针和指针类型
- 2.1指针+-整数
- 2.2指针的解引用
- 3.野指针
- 3.1野指针成因
- 3.2如何规避野指针
- 4.指针运算
- 4.1指针+-整数
- 4.2指针-指针
- 4.3指针的关系运算
- 标准规定:
- 5.指针和数组
- 6.二级指针
- 7.指针数组
- 练习:
- 配套练习:
第六章指针
1.指针是什么
2.指针和指针类型
3.野指针
4.指针运算
5.指针和数组
6.二级指针
7.指针数组
1.指针是什么?
指针也就是内存地址,指针变量是用来存放内存地址的变量。
指针变量:可以通过使用&(取地址操作符)取出变量的内存地址,把地址存到一个变量中,这个变量就是指针变量。
#include <stdio.h>int main()
{int a = 5; //在内存中开辟一块空间int* p = &a; //a的内存空间为4个字节,这里将第一个字节的地址存放在p变量中,则p就是一个指针变量。//使用&,取出a的地址放入p中return 0;
}
这里我们总结一下:
指针变量是用来存放地址变量的。(存放在指针中的值都被当成地址处理了)
问题:
一个小的单元是多大?
1个字节。
如何编排地址?
经过计算发现一个字节给一个对应的地址是较合适的。
对于32位的机器,在寻找地址的时候产生高电平(1)和低电平(0)。
那么32位的机器,产生的地址就会是:
00000000 00000000 00000000 0000000000000000 00000000 00000000 0000000100000000 00000000 00000000 0000001000000000 00000000 00000000 00000011........11111111 11111111 1111111 111111111
这里有2^32个地址。
到这里其实我们就可以明白:
-
在32位的机器中,地址是32个0或1组成二进制序列,地址就要用4个字节的空间来进行存储,所以一个指针变量大小就应该为4个字节。
-
那么如果在64位的机器上,那一个指针变量的大小就是8个字节。
省流版:
-
指针用来存放地址的,地址是唯一能够标识一块地址空间的。
-
指针的大小在32位平台为4个字节,在64位平台为8个字节。
2.指针和指针类型
char* pa = NULL;int* pb = NULL;short* pc = NULL;long* pd = NULL;float* pe = NULL;double* pf = NULL;
可以看出,指针的定义方式是:type + *。
char*类型的指针是为了存放char类型变量的地址。
int*类型的指针是为了存放int类型变量的地址。
short*类型的指针是为了存放short类型变量的地址。
long*类型的指针是为了存放long类型变量的地址。
float*类型的指针是为了存放float类型变量的地址。
double*类型的指针是为了存放double类型变量的地址。
指针的类型有什么意义?(看以下内容)
2.1指针±整数
int x = 5;int* pa = &x;char* pb = (char*)&x;printf("%p\n",pa);printf("%p\n", pa+1);printf("%p\n", pb);printf("%p\n", pb+1);
省流:指针的类型决定了指针向前或者向后一步的距离有多大。
2.2指针的解引用
int i = 0x12223242;char* pa = (char*)&i; int* pb = &i; *pa = 0; //注意观察调试的过程中内存的变化*pb = 0; //注意观察调试的过程中内存的变化//调试->窗口->内存
总结:
指针的类型决定了对指针解引用的时候有多大的权限(就是能够操作几个字节)。
如:char *的类型解引用就只能访问一个字节,而int *类型解引用能访问四个字节。
3.野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1野指针成因
1.指针未初始化
#include <stdio.h>int main()
{int *n; //局部指针变量未初始化,默认是随机值*n = 8;return 0;
}
2.指针越界访问
#include <stdio.h>int main()
{int arr[10] = { 0 };int* pa = arr;for (int i = 0;i < 11;i++){//当指针指向的范围超过arr的范围时,p就是野指针*(pa++) = i;}return 0;
}
3.指针指向的空间释放
#include <stdio.h>int* test()
{int a = 2;return &a;
}int main()
{int* p = test();return 0;
}
3.2如何规避野指针
1.指针初始化
2.小心指针越界
3.指针指向空间释放即使置NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性
#include <stdio.h>int main()
{int* p = NULL;int a = 2;p = &a;if (p != NULL){*p = 4;}return 0;
}
4.指针运算
-
指针±整数
-
指针-指针
-
指针的关系运算
4.1指针±整数
#include <stdio.h>#define x_num 5int main()
{float num[x_num];float* p;for (p = &num[0]; p < &num[x_num];){*p++ = 0;}return 0;
}
4.2指针-指针
int mystrlen(char* x)
{char* n = x;while (*n != '\0'){n++;}return n - x;
}
4.3指针的关系运算
for (p = &num[x_num]; p > &num[0];){*--p = 0;}
简化代码,修改如下:
for (p = &num[x_num]; p > &num[0]; p--){*p = 0;}
绝大部分的编译器上完成任务是没什么问题的,但还是避免这么些,因为标准并不能确保它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5.指针和数组
#include <stdio.h>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;
}
可知数组名和数组首元素的地址相同。
结论:数组名表示的是数组首元素的地址。(2种情况除外,之前的数组章节有详细讲解)
那么这样写代码是可以的:
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
既然数组名可以被当成地址存放到一个指针中,那么就可以直接通过指针来访问数组。
#include <stdio.h>int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int i = 0;int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *(p+i));}return 0;
}
6.二级指针
int a = 2;int* pa = &a;int** ppa = &pa;
二级指针的运算:
*ppa通过对ppa中的地址进行解引用,*ppa其实访问的就是pa。
int b = 4;
*ppa = &b //等价于 pa = &b;
**ppa先通过pa,进行解引用操作,访问的就是a。
**ppa = 30;
//等价于*pa = 30;
//等价于
7.指针数组
指针数组是存放指针的数组。
int *arr[5];
arr是一个数组,里面有五个元素,每个元素都是整型指针。
练习:
1.题目名称:计算求和
题目内容:求Sn=a+aa+aaa+aaaa+aaaaa的前五项之和,其中a是一个数字。
例如:2+22+222+2222+22222
#include <stdio.h>int main()
{int a = 0;int n = 0;scanf("%d %d", &a, &n);int sum = 0;int ret = 0;int i = 0;for (i = 0; i < n; i++){ret = ret * 10 + 2;sum += ret;}printf("sum的值为:%d", sum);return 0;
}
2.题目名称:打印数组内容
题目内容:写一个函数打印arr数组的内容,不使用数组下标,使用指针。arr是一个整形一维数组。
#include <stdio.h>void print(int* p, int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ",*(p+i));}printf("\n");
}int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);print(arr, sz);return 0;
}
3.题目名称:打印水仙花数
题目内容:求出0~100000之间的所有”水仙花数“并输出。
“水仙花数”是指一个n位数,其各位数字的n次方之和等于该数本身。
如:153=13+53+3^3,则153是一个水仙花数。
#include <stdio.h>
#include <math.h>int main()
{int i = 0;for (i = 0; i <= 100000; i++){int n = 1;int tmp = i;while (tmp / 10){n++;tmp = tmp / 10;}tmp = i;int sum = 0;while (tmp){sum += pow(tmp % 10, n);tmp = tmp / 10;}if (sum == i){printf("%d ", sum);}}return 0;
}
4.题目名称:字符串逆序
题目内容:写一个函数,可以逆序一个字符串的内容。
#include <stdio.h>void reverse(char* arr, int len)
{char* left = arr;char* right = arr + len - 1;while (left < right){char tmp = *left;*left = *right;*right = tmp;left++;right--;}
}int main()
{char arr[] = "abcdef";int len = strlen(arr);reverse(arr, len);printf("%s", arr);return 0;
}
5.题目名称:打印菱形
#include <stdio.h>int main()
{int i = 0;int line = 0;scanf("%d", &line);for (i = 0; i < line; i++){int j = 0;for (j = 0; j<line -1 - i; j++){printf(" ");}for (j = 0; j < 2 * i + 1; j++){printf("*");}printf("\n");}for (i = 0; i < line - 1; i++){int j = 0;for (j = 0; j <= i; j++){printf(" ");}for (j = 0; j < 2 * (line - 1 - i) - 1; j++){printf("*");}printf("\n");}return 0;
}
6.题目名称:喝汽水问题
题目内容:喝汽水,一瓶汽水1元,2个空瓶可以换一瓶汽水,给20元,可以多少汽水。
#include <stdio.h>int main()
{int money = 20;int total = money;;int empty = money;while (empty >= 2){total += empty / 2;empty = empty / 2 + empty % 2;}printf("%d", total);return 0;
}
上一章:C语言入门学习 — 5.操作符
配套练习:
C语言练习题110例(一)
C语言练习题110例(二)
C语言练习题110例(三)
C语言练习题110例(四)
C语言练习题110例(五)
C语言练习题110例(六)
C语言练习题110例(七)
C语言练习题110例(八)
C语言练习题110例(九)
C语言练习题110例(十)
C语言练习题110例(十一)