文章目录
- 深入了解指针(一)
- 1.了解内存和地址
- 2.指针变量和地址
- 2.1取地址操作符(&)
- 2.2指针变量和解引⽤操作符(*)
- 3. 指针的大小
- 4.指针变量类型的意义
- 5. const修饰指针
- 5.1使用const修饰变量
- 5.2当const修饰指针
- 6. 指针运算
- 6.1指针+-整数
- 6.2指针-指针
- 6.3指针的关系运算
- 7. 野指针
- 7.1. 指针未初始化
- 7.2指针的越界
- 7.3 指针指向的空间释放
- 深入了解指针(二)
- 1-数组名的了解
- 2. 使⽤指针访问数组
- 3. ⼀维数组传参的本质
- 4. 冒泡排序
- 5.二级指针
- 6.指针数组
- 7. 指针数组模拟⼆维数组
- 深入了解指针(三)
- 1.字符指针变量
- 2.数组指针变量
- 2.1了解数组指针变量的概念
- 2.2数组指针变量初始化
- 3.二维数组传参的本质
- 4.函数指针变量
- 4.1 函数指针变量的创建
- 4.2 函数指针变量的使⽤
- 5.函数指针数组
- 6.转移表
- 深入了解指针(四)
- 1.回调函数
- 2.qrost的使用
- 3.qrost函数的模拟实现
- 深入了解指针(五)
- 1. sizeof和strlen的对⽐
- 1.1 sizeof
- 1.2 strlen
- 1.3 sizeof和strlen的对⽐
- 2. 数组和指针笔试题分析
- 2.1一维数组
- 2.2 字符数组
- 2.3 ⼆维数组
- 3. 指针运算笔试题分析
深入了解指针(一)
1.了解内存和地址
内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。
⼀个字节空间⾥⾯能放8个⽐特位,一个比特位可以储存2进制的位1或者0。
1Byte = 8bit
1KB = 1024Byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB
每个内存单元也都有⼀个编号,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字叫:指针。
内存单元的编号=地址=指针
2.指针变量和地址
2.1取地址操作符(&)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{ //利用&获取a的地址int a = 20;printf("%p", &a);return 0;
}
在不同的环境下地址也会不同。
2.2指针变量和解引⽤操作符(*)
通过取地址操作符(&)拿到的地址是⼀个数值,⽐如:00FAFC00,这个数值有时候也是需要存储起来,⽅便后期再使⽤的,那我们把这样的地址值存放在哪⾥呢?答案是:指针变量中。
#include <stdio.h>
int main()
{int a = 20;int * pa = &a;//取出a的地址并存储到指针变量pa中return 0;
解引⽤操作符(*)。
#include<stdio.h>
int main()
{ //利用&获取a的地址int a = 20;int* p = &a;printf("%d\n", *p);*p = 10;printf("%d", *p);return 0;
}
p是p中存放的地址,改变p相当于改变a的变量。
3. 指针的大小
32位平台下地址是32个bit位(即4个字节)
64位平台下地址是64个bit位(即8个字节)
int main()
{printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(float*));printf("%zd\n", sizeof(double*));printf("%zd\n", sizeof(short*));return 0;
}
X86环境输出结果:
X64环境输出结果:
4.指针变量类型的意义
其实指针类型是有特殊意义的,指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。
int main()
{int a = 20;int* pi = &a;char* pc = (char *)&a;printf("%p\n", &a);printf("%p\n", pc);printf("%p\n", pc+1);printf("%p\n", pi);printf("%p\n", pi+1);
}
5. const修饰指针
5.1使用const修饰变量
x的本质还是变量,因为有const修饰,编译器在语法上不允许修改这个变量。
5.2当const修饰指针
const 放在*的左边:const int * p / int const * p;
/ 意思:表示指针指向的内容,不能通过指针来改变了。但是指针变量本身的值是可以改的。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{int n = 0;printf("n = %d\n", n);//const int * p = &n; int const *p=&n;/*p = 20;n=20;printf("n = %d\n", n);return 0;
}
const 放在*的右边:int * const p;
意思:表示指针变量p本身不可以修改了,但是指针指向的内容是可以通过指针变量来改变的。
#include<stdio.h>
int main()
{int n = 0;printf("n = %d\n", n);int * const p = &n;// int num = 20;// p = #//printf("n = %d\n", num);*p = 20;printf("n = %d\n", n);return 0;
}
当*左右都有const
const int * const p;
意思:指针变量p不能被修改,指针变量p指向的内容也不能被修改。
6. 指针运算
指针的基本运算有三种,分别是:
• 指针±整数
• 指针-指针
• 指针的关系运算
6.1指针±整数
指针+整数
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];//*p获取arr数组的首地址int s = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < s; i++){printf("%d ", *p+i);}return 0;
}
指针-整数
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int s = sizeof(arr) / sizeof(arr[0]);int* p = &arr[s - 1];for (int i = 0; i <s; i++){printf("%d ", *p-i );}return 0;
}
6.2指针-指针
指针之间的运算的前提是两个指针指向同一块空间
不能相加只能相减
指针-指针本质是地址-地址获取的绝对值是指针和指针之间的元素个数。
6.3指针的关系运算
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;//*p获取arr数组的首地址int s = sizeof(arr) / sizeof(arr[0]);while (p < &arr[s]){printf("%d ", *p);p++;}return 0;
}
7. 野指针
7.1. 指针未初始化
7.2指针的越界
7.3 指针指向的空间释放
深入了解指针(二)
1-数组名的了解
首先单独获取arr的地址会发现与数组的首元素地址一样
数组名就是数组⾸元素(第⼀个元素)的地址。
int main()
{int arr[5] = { 1,2,3,4,5 };printf("%p\n",arr);printf("%p", &arr[0]);return 0;
}
其实数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:
1• sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,
单位是字节
而在sizeof 中arr代表的是整个数组
int main()
{int arr[5] = { 1,2,3,4,5 };int n = sizeof(arr);printf("%d", n);return 0;
}
2• &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素
的地址是有区别的)
int main()
{int arr[5] = { 1,2,3,4,5 };printf("%p\n", arr);printf("%p\n", &arr);printf("%p\n", arr+1);printf("%p\n", &arr+1);return 0;
}
2. 使⽤指针访问数组
了解清楚地址就可以轻松访问数组内容。
可以使⽤arr[i]可以访问数组的元素,那p[i]是否也可以访问数组呢?
#include <stdio.h>
int main()
{int i = 0;int arr[10] = { 0 };int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);for ( i = 0; i < sz; i++){scanf("%d",p+i);//p+i可以换成&arr[i]}for ( i = 0; i < sz; i++){printf("%d ", *(p+i));//*(p+i)可以换成arr[i]}return 0;
}
3. ⼀维数组传参的本质
void szcc(int arr[])
{int sz = sizeof(arr) / sizeof(arr[0]);for (int a = 0; a < sz; a++){printf("\n%d ", arr[a]);}
}
int main()
{int arr[5] = { 1,2,3,4,5 };int sz = sizeof(arr) / sizeof(arr[0]);for (int a = 0; a < sz; a++){printf("%d ", arr[a]);}szcc(arr);return 0;
}
在32位环境下
函数形参的部分理论上应该使⽤指针变量来接收⾸元素的地址。那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的⼤⼩(单位字节)⽽不是数组的⼤⼩(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的
所以⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
void test1(int arr[])//参数写成数组形式,本质上还是指针
{
printf("%d\n", sizeof(arr));
}
void test2(int* arr)//参数写成指针形式
{printf("%d\n", sizeof(arr));//计算⼀个指针变量的⼤⼩
}
4. 冒泡排序
void mppx(int arr[], int n)
{for (int i = 0; i < n - 1; i++){for (int j = 0;j<n-i-1;j++){if (arr[j] > arr[j + 1]){int temp=arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}
void print(int arr[], int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}
int main() //冒泡排序
{int arr[] = { 10,4,5,6,8,7,9,1,2,3 };int n = sizeof(arr) / sizeof(arr[0]);mppx(arr, n);print(arr, n);return 0;
}
再优化一些
减少重复排序
void mppx(int arr[], int n)
{for (int i = 0; i < n - 1; i++){int flag = 0;for (int j = 0;j<n-i-1;j++){if (arr[j] > arr[j + 1]){flag = 1;int temp=arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}if (flag == 0)//这⼀趟没交换就说明已经有序,后续不需要排序了直接break跳出循环。break;}}
void print(int arr[], int n)
{for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}
int main() //冒泡排序
{int arr[] = { 10,4,5,6,8,7,9,1,2,3 };int n = sizeof(arr) / sizeof(arr[0]);mppx(arr, n);print(arr, n);return 0;
}
5.二级指针
二级指针通俗来讲就是指针变量的地址
int main()
{int a = 10;int* p = &a;//p是指针变量,是一级指针变量int** pa = &p;//*p 是二级指针变量printf("%d", **pa);//输出为10return 0;
}
三级指针就是二级指针便的地址(int ***paa=&pa)
以此类推四、五……指针。
6.指针数组
int main()
{int a = 1;int b = 2;int c = 3;int* arr[] = { &a,&b,&c };//指针数组 或者{a,b,c}for (int i = 0; i < 3; i++){printf("%d ", *(arr[i]));}return 0;
}
7. 指针数组模拟⼆维数组
int main()
{int arr1[5] = { 0,1,2,3,4 };int arr2[5] = { 6,7,8,9,10 };int arr3[5] = { 11,12,13,14,15 };int* arr[3] = { arr1,arr2,arr3 };//||{&arr1,&arr2,&arr3}for (int i = 0; i < 3; i++){for (int j = 0; j < 5; j++){printf("%d ", arr[i][j]);}}return 0;
}
深入了解指针(三)
1.字符指针变量
int main()
{char arr[] = "abcdef";char* p = arr;printf("%c\n", *p);const char* ps = "abcdef";//这⾥是把⼀个字符串放到ps指针变量里了吗?printf("%s\n", ps);printf("%c\n", arr[2]);return 0;
}
代码 const char* ps = “abcdef”; 特别容易让同学以为是把字符串 abcdef放到字符指针 ps ⾥了,但是本质是把字符串 abcdef. ⾸字符的地址放到了ps中。
这⾥arr3和arr4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域,当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以arr1和arr2不同,arr3和arr4相同。
int main()
{char arr1[] = "abcdef";char arr2[] = "abcdef";const char *arr3= "abcdef";const char *arr4= "abcdef";if (arr1 == arr2)printf("arr1和arr2是内存相同的\n");elseprintf("arr1和arr2是内存不同的\n");if (arr3 == arr4)printf("arr3和arr4是内存相同的\n");elseprintf("arr3和arr4是内存不同的\n");return 0;
}
2.数组指针变量
2.1了解数组指针变量的概念
整形指针变量 ——变量——存放的是整形的地址
字符指针变量——变量——存放的是字符的地址
数组指针——变量——存放的是数组的地址
int (p)[5]
诠释: * 先与p结合 说明 p是一个指针变量 , [5]代表的是存放的是5个整形的数组,所以p是一个指针,指向数组,这就是一个数组指针。
注意:[]的优先级比高所以要用()确保p先于*结合。
2.2数组指针变量初始化
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{int arr[3] = { 0 };int(*p)[3] = &arr;//得到的是数组的地址return 0;
}
p与&arr的类型一样
3.二维数组传参的本质
二维数组传参,形参是数组时
void xs(int a[3][4], int b, int c)
{for (int i = 0; i < b; i++){for (int j = 0; j < c; j++){printf("%d ", a[i][j]);}}
}
int main()
{int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };xs(arr, 3, 4);return 0;
}
根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [4] ,所以第⼀⾏的地址的类型就是数组指针类型 int(*)[4] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:
void xs(int(*p)[4], int b, int c)
{for (int i = 0; i < b; i++){for (int j = 0; j < c; j++){printf("%d ", *(*(p+i)+j));}}
}
int main()
{int arr[3][4] = { 1,2,3,4,5,6,7,8,9 ,10,11,12};xs(arr, 3, 4);return 0;
}
4.函数指针变量
变量可以取地址、数组可以取地址、函数也可以取地址。
写一个简单函数来看看
int mul(int x, int y)
{return x * y;
}
int main()
{printf("%p\n", mul);printf("%p\n", &mul);return 0;
}
函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名的⽅式获得函数的地址。
如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针⾮常类似。如下:
4.1 函数指针变量的创建
int (*p)(x, y) = mul;//x和y写上或者省略都是可以的
int —— p指向函数的返回类型
(*p)—— 函数指针变量名
(int x, int y)—— p指向函数的参数类型和个数的交代
4.2 函数指针变量的使⽤
int mul(int x, int y)
{return x * y;
}
int main()
{int (*p)(x, y) = mul;//x和y写上或者省略都是可以的int ret = (*p)(2, 4);printf("%d\n", ret);printf("%d\n", (*p)(2, 3));printf("%d\n", p(3, 4));return 0;
}
5.函数指针数组
存放函数指针的数组
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int add(int x, int y)
{return x + y;
}
int sub(int x, int y)
{return x - y;
}int main()
{int (*p1)(int x, int y) = add;int (*p2)(int x, int y) = sub;//在这里p1与p2的类型相同我们可以把其放在数组int (*p[4])(int, int) = { add,sub };//这里[4]代表函数指针数组大小可以存放4个函数指针。printf("%d\n",p[0](20, 30));printf("%d\n", p[1](20, 30));return 0;
}
6.转移表
函数指针数组的应用
首先使用数组的方式实现一个简易的计算功能
void meau()
{printf(" 开始选择 \n");printf("**** 1.add 2.sub ****\n");printf("**** 3.mul 4.div ****\n");printf("**** 0.exit ****\n");printf(" \n");}
int add(int a, int b)//加法
{return a + b;
}
int sub(int a, int b)//减法
{return a - b;
}
int mul(int a, int b)//乘法
{return a * b;
}
int div(int a, int b)//除法
{return a /b;
}
void calc(int(*p)(int a, int b))
{int a = 0; int b = 0; int c = 0;printf("请输入两个整数进行运算\n");scanf("%d %d", &a, &b);c = p(a, b);printf("运算结果=%d\n", c);
}
int main()
{int input = 1;
do{int a = 0; int b = 0; int c = 0;meau();scanf("%d", &input);switch(input){case 1:printf("请输入两个整数进行运算\n");scanf("%d %d", &a, &b);c = add(a, b);printf("运算结果=%d\n", c);break;case 2:printf("请输入两个整数进行运算\n");scanf("%d %d", &a, &b);c = sub(a, b);printf("运算结果=%d\n", c);break;case 3:printf("请输入两个整数进行运算\n");scanf("%d %d", &a, &b);c = mul(a, b);printf("运算结果=%d\n", c);break;case 4:printf("请输入两个整数进行运算\n");scanf("%d %d", &a, &b);c = div(a, b);printf("运算结果=%d\n", c);break;case 0:printf("退出计算\n"); break;default :printf("选择错误请重新选择/n"); break;}
} while (input);return 0;
}
用函数指针数组的方式实现一个转移的效果
void meau()
{printf(" 开始选择 \n");printf("**** 1.add 2.sub ****\n");printf("**** 3.mul 4.div ****\n");printf("**** 0.exit ****\n");printf(" \n");}
int add(int a, int b)//加法
{return a + b;
}
int sub(int a, int b)//减法
{return a - b;
}
int mul(int a, int b)//乘法
{return a * b;
}
int div(int a, int b)//除法
{return a / b;
}
void calc(int(*p)(int a, int b))
{int a = 0; int b = 0; int c = 0;printf("请输入两个整数进行运算\n");scanf("%d %d", &a, &b);c = p(a, b);printf("运算结果=%d\n", c);
}
int main()
{int input = 1;do{meau();scanf("%d", &input);switch (input){case 1:calc(add);break;case 2:calc(sub);break;case 3:calc(mul);break;case 4:calc(div);break;case 0:printf("退出计算\n");break;default:printf("选择错误请重新选择/n");break;}} while (input);return 0;
}
深入了解指针(四)
1.回调函数
回调函数就是⼀个通过函数指针调⽤的函数。
如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现⽅直接调⽤,⽽是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进⾏响应。
在上面转移表的实现中已经使用的回调函数接下来给诠释一下
在这里列如 int add(int x,int y)——到void calc(int(*p)(int a, int b))——c=p(a,b);
这就是一次回调函数的实现。
2.qrost的使用
qrost—库函数—可以实现任意数据类型的快速排序。
void qsort(void* base, //base中存放的是待排序数组的第一个元素的地址
size_t num, //num存放的是base指向的数组中的元素个数
size_t size, //size是base指向的数组中一个元素的长度,单位是字节
int (compar)(const voidp1, const voidp2) //函数指针-指向了一个比较函数,这个比较函数是用来比较数组中的两个元素的
//如果p1指向的元素大于p2指向的元素,那么函数返回>0的数字
如果p1指向的元素等于p2指向的元素,那么函数返回0
如果p1指向的元素小于p2指向的元素,那么函数返回<0的数字 );
void 类型的指针不能解引用操作符,也不能+/-整数的操作
这种指针变量一般是用来存放地址的
使用之前要强制类型转换成想要的类型
char类型:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
void com(const void* p1, const void* p2)
{return (*(char*)p1 - *(char*)p2);
}
int main()
{ //qsort函数的实现char arr[] = "fd gas";size_t a = strlen(arr);qsort(arr, a, sizeof(arr[0]), com);for (int i = 0; i < a; i++){printf("%c ", arr[i]);}return 0;
}
int 类型:
void com(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
int main()
{ //qsort函数的实现int arr[] = {2,6,8,4,5,3,1,7,9,10};size_t a = sizeof(arr) / sizeof(arr[0]);qsort(arr, a, sizeof(arr[0]), com);for (int i = 0; i < a; i++){printf("%d ", arr[i]);}return 0;
}
结构体类型:
#include<stdio.h>
struct stu
{char name[15];//名字int age;//年龄
};
int com_age(const void* p1, const void* p2)
{return (*(struct stu*)p1).age - (*(struct stu*)p2).age;
}
int main()
{ //qsort函数的实现struct stu arr[3] = { {"liuxin",18},{"zhangsan",20},{"wangwu",16} };size_t a = sizeof(arr) / sizeof(arr[0]);qsort(arr, a, sizeof(arr[0]), com_age);for (int i = 0; i < a; i++){printf("%d ",arr[i].age);}return 0;
}
结构体中名字的比较:
#include<string.h>
struct stu
{char name[15];//名字int age;//年龄
};
//名字是字符串不能直接相减来比较
//使用strcmp函数来比较
//strcmp(字符串1,字符串2)
//如果字符串1>字符串2 返回>0 字符串1=字符串2返回=0 字符串1<字符串2返回<0
//strcmp从字符串的第一个字符开始比较 若第一个字符相等则向后推一个
char com_name(const void* p1, const void* p2)
{return strcmp( (*(struct stu*)p1).name,(*(struct stu*)p2).name);
}
int main()
{ //qsort函数的实现struct stu arr[3] = { {"liuxin",18},{"zhangsan",20},{"wangwu",16} };size_t a = sizeof(arr) / sizeof(arr[0]);qsort(arr, a, sizeof(arr[0]), com_name);for (int i = 0; i < a; i++){printf("%s ",arr[i].name);}return 0;
}
3.qrost函数的模拟实现
这个是int类型的想实现其他类型需要改cmp和打印的方式
int cmp(const void* p1, const void* p2)
{return (*(int*)p1 - *(int*)p2);
}
void jh(char* e1, char* e2, size_t with)
{for (int i = 0; i < with; i++){char a = *e1;*e1 = *e2;*e2 = a;*e1++;*e2++;}
}
void myself_qsort(void *base, size_t num,size_t with,int (*cmp)(const void *p1,const void *p2)) //这里可以看文章开头有解释
{for (int i = 0; i < num-1; i++){for (int j = 0; j < num-1-i; j++){if (cmp((char*)base+j*with,(char*)base+(j+1)*with)>0){jh((char*)base + j * with, (char*)base + (j + 1)*with,with);}}}
}
int main()
{int arr[] = { 2,6,5,7,9,8,4,3,1,10 };size_t num = sizeof(arr) / sizeof(arr[0]);myself_qsort(arr, num, sizeof(arr[0]), cmp);for (int a = 0; a < num ; a++){printf("%d ", arr[a]);}return 0;
}
深入了解指针(五)
1. sizeof和strlen的对⽐
1.1 sizeof
在学习操作符的时候,我们学习了 sizeof , sizeof 计算变量所占内存内存空间⼤⼩的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。
sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。
int main()
{int a = 0;printf("%d\n", sizeof a);printf("%d\n", sizeof (a));printf("%d\n", sizeof (int));return 0;}
1.2 strlen
strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:
size_t strlen ( const char * str );
int main()
{
char arr1[] = { ‘a’,‘gh’,‘s’,‘f’};
char arr2[] = “abcefgh”;
printf(“%d\n”, strlen(arr1));
printf(“%d\n”, strlen (arr2));//strlen 需要包含#include
printf("%d\n", sizeof(arr1));
printf("%d\n", sizeof(arr2));
return 0;
}
1.3 sizeof和strlen的对⽐
sizeof:
- sizeof是操作符
- sizeof计算操作数所占内存的⼤⼩,单位是字节
- 不关注内存中存放什么数据
strlen:
4. strlen是库函数,使⽤需要包含头⽂件 string.h
5. srtlen是求字符串⻓度的,统计的是 \0 之前字符的个数
6. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界
2. 数组和指针笔试题分析
接下来的代码环境都是在32位下
2.1一维数组
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{//数组名是数组首元素的地址//但是有两个例外://1. sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节//2. &数组名,数组名表示整个数组,取出的是整个数组的地址int a[] = { 1,2,3,4 };printf("%zd\n", sizeof(a));// 16 a代表数组名,数组中4个整数大小为4*4=16 printf("%zd\n", sizeof(a + 0));// 4/8 首元素地址 ,地址在32位下为8,64位下为4printf("%zd\n", sizeof(*a)); // 4 首元素大小printf("%zd\n", sizeof(a + 1));// 4/8 首元素地址+1printf("%zd\n", sizeof(a[1])); // 4 代表a[1]元素大小 printf("%zd\n", sizeof(&a));// 4/8 整个数组的地址还是地址printf("%zd\n", sizeof(*&a));// 16 整个数组大小printf("%zd\n", sizeof(&a + 1));// 4/8 (&a+1)代表跳过数组的下一个地址,还是地址所以还是4/8printf("%zd\n", sizeof(&a[0]));// 4/8 首元素地址printf("%zd\n", sizeof(&a[0] + 1));// 4/8 首元素地址+1(下一个地址)return 0;
}
%zd修饰size_t类型比%d更准确
2.2 字符数组
代码1:
//字符数组
char arr[] = { 'a','b','c','d','e','f' };printf("%zd\n", sizeof(arr));// 6 数组的大小6个字符大小为6printf("%zd\n", sizeof(arr + 0)); // 4/8 首地址大小printf("%zd\n", sizeof(*arr));// 1 arr是首元素的地址,*arr 就是首元素printf("%zd\n", sizeof(arr[1]));// 1 arr[1]这个元素大小printf("%zd\n", sizeof(&arr));// 4/8 数组地址printf("%zd\n", sizeof(&arr + 1));// 4/8 (&a+1)代表跳过数组的下一个地址,还是地址所以还是4/8printf("%zd\n", sizeof(&arr[0] + 1));// 4/8 &arr[0]+1是第二个元素的地址,是地址大小4/8个字节return 0;
}
代码2:
char arr[] = { 'a','b','c','d','e','f' };
printf("%zd\n", strlen(arr));// 随机值 没有标注\0
printf("%zd\n\n\n", strlen(arr + 0));// 随机值 首元素地址向后找到、0
//printf("%zd\n", strlen(*arr));// 报错 arr[1] 指的是'a' 在指的是97 streln会把97当作地址 向后统计字符串长度 97作为地址的空间,不一定属于当前程序
//printf("%zd\n", strlen(arr[1]));// 报错 'b'-98 也是非法访问
printf("%zd\n", strlen(&arr));// 随机值 &arr数组地址传给strlen向找\0
printf("%zd\n", strlen(&arr + 1));// 随机值 没有\0
printf("%zd\n", strlen(&arr[0] + 1)); // 随机值 return 0;
代码 3:
char arr[] = "abcdef";
printf("%zd\n", sizeof(arr));// 7 字符串大小\0也算
printf("%zd\n", sizeof(arr + 0));// 4/8 首元素地址
printf("%zd\n", sizeof(*arr));// 1 首元素大小
printf("%zd\n", sizeof(arr[1]));// 1 'a'字符大小
printf("%zd\n", sizeof(&arr));// 4/8
printf("%zd\n", sizeof(&arr + 1));// 4/8
printf("%zd\n", sizeof(&arr[0] + 1));// 4/8
代码4:
char arr[] = "abcdef";
printf("%zd\n", strlen(arr));// 6 \0之前的字符个数
printf("%zd\n", strlen(arr + 0));// 6 首地址地址向后找\0
printf("%zd\n", strlen(*arr));// 报错 'a'-97
printf("%zd\n", strlen(arr[1]));//报错 ’b'-98
printf("%zd\n", strlen(&arr));//6 首地址向后找到\0
printf("%zd\n", strlen(&arr + 1));// 随机值
printf("%zd\n", strlen(&arr[0] + 1));//5
代码5:
char* p = "abcdef";
printf("%zd\n", sizeof(p));// 4/8 p是指针变量 计算的是指针变量的大小
printf("%zd\n", sizeof(p + 1));// 4/8 p+1是'b'的地址
printf("%zd\n", sizeof(*p)); // 1 *p-'a'
printf("%zd\n", sizeof(p[0]));// 1 把p[]当数组p[0]-'a'
printf("%zd\n", sizeof(&p));// 4/8 整个字符串地址
printf("%zd\n", sizeof(&p + 1));// 4/8
printf("%zd\n", sizeof(&p[0] + 1));// 4 / 8
代码6:
char* p = "abcdef";
printf("%zd\n", strlen(p));//6
printf("%zd\n", strlen(p + 1));//5
printf("%zd\n", strlen(*p));//报错 *p-'a'
printf("%zd\n", strlen(p[0]));//报错 'a'
printf("%zd\n", strlen(&p));//随机值
printf("%zd\n", strlen(&p + 1));//随机值
printf("%zd\n", strlen(&p[0] + 1));//5
2.3 ⼆维数组
int a[3][4] = { 0 };
printf("%zd\n", sizeof(a));//48 整个数组
printf("%zd\n", sizeof(a[0][0]));//4
printf("%zd\n", sizeof(a[0]));//16 a[0][]一维数组,a[0]是第一行的数组名,现在单独放在sizeof内部,计算的是第一行的大小
printf("%zd\n", sizeof(a[0] + 1));// 4/8 地址
printf("%zd\n", sizeof(*(a[0] + 1)));//4 *(a[0] + 1) 是第一行第二个元素,4个字节
printf("%zd\n", sizeof(a + 1));// 4/8 首地址+1
printf("%zd\n", sizeof(*(a + 1)));// 16 a[1][]
printf("%zd\n", sizeof(&a[0] + 1));//4/8 &a[0]+1就是第二行的地址,是地址就是4/8个字节
printf("%zd\n", sizeof(*(&a[0] + 1)));//16
printf("%zd\n", sizeof(*a));//16
printf("%zd\n", sizeof(a[3]));//16 不存在越界,因为不会真实的访问内存,仅仅是通过类型推导就可以知道长度的
3. 指针运算笔试题分析
代码1:
int a[5] = { 1, 2, 3, 4, 5 };int* ptr = (int*)(&a + 1);printf("%zd,%zd", *(a + 1), *(ptr - 1));// *(a+1)--指向 数组第一2个元素 2
//(ptr-1)--强制类型转换int* -1 到了数组最后一个元素地址 ,所以*(ptr-1)=5
代码2:
//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
struct Test
{int Num;char* pcName;short sDate;char cha[2];short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{ //0x16进制printf("%p\n", p + 0x1);//结构体20个字节 0x100014printf("%p\n", (unsigned long)p + 0x1);//unsigned long一个字节 0x100001printf("%p\n", (unsigned int*)p + 0x1);//0x100004return 0;
}
代码3:
int main()
{int a[3][2] = { (0, 1), (2, 3), (4, 5) };int* p;p = a[0];printf("%d", p[0]);//1 注意,表达式的操作使用 (0,1)就是1return 0;
}
代码4:
//假设环境是x86环境,程序输出的结果是啥?int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//%p内存中的补码以16进制的形式打印 -4的 源码 10000000 00000000 00000000 00000100// 补码 11111111 11111111 11111111 11111100
return 0; // F F F F F F F C
代码5:
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));// 10 (&aa+1)第三行首地址 int*类型*(ptr1-1)- 5,// 5 第二行首地址
return 0;
代码6:
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);// "at" pa++ -*pa到了"at"
return 0;
代码7:
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp; //注意++cpp会运算效果保留
printf("%s\n", **++cpp);//"POINT" **++cpp -c+2-"POINT"
printf("%s\n", *-- * ++cpp + 3);//"ER" 再++cpp 到了c+1 - "NEW" 再(--)就到了 "ENTER" 再首元素+3 到了"ER"
printf("%s\n", *cpp[-2] + 3);//"ST" 接上面 使用*cpp[-2] 就到了c+3 - "FIRST" 再首元素+3 到了"ST"
printf("%s\n", cpp[-1][-1] + 1);//"EW" 第三个的运算没有产生后续影响 所以cpp[-1][-1]到了"NEW" 再首元素+1就是"EW"
return 0;
这就是指针系列2万多字的总篇章,喜欢的话可以点点关注、收藏、赞。
(⑅•͈ᴗ•͈).:*♡