本期内容主要是初阶指针!
指针就是变量,用来存放地址的变量。
指针在32位平台上是4字节,在64位平台上是8字节。
指针类型的意义
指针类型决定了:指针解引用的权限有多大(整型指针解引用访问4个字节,字符型指针解引用只能访问1个字节)
指针类型决定了,指针走一步,能走多远(步长)
例题1:
int arr[10] = {
0 };
int *p = arr;
char *pc = arr;
printf("%p\n", p);
printf("%p\n", p+1);//加了4,因为int4字节
printf("%p\n", pc);
printf("%p\n", pc+1);//加了1,因为char1字节
输出解释:p+1加了4,因为int4字节;pc+1加了1,因为char1字节
例题2:
int a = 0x11223344;
char* pc = &a;
*pc = 0;
int* pa = &a;
*pa = 0;//只改变一个字节
对比调试窗口会发现*pa=0之后,a变成0x11223300,只改变了一个字节
野指针
概念:野指针就是指针指向的位置是不可知的。
指针定义时不进行初始化的话,默认是随机值(养成初始化习惯),例如下例的P就是野指针:
int* p;//p是一个局部的指针变量,局部变量不初始化的话,默认是随机值
*p = 20;//非法访问内存了
指针越界也会造成野指针,如下例:
//越界访问
int arr[10] = {
0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
*p = i;
p++;
}
指针指向的空间释放,如下例:
int* test()
{
int a = 10;
return &a;
}
int main()
{
int*p = test();
*p = 20;
return 0;
}
进入test函数的时候a的空间创建了,但是出来的时候就销毁了,还给操作系统了,返回主函数就出问题了,这块空间不属于你了。
总结:如何规避野指针?
1.指针初始化
2.小心指针越界
3.指针指向空间释放即使置NULL
4.指针使用之前检查有效性
指针运算
指针±整数
指针-指针
指针的关系运算
例题1(指针±整数):
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int* pend = arr + 9;
while (p<=pend)
{
printf("%d\n", *p);
p++;
}
return 0;
}
例题2(指针-指针):
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,10 };
char c[5];
printf("%d\n", &arr[9] - &c[0]);//err,不对的哦,两个指针指向不同空间
printf("%d\n", &arr[9] - &arr[0]);//这个输出9
return 0;
}
注释:指针-指针得到的是俩个间元素个数,但是指针和指针相减的前提是两个指针指向同一块空间。
例题3(求字符串长度):
int my_strlen(char* str)
{
char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
//strlen(); - 求字符串长度
//递归
int len = my_strlen("abc");//这里"abc"传入的只有首字母a的地址
printf("%d\n", len);
return 0;
}
指针要遍历可以写成*p++,不推荐写成 *–p,因为标准规定,允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
以下数组和指针内容一样:
arr[2] <==> *(arr+2) <==> *(p+2) <==> *(2+p) <==> *(2+arr) == 2[arr]
问:以下系统中,int类型占几个字节,指针占几个字节,操作系统可以使用的最大内存空间是多大:( )
32位系统下:
int占4个字节,指针表示地址空间个数,总共有2^32个,故占4个字节
64位系统下:
int占4个字节,指针表示地址空间个数,总共有2^64个,故占8个字节
二级指针
int main()
{
int a = 10;
int* pa = &a;//pa是指针变量,一级指针
//ppa就是一个二级指针变量
int ** ppa = &pa;//pa也是个变量,&pa取出pa在内存中起始地址,第二个*说明ppa是指针
int*** pppa = &ppa;
return 0;
}
注释:*ppa== pa; *pa== a; **ppa==a。以此类推可以做出三级四级指针,但不建议!
指针数组 - 存放指针的数组
int arr[10];//整形数组 - 存放整形的数组就是整形数组
char ch[5];//字符数组 - 存放的是字符
int* parr[5];//整形指针的数组
char* pch[5];
作业练习
作业练习1:编程实现:两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同?
输入例子:1999 2299
输出例子:7
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int m = 1999;
int n = 2000;
int tmp = m ^ n;
int count = 0;
while (tmp)
{
tmp = tmp & (tmp - 1);
count++;
}
printf("%d", count);
return 0;
}
注释:tmp = tmp & (tmp - 1)的作用就是每次都将最右边那位清零。
作业练习2:获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列
/*
思路:
1. 提取所有的奇数位,如果该位是1,输出1,是0则输出0
2. 以同样的方式提取偶数位置
检测num中某一位是0还是1的方式:
1. 将num向右移动i位
2. 将移完位之后的结果与1按位与,如果:
结果是0,则第i个比特位是0
结果是非0,则第i个比特位是1
*/
int main()
{
int num = 2000;
printf("二进制下所有位:");
for (int i = 31; i >= 1; i -= 1)
{
printf("%d ", (num >> i) & 1);
}
printf("\n");
printf("二进制下奇数位:");
for (int i = 31; i >= 1; i -= 2)
{
printf("%d ", (num >> i) & 1);
}
printf("\n");
printf("二进制下偶数位:");
for (int i = 30; i >= 0; i -= 2)
{
printf("%d ", (num >> i) & 1);
}
printf("\n");
return 0;
}
作业练习3:统计二进制中1的个数
/*
思路:
一个int类型的数据,对应的二进制一共有32个比特位,可以采用位运算的方式一位一位的检测,具体如下
*/
int main()
{
int num = 1999;
int a = 0;
int count = 0;
for (int i = 32; i >= 1; i -= 1)
{
a = (num >> i) & 1;
if (a == 1)
count++;
}
printf("二进制下所有位中1的个数:");
printf("%d ", count);
printf("\n");
return;
}
作业练习4:下列代码结果是
#include <stdio.h>
int main()
{
int arr[] = {
1,2,3,4,5};
short *p = (short*)arr;
int i = 0;
for(i=0; i<4; i++)
{
*(p+i) = 0;
}
for(i=0; i<5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
解析:
arr数组在内存中的存储格式为:
0x00ECFBF4: 01 00 00 00
0x00ECFBF8: 02 00 00 00
0x00ECFBFC: 03 00 00 00
0x00ECFC00: 04 00 00 00
0x00ECFC04: 05 00 00 00
指针p的类型为short*类型的,因此p每次只能所有两个字节,for循环对数组中内容进行修改时,一次访问的是:
arr[0]的低两个字节,arr[0]的高两个字节,arr[1]的低两个字节,arr[1]的高两个字节,故改变之后,数组中内容如下:
0x00ECFBF4: 00 00 00 00
0x00ECFBF8: 00 00 00 00
0x00ECFBFC: 03 00 00 00
0x00ECFC00: 04 00 00 00
0x00ECFC04: 05 00 00 00
故最后打印:0 0 3 4 5
作业练习5:下面代码输出的结果是
#include <stdio.h>
int main()
{
int a = 0x11223344;
char *pc = (char*)&a;
*pc = 0;
printf("%x\n", a);
return 0;
}
解析:
假设,a变量的地址为0x64,则a变量在内存中的模型为:
0x64| 44 |
0x65| 33 |
0x66| 22 |
0x67| 11 |
char*类型的指针变量pc指向只能指向字符类型的空间,如果是非char类型的空间,必须要将该空间的地址强转为char*类型。
char *pc = (char*)&a; pc实际指向的是整形变量a的空间,即pc的内容为0x64,即44,
*pc=0,即将44位置中内容改为0,修改完成之后,a中内容为:0x11223300
作业练习6:下面代码输出的结果是(难!!!)
#include <stdio.h>
int i;
int main()
{
i--;
if (i > sizeof(i))
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
解析:
C语言中,0为假,非0即为真。
全局变量,没有给初始值时,编译其会默认将其初始化为0。
i的初始值为0,i--结果-1,i为整形,sizeof(i)求i类型大小是4,按照此分析来看,结果应该选择B,但是sizeof的返回值类型实际为无符号整形,因此编译器会自动将左侧i自动转换为无符号整形的数据,-1对应的无符号整形是一个非常大的数字,超过4或者8,故实际应该输出>
这道题其实很隐蔽,真是虾仁猪心!!!
作业练习7:求Sn=a+aa+aaa+aaaa+aaaaa的前5项之和,其中a是一个数字
例如:2+22+222+2222+22222
/*
思路:
通过观察可以发现,该表达式的第i项中有i个a数字,因此:
假设第i项为temp,则第i+1项为temp*10+a
具体参考以下代码
*/
int main()
{
int a = 0;
int i = 0;
int sum = 0;
int tmp = 0;
scanf("%d", &a);
for (i = 0; i < 5; i++)
{
tmp = tmp * 10 + a;
sum += tmp;
}
printf("%d\n", sum);
return 0;
}
作业练习8:写一个函数打印arr数组的内容,不使用数组下标,使用指针。
arr是一个整形一维数组。
int main()
{
int arr[] = {
1,2,3,4,5,6,7,8,9,10 };
// 分析:因为数组中存储的元素类型是int类型的,因此只要给一个int的指针,依次取索引数组中的每个元素即可
int* p = arr; // 数组名代表数组首元素的地址
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", *p); // *p: 取到p所指向位置的元素
++p; // 获取p的下一个位置
}
return 0;
}
作业练习9:求出0~100000之间的所有“水仙花数”并输出。
“水仙花数”是指一个n位数,其各位数字的n次方之和确好等于该数本身,如:153=13+53+3^3,则153是一个“水仙花数”。
/*
思路:
此题的关键在于只要知道判断一个数据是否为水仙花数的方式,问题就迎刃而解。假定给定一个数据data,具体检测方式如下:
1. 求取data是几位数
2. 获取data中每个位置上的数据,并对其进行立方求和
3. 对data中每个位上的数据立方求和完成后,在检测其结果是否与data相等即可,
相等:则为水仙花数
否则:不是
具体实现参考以下代码。
*/
int main()
{
int i = 0;
for(i=0; i<=999999; i++)
{
int count = 1;
int tmp = i;
int sum = 0;
//判断i是否为水仙花数
//1. 求判断数字的位数
while(tmp/10)
{
count++;
tmp = tmp/10;
}
//2. 计算每一位的次方和
tmp = i;
while(tmp)
{
sum += pow(tmp%10, count);
tmp = tmp/10;
}
//3. 判断
if(sum == i)
printf("%d ", i);
}
return 0;
}
转载:https://blog.csdn.net/weixin_48953972/article/details/116757611