- 指针的概念
指针就是个变量,用来存放地址,地址唯一标识一块内存空间
指针的大小是固定的4/8个字节(32位平台/64位平台)
指针有类型,指针的类型决定了指针+-整数的步长、指针解引用操作时候的权限
指针的运算
- 字符指针char*
使用方法:
int main()
{
char ch='w';
char*p=&ch;
*p='w';
const char*pstr="hello bit";
//把字符串的第一个字符的地址放到pstr指针变量中,*pstr得到的是a
//常量字符串不能被修改,所以在前面加上const,则指针不能修改所指向字符串的内容
printf("%s\n",pstr);
//%s为打印字符串,从起始位置开始打印,一直到'\0'结束
}
- 练习:
#include<stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
//创建两个数组,将字符串的内容拷贝进两个数组中,所以str1和str2是两个不同的空间,只是内容相同
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
//相同的常量字符串只在内存中保存一份,str3和str4都是指针,指向统一地址,所以相等
if (str1 == str2)//不满足条件
printf("str1 and str2 are same\n");//不执行
else//满足条件
printf("str1 and str2 are not same\n");//执行
if (str3 == str4)//满足条件
printf("str3 and str4 are same\n");//执行
else//不满足条件
printf("str3 and str4 are not same\n");//不执行
return 0;
}
- 指针数组
——存放指针(地址)的数组
如:
char* arr[4]={"abcdef","qwer","hello bit","hehe"};
//存放字符指针的数组(数组的每个元素为字符串首字母的地址)
int arr1[5]={1,2,3,4,5};
int arr2[5]={2,3,4,5,6};
int arr3[5]={3,4,5,6,7};
int arr4[5]={0,0,0,0,0};
int* arr[4]={arr1,arr2,arr3,arr4};
//整型指针数组
char *arr[4];//一级字符指针的数组
char **arr[5];//二级字符指针的数组
- 数组指针
什么是数组指针?
字符指针——存放字符地址的指针——指向字符的指针 char*
整型指针——存放整型地址的指针——指向整型的指针 int*
浮点型的指针——指向浮点型的指针 float* double*
那么
数组指针——存放数组地址的指针——指向数组的指针
int arr[10];
//pa就是一个数组指针
int (*pa)[10]=&arr;
//p先和*结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整型的数组。
//所以p是一个指针,指向一个数组,叫数组指针。
//由于[]的优先级要高于*号,所以必须加上()来保证p先与*结合
- &数组名VS数组名
数组名——数组首元素的地址
&数组名——数组的地址
数组首元素的地址和数组的地址从值的角度来看是一样的,但意义是不一样的
int arr[10]={1,2,3,4,5,6,7,8,9,10};
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);//类型为int(*)[10]
printf("%p\n",&arr+1);//跳过40个字节
//&arr取出的是数组的地址,只有数组的地址才需要数组来接收
- 数组指针的用法
看代码:
#include<stdio.h>
void Print(int(*p)[4], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", (*(p+i))[j]);
//p+i表示的是第i行数组的地址,进行解引用操作得到第i行数组,
//[j]表示第i行数组的第j个元素,由于*的优先级低于[],所以需要加()
}
printf("\n");
}
}
int main()
{
int arr[3][4] = { {1,2,3,4},{5,6,7,8},{9,0,1,2} };
Print(arr, 3, 4);
//数组名表示首元素的地址,对于二维数组来说,二维数组是存放一维数组的数组
//所以首元素是第一行数组,传参时传的是第一行数组的地址,需要用数组指针来接收
}
回顾并指出以下代码的意思:
int arr[5];
//整型数组,数组有5个元素,每个元素是int类型
int *parr1[10];
//指针数组,数组有10个元素,每个元素是int*类型
int (*parr2)[10];
//数组指针,该指针指向一个数组,数组是10个元素,每个元素是int类型
int (*parr3[10])[5];
//数组指针数组,parr3是数组,数组有10个元素,每个元素是类型为int*[5]的数组指针
- 数组传参、指针传参
1>一维数组传参
#include <stdio.h>
void test(int arr[])//ok
{}
//数组传参,用数组接收
void test(int arr[10])//ok
{}
//数组传参,用数组接收
void test(int *arr)//ok
{}
//数组的每个元素为int类型,所以首元素的地址为int*类型
void test2(int *arr[20])//ok
{}
//数组传参,用数组接收
void test2(int **arr)//ok
{}
//数组的每个元素为int*类型,所以首元素的地址为二级指针,用二级指针接收
int main()
{
int arr[10] = {0};
//arr是一个整形数组,每个元素的类型为int
int *arr2[20] = {0};
//arr2是一个指针数组,每个元素的类型为int*
test(arr);
//整型数组传参,数组名表示首元素的地址
test2(arr2);
//指针数组传参,数组名表示首元素的地址
}
2>二维数组传参
void test(int arr[3][5])//ok
{}
//二维数组传参,二维数组接收
void test(int arr[][])//err
{}
//二维数组行可以省略,但列不能省略
void test(int arr[][5])//ok
{}
//二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//err
{}
//第一行数组的地址,应该用数组指针接收,整型指针无法接收
void test(int* arr[5])//err
{}
//指针数组无法接收数组的地址
void test(int (*arr)[5])//ok
{}
//数组指针接收,可行
void test(int **arr)//err
{}
二级指针用于接收一级指针的地址,无法接收数组指针
int main()
{
int arr[3][5] = {0};
test(arr);
//二维数组传参,数组名表示第一行数组的地址
}
3>一级指针传参
一级指针传参,用一级指针接收
若一个函数的参数部分为一级指针时,函数能接收什么参数
void test(int*p)
{}
int a = 0;
int *p=&a;
int arr[10];
test(arr);
test(&a);
test(p);
4>二级指针传参
当函数的参数为二级指针的时候,可以接收什么参数
void test(int **p)
{}
int **ptr;
int *pp;
int *arr[10];
test(ptr);
test(&pp);
test(arr);
- 函数指针
数组指针——指向数组的指针
函数指针——指向函数的指针
int Add(int x,int y)
{
return x+y;
}
int main()
{
//int arr[10];
//int(*pa)[10]=&arr;
//pa是数组指针
int(* pf)(int,int)=&Add;
//int(*pf)(int,int)=Add;
//&函数名和函数名都是函数的地址,没有区别
//pf是一个存放函数地址的指针变量——函数指针
int ret=pf(2,3);
//也可以写成
//int ret=(*pf)(2,3);
printf("%d\n",ret);
//5
}
关于两段有趣的代码:
( *(void (*)())0 )();
//该代码是一次函数调用,调用0地址处的一个函数
//首先代码中将0强制类型转换为类型为void(*)()的函数指针
//然后去调用0地址处的函
void(*signal(int,void(*)(int)))(int);
//该代码是一次函数的声明
//声明的函数名字叫signal
//signal函数的参数有2个,第一个是int类型,第二个是函数指针类型
//该函数指针能够指向的那个函数的参数是int,返回类型是void
//signal函数的返回类型是一个函数指针
//该函数指针能够指向的那个函数的参数是int,返回类型是void
//此种写法过于难懂,可以改成
typedef void(*pf_t)(int);
//对指针类型重命名的时候需要将指针名字放在*旁边
pf_t signal(int,pf_t);
10. 函数指针数组
指针数组
//存放的是字符指针
char* arr1[10];
//存放整型指针
int* arr2[5];
函数指针数组——存放函数指针的数组
int(*pfA[5])(const char*)={&my_strlen};
//[]的优先级高于*,pfA先跟[]结合,表示数组,该数组有5个元素,每个元素是类型为
int(*)(const char*)的函数指针
当多个函数的返回值和参数都一样时,可以将它们都放到一个函数指针数组中,方便调用
函数指针数组的用途:转移表(通过输入的下标,找到数组中的某个地址,通过该地址来调用函数)
举例:写一个计算器,实现加、减、乘、除
#include <stdio.h>
//写一个计算器,实现加、减、乘、除
//初级版
void menu()
{
printf("************************\n");
printf("******1.Add 2.Sub******\n");
printf("******3.Mul 4.Div******\n");
printf("******0.Exit ******\n");
printf("************************\n");
}
int Add(int x,int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请输入你要执行的操作:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入你要进行操作的两个数字:>");
scanf("%d %d", &x, &y);
ret=Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入你要进行操作的两个数字:>");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入你要进行操作的两个数字:>");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入你要进行操作的两个数字:>");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("输入不合法,请重新输入");
break;
}
} while (input);
}
改造后
#include <stdio.h>
//改造版
void menu()
{
printf("************************\n");
printf("******1.Add 2.Sub******\n");
printf("******3.Mul 4.Div******\n");
printf("******0.Exit ******\n");
printf("************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
int (*p[5])(int, int) = { NULL,Add,Sub,Mul,Div };
do
{
menu();
printf("请输入你要执行的操作:>");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
break;
}
else if (input > 0 && input < 5)
{
printf("输入你要执行的操作数:>");
scanf("%d %d", &x, &y);
printf("%d\n", p[input](x, y));
}
else
{
printf("输入不合法,请重新输入\n");
}
} while (input);
}
11. 指向函数指针数组的指针
//数组指针
int arr[10];
int (*pA)[10]=&arr;
//函数指针数组
int(*pf[5])(int,int);
//ppf是指向函数指针数组的指针
int(*(*ppf)[5])(int,int)=&pf;
//ppf先与*结合,表示ppf是一个指针,指针指向一个数组,
//该数组有5个元素,每个元素是类型为int(*)(int,int)的函数指针
//ppf+1则会跳过整个pf数组
12. 回调函数
概念:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。
举例:计算器改造版
#include <stdio.h>
//写一个计算器,实现加、减、乘、除
void menu()
{
printf("************************\n");
printf("******1.Add 2.Sub******\n");
printf("******3.Mul 4.Div******\n");
printf("******0.Exit ******\n");
printf("************************\n");
}
int Add(int x,int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void calc(int (*pf)(int,int))
{
int x = 0;
int y = 0;
printf("请输入你要执行的操作数:>");
scanf("%d %d", &x, &y);
int ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请输入你要执行的操作:>");
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("退出计算器");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
}
13. 排序
冒泡排序:
#include<stdio.h>
//冒泡排序
void bubble_sort(int arr[], int sz)
{
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j+1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
当前冒泡排序有局限:只能排整型数组的顺序
而在库函数中有一个函数可用于排序:qsort
#include<stdlib.h>
void qsort(void*base,//待排序数组的起始位置
size_t num,//数组的元素个数
size_t width,//一个元素是几个字节
int(*cmp)(const void*e1, const void*e2));
//两个元素的比较函数,e1是要比较的第一个元素的地址,e2是要比较的第二个元素的地址
//若e1小于e2,返回一个小于0的数
//若e1大于e2,返回一个大于0的数
//若e1等于e2,返回0
//void*是一种无具体类型的指针,可接收任意类型的地址,但是不能直接用,需要强制类型转换
用qsort实现整型数组排序:
#include<stdio.h>
#include<stdlib.h>
//qsort函数实现排序
int com_int(void* e1, void* e2)
{
return *(int*)e1 - *(int*)e2;
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//bubble_sort(arr, sz);
qsort(arr, sz, sizeof(arr[10]), com_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
用qsort实现结构体的排序:
#include<stdio.h>
#include<stdlib.h>
struct Stu
{
char name;
int age;
};
int com_by_age(const void* e1,const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int main()
{
struct Stu s[3] = { {"wanghedi",23},{"wangsulong",33},{"xuzhisheng",26} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), com_by_age);
}
qsort函数的模拟实现:
改造冒泡排序函数,使得这个函数可以排序任意指定的数组
整型数组:
#include<stdio.h>
//qsort的模拟实现
//改造冒泡排序函数,使其可以排序任意指定的数组(整型数组)
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
int com_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
}
void bubble_sort(void* base, size_t sz, size_t width, int(*com)(const void* e1, const void* e2))
{
size_t i = 0;
size_t j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if (com((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), com_int);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
结构体:
#include<stdio.h>
//qsort的模拟实现
//改造冒泡排序函数,使其可以排序任意指定的数组(结构体)
struct Stu
{
char name;
int age;
};
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
int com_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void bubble_sort(void* base, size_t sz, size_t width, int(*com)(const void* e1, const void* e2))
{
size_t i = 0;
size_t j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if (com((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
int main()
{
struct Stu s[3] = { {"zhangsan",23},{"lisi",50},{"wangwu",33} };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), com_by_age);
}
14.关于指针和数组
//一维数组
//整型数组
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
//计算整个数组的大小,4*4=16字节
printf("%d\n",sizeof(a+0));
//a+0其实是数组第一个元素的地址,是地址就是4/8字节
printf("%d\n",sizeof(*a));
//a是数组首元素的地址,*a是数组首元素,计算的是数组首元素的大小,是4字节
printf("%d\n",sizeof(a+1));
//a+1是数组第二个元素的地址,是地址就是4/8字节
printf("%d\n",sizeof(a[1]));
//a[1]是数组第二个元素,计算的是第二个元素的大小,为4字节
printf("%d\n",sizeof(&a));
//&a取出的是整个数组的地址,是地址就是4/8字节
//&a--->类型:int(*)[4]
printf("%d\n",sizeof(*&a));
//&a是整个数组的地址,*&a就是拿到了数组,*和&可以相互抵消
//所以*&a就是a,a就是数组名,sizeof(*&a)就是sizeof(a)
//当*&a没有放在sizeof内部时,则为数组名,表示首元素地址
printf("%d\n",sizeof(&a+1));
//&a是整个数组的地址,&a+1跳过整个数组,指向数组后边的空间,是一个地址,大小就是4/8字节
//&a+1的类型:int(*)[4]
printf("%d\n",sizeof(&a[0]));
//&a[0]是首元素的地址,计算的是首元素地址,大小为4/8字节
printf("%d\n",sizeof(&a[0]+1));
//&a[0]+1是第二个元素的地址,地址的大小就是4/8字节
sizeof是用于计算大小的,返回类型是size_t,即为unsigned int
//字符数组
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));
//arr单独放在sizeof内部,数组名表示整个数组,计算的是整个数组的大小,大小为6字节
printf("%d\n", sizeof(arr+0));
//arr+0表示数组第一个元素的地址,是地址大小就为4/8字节
printf("%d\n", sizeof(*arr));
//arr表示数组首元素地址,*arr表示首元素,大小为1字节
printf("%d\n", sizeof(arr[1]));
//arr[1]表示数组第二个元素,大小为1字节
printf("%d\n", sizeof(&arr));
//&arr表示取出的是整个数组的地址,是地址大小就为4/8字节
printf("%d\n", sizeof(&arr+1));
//&arr表示取出的是整个数组的地址,&arr+1跳过整个数组,指向数组后边的空间,是一个地址,大小就是4/8字节
printf("%d\n", sizeof(&arr[0]+1));
//&arr[0]表示首元素的地址,&arr+1跳过一个元素,指向数组第二个元素的地址,大小为4/8字节
//size_t strlen(const char*str)
printf("%d\n", strlen(arr));
//strlen用于计算字符串长度,直到'\0'结束,由于数组中不含'\0',所以为随机值
printf("%d\n", strlen(arr+0));
//随机值
printf("%d\n", strlen(*arr));
//*arr表示的是首元素,strlen(*arr)->strlen('a')->strlen(97)
//即访问地址为97的空间,非法访问,报错
printf("%d\n", strlen(arr[1]));
//和上面的代码类似,非法访问,报错
printf("%d\n", strlen(&arr));
//&arr虽然是数组的地址,但也是从数组的起始位置开始的,计算的还是随机值
printf("%d\n", strlen(&arr+1));
//&arr是数组的地址,&arr+1是跳过整个数组的地址,求字符串长度也是随机值
printf("%d\n", strlen(&arr[0]+1));
//&arr[0]是数组首元素的地址,&arr+1是数组第二个元素的地址,从第二个元素开始求字符串长度,为随机值
char arr[] = "abcdef";
//[a,b,c,d,e,f,\0] 数组是7个元素
printf("%d\n", sizeof(arr));
//sizeof(arr)表示计算整个数组的大小,为7字节
printf("%d\n", sizeof(arr+0));
//arr+0表示首元素的地址,是地址大小就为4/8字节
printf("%d\n", sizeof(*arr));
//*arr表示首元素,大小为1字节
printf("%d\n", sizeof(arr[1]));
//arr[1]表示数组第二个元素,大小为1字节
printf("%d\n", sizeof(&arr));
//&arr表示整个数组的地址,是地址大小就为4/8字节
printf("%d\n", sizeof(&arr+1));
//&arr表示整个数组的地址,&arr+1表示跳过整个数组,指向后面的空间,是地址大小就为4/8字节
printf("%d\n", sizeof(&arr[0]+1));
//&arr[0]表示首元素的地址,&arr[0]+1表示数组第二个元素的地址,是地址大小就为4/8字节
printf("%d\n", strlen(arr));
//arr表示数组首元素地址,从首元素开始计算字符串长度,到'\0'结束,为6字节
printf("%d\n", strlen(arr+0));
//arr+0表示首元素地址,从首元素开始计算字符串长度,到'\0'结束,为6字节
printf("%d\n", strlen(*arr));
//*arr是'a',是97,传给strlen是一个非法的地址,造成非法访问,报错
printf("%d\n", strlen(arr[1]));
//arr[1]是数组第二个元素,是98,造成非法访问,报错
printf("%d\n", strlen(&arr));
//&arr表示整个数组的地址,也是从首元素开始到'\0'结束,为6字节
printf("%d\n", strlen(&arr+1));
//&arr表示整个数组的地址,&arr+1表示跳过整个数组,统计字符串长度是随机值
printf("%d\n", strlen(&arr[0]+1));
//&arr[0]表示数组首元素,&arr[0]+1表示数组第二个元素,则从第二个元素开始,为5字节
char *p = "abcdef";
//p是一个char类型的指针,指向a的地址
printf("%d\n", sizeof(p));
//p是一个指针,大小为4/8字节
printf("%d\n", sizeof(p+1));
//p指向a的地址,p+1为b的地址,是地址大小就为4/8
printf("%d\n", sizeof(*p));
//*p是'a',sizeof(*p)计算的是字符的大小,为1字节
printf("%d\n", sizeof(p[0]));
//p[0]-->*(p+0)-->*p,同上,为1字节
printf("%d\n", sizeof(&p));
//&p表示p的地址,为二级指针,是指针大小就为4/8字节
printf("%d\n", sizeof(&p+1));
//&p表示p的地址,&p+1表示跳过p变量后的地址,是地址大小就为4/8
printf("%d\n", sizeof(&p[0]+1));
//p[0]-->*(p+0)-->*p-->'a',&p[0]表示a的地址,&p[0]+1表示b的地址,是地址大小就为4/8
printf("%d\n", strlen(p));
//从'a'开始求字符串长度,为6字节
printf("%d\n", strlen(p+1));
//p+1是b的地址,求字符串长度是5字节
printf("%d\n", strlen(*p));
//*p就是'a',即为97,非法访问,报错
printf("%d\n", strlen(p[0]));
//同上,报错
printf("%d\n", strlen(&p));
//&p得到的是p这个指针变量的起始地址,从这里开始求字符串长度完全是随机值
printf("%d\n", strlen(&p+1));
//&p+1是跳过p变量的地址,从这里开始求字符串长度也是随机值
printf("%d\n", strlen(&p[0]+1));
//&p[0]+1是b的地址,从'b'开始求字符串长度,为5字节
//二维数组
int a[3][4] = {0};
printf("%d\n",sizeof(a));
//a单独在sizeof内部,表示整个数组,计算的是整个数组的大小,3*4*4=48字节
printf("%d\n",sizeof(a[0][0]));
//a[0][0]表示数组第一行第一列的元素,为4字节
printf("%d\n",sizeof(a[0]));
//a[0]是第一行的数组名,数组名单独放在sizeof内部,计算的就是数组(第一行)的大小,为16字节
printf("%d\n",sizeof(a[0]+1));
//a[0]是第一行的数组名,没有单独放在sizeof内部,没有取地址,表示的就是第一行首元素的地址,即为a[0][0]
//a[0]+1表示第一行第二个元素的地址,是地址大小就为4/8字节
printf("%d\n",sizeof(*(a[0]+1)));
//*(a[0]+1)表示第一行第二个元素,计算的是元素的大小为4字节
printf("%d\n",sizeof(a+1));
//a是二维数组的数组名,数组名表示首元素的地址,就是第一行的地址,a+1就是第二行的地址,是地址大小就为4/8字节
//a+1的类型为int(*)[4]
printf("%d\n",sizeof(*(a+1)));
//a+1是第二行的地址,*(a+1)表示的是第二行,*(a+1)->a[1],第二行的大小为4*4=16字节
printf("%d\n",sizeof(&a[0]+1));
//&a[0]表示第一行数组的地址,+1跳过整个第一行数组,表示第二行数组的地址,是地址大小就为4/8字节
printf("%d\n",sizeof(*(&a[0]+1)));
//&a[0]+1表示第二行的地址,*(&a[0]+1)得到的是第二行,计算第二行的大小,为4*4=16字节
printf("%d\n",sizeof(*a));
//a表示首元素的地址,也就是第一行的地址,*a就是第一行,计算的是第一行的大小,为4*4=16字节
printf("%d\n",sizeof(a[3]));
//如果数组存在第四行,那么a[3]表示第四行的数组名,单独放在sizeof内部,计算第四行的大小为4*4=16字节
//sizeof不会真实地去访问空间,通过类型得出大小,放在sizeof中的表达式不会参与运算
int main()
{
short s=3;
int a=10;
printf("%d\n",sizeof(s=a+2));
//2
printf("%d\n",s);
//3 (sizeof内的表达式不会参与运算)
}
//C语言中,表达式有2个属性:
//2+3
//值属性:5
//类型属性:int
总结:
1.sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节
&数组名,数组名表示整个数组,取出的是整个数组的地址
除此之外,所有的数组名都是数组首元素的地址
sizeof只关注占用内存空间的大小,单位是字节,不关心内存中存放的是什么
sizeof是操作符
strlen是求字符串长度的,统计的是\0之前出现的字符个数,一定要找到\0才算结束,所以可能存在越界访问的情况
strlen是库函数
15.练习
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//程序的结果为:2,5
//a是数组名,表示首元素的地址,+1表示第二个元素的地址,*(a+1)表示第二个元素,为2
//&a表示整个数组的地址,类型为int(*)[5],+1跳过整个数组指向后面的空间
//ptr为整型指针,指向a后面的空间,-1则向后读取4个字节的内容,为5
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
//程序的结果是
0x00100014
0x00100001
0x00100004
//指针+1加的是指针所指向对象的大小
//p为结构体指针,+1加的是一个结构体的大小,为20字节,
//p被强制类型转换为unsigned int类型,+1就按照整数+1的法则
//p被强制类型转换为unsigned int*类型,+1跳过一个整形指针的大小,为4字节
//小端 x86环境
int main()
{
int a[4] = { 1, 2, 3, 4 };
int *ptr1 = (int *)(&a + 1);
int *ptr2 = (int *)((int)a + 1);
printf( "%x,%x", ptr1[-1], *ptr2);
return 0;
}
//程序的结果为:
4
2000000
//&a表示整个数组的地址,&a+1跳过整个数组,指向后面的空间
//ptr1[-1]->*(ptr1-1)->向后读取一个整形(4个字节)的内容,即为4
//a是数组名,表示数组首元素的地址,强制类型转换为int类型,+1按照整数+1法则运算,再强转为int*类型
//则相当于向后移动1个字节,ptr2为整型指针,*ptr2为向后读取4个字节的内容
//数组a的内容在内存中按照小端存放,则为01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
//*ptr2访问4个字节则为00 00 00 02,由于内存中为小端存放,所以按照原顺序为02 00 00 00
//%x为打印16进制数
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//逗号表达式,表达式结果为最后一个表达式的值,所以a数组存储的内容为
//1 3
//5 0
//0 0
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
//程序的结果为 1
//a[0]为数组名,表示数组首元素的地址,p[0]->*(p+0),即为第一个元素,为1
int main()
{
int a[5][5];
int(*p)[4];
//p为数组指针,指向一个整形数组,该数组有4个元素,p+1跳过4个字节
p = a;
//a为数组名,表示数组第一行的地址,类型为int(*)[5]
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
//地址-地址得到两个地址直接的元素个数,如果是高地址-低地址结果就为正,如果是低地址-高地址结果为负
//%p是打印地址(以16进制打印)
return 0;
}
//程序结果为
ff ff ff fc -4
int main()
{
int aa[2][5] = {10,9,8,7,6,5,4,3,2,1};
//10 9 8 7 6
//5 4 3 2 1
int *ptr1 = (int *)(&aa + 1);
//&aa表示整个数组的地址,+1跳过整个数组指向后面的空间,强转为int*类型,并赋值给指针ptr1
int *ptr2 = (int *)(*(aa + 1));
//aa是数组名,表示第一行数组的地址,*(aa+1)->aa[1]表示第二行数组首元素的地址,赋值给ptr2
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
//程序的结果为 1,6
//ptr1指向aa数组后面的空间,正好是刚越界的部分,-1则向前挪动一个整形指向10的地址,*ptr1得到1
//ptr2指向第二行首元素,-1指向第一行最后一个元素的地址,*(ptr2-1)得到第一行最后一个元素的内容为6
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
//数组a存放的是字符串首字符的地址
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
//程序的结果为 at
//a为数组名,表示首元素地址,赋值给pa,pa++指向第二个元素的地址
//*pa对第二个元素解引用为at
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);//**(cpp-2)+3
printf("%s\n", cpp[-1][-1]+1);//*(*(cpp-1)-1)+1
return 0;
}
//程序的结果为
//POINT
//ER
//ST
//EW