在指针的类型中我们知道有一种指针类型为字符指针 : char *
一般使用:
#include<stdio.h>
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = b;
printf("%c\n", ch);
return 0;
}
还有一种使用方式如下:
#include<stdio.h>
int main()
{
const char* p = "abcdef";赋值给p的是字符串首字符的地址
printf("%s\n", p);
return 0;
}
本质:const char * pstr = "hello world";本质是把字符串hello world,首字符的地址放到了pstr中.
上面代码的意思是把一个常量字符串首字符h的地址存放到指针变量pstr中.
面试题
下列程序的运行结果为:
#include<stdio.h>
int main()
{
const char* p1 = "abcdef";
const char* p2 = "abcdef";常量字符串有且只有一份
char arr1[] = "abcdef";
char arr2[] = "abcdef";
if (p1 == p2)
printf("p1==p2\n");
else
printf("p1 != p2\n");
if (arr1 == arr2)
{
printf("arr1==arr2\n");
}
else
{
printf("arr1!=arr2\n");
}
return 0;
}
程序的运行结果为:p1==p2 arr1!=arr2
这里p1和p2指向的是同一个常量字符串,C/C++会把常量字符串存储到单独的一个内存区域(数据区),当几个指针指向同一个字符串的时候,它们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块,所以p1==p2 arr1!=arr2.
指针数组的定义:
指针数组是用来存放指针的数组,指针数组作为实参传递给形参时,形参要用二级指针来接收.
int* arr1[10];整型指针的数组
char* arr2[4];一级字符指针的数组
char** arr3[5];二级字符指针的数组
指针数组的应用:模拟实现二维数组
#include<stdio.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* parr[3] = { arr1,arr2,arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", parr[i][j]);
printf("%d ", *((parr[i] + j)));
}
printf("\n");
}
return 0;
}
数组指针的定义:
数组指针是指针,好比整型指针是用来存放整形的指针,字符指针是用来存放字符的指针,那么数组指针也就是存放数组的指针.
数组指针的表示形式:
int (*p) [10]:p先和 * 结合,说明p是一个指针变量,然后指向的是一个大小为10个整型的数组,所以p是一个指针,指向一个数组,叫数组指针.
注意:[ ]的优先级要高于 * 号的,所以必须加上( )来保证p先和 * 结合.
&数组名 VS 数组名
#include<stdio.h>
int main()
{
char* arr[5] = { 0 };
//数组指针为:char* (*p)[5] = &arr;
char ch = 'w';
char* p1 = &ch;一级指针存储变量的地址
char** p2 = &p1;二级指针存储一级指针的地址
int arr[10] = { 0 };
int* p = &arr;有警告的
int(*p2)[10] = &arr;
//p2的类型为int(*)[10]
printf("%p\n", arr);
printf("%p\n", arr + 1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0] + 1);
printf("%p\n", &arr);
printf("%p\n", &arr + 1);
int sz = sizeof(arr);
printf("%d\n", sz);
return 0;
}
注:数组名通常表示的都是首元素的地址,但是有两个例外:
1.sizeof(数组名):这里的数组名表示整个数组 计算的是整个数组的大小 单位是字节.
2.&数组名:这里的数组名表示的依然是整个数组 所以&数组名取出的是整个数组的地址.
实际上&arr表示的是数组的地址,而不是数组首元素的地址,本例中&arr的类型是:int( * )[10],
是一种数组指针类型,数组的地址+1,跳过整个数组的大小,所以&arr+1相对于&arr的差值是40.
注意:二维数组的首元素是它的第一行,二维数组的数组名表示的是整个第一行一维数组的地址.
数组指针在一维数组中的使用(不推荐):
这里简单介绍一下数组指针在一维数组中的使用,但数组指针不推荐在一维数组使用.
原因在于:数组指针将一维数组升维,一级指针变成二级指针,使其更加复杂化.
#include<stdio.h>
int main()
{
代码1
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
代码2 不推荐
int(*p)[10] = &arr;
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
p是指向数组的 *p其实就相当于数组名 数组名又是首元素的地址 所以*p本质上是数组首元素的地址
printf("%d ", *( * p + i));
}
return 0;
}
数组指针在二维数组中的使用:
打印二维数组(数组指针多用于二维数组或三维数组)
#include<stdio.h>
void print1(int arr[3][5], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print2(int(*p)[5], int row, int col)
{
int i = 0;
for (i = 0; i < row, i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%d ", *(*(p + i) + j));
printf("%d ", p[i][j]);
}
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
print1(arr, 3, 5);
print2(arr, 3, 5);
return 0;
}
数组指针的性质:
1. 数组指针+1跳过整个一维数组. 2. 数组指针解引用得到的是数组首元素的地址. 3. 数组指针[ ]的数字不可省略.
注: 1. 形参的一维数组的行可以省略,二维数组行可以省略但列不可以省略. 2. int(*p)[5],p的类型是int(*)[5],p是指向一个整型数组的,数组5个元素,即int[5]. 3. 二维数组的数组名是第一行的地址 相当于一个一维数组的地址 所以用数组指针接收 4. p+1跳过了一个5个元素的数组
练习:
int arr[5] arr是整型数组
int* parr1[10] parr1是整型指针数组
int(*parr2)[10] parr2数组指针
int(*parr3[10])[5] parr3是存放数组指针的数组
一维数组传参:
二维数组传参:
一级指针传参:
二级指针传参:
数组的地址不能用二级指针接收的原因:
函数指针的引入:
首先看一段代码:
#include<stdio.h>
int Add(int x, int y)
{
return(x + y);
}
int main()
{
printf("%p\n", &Add);
printf("%p\n", Add);
return 0;
}
注:对于函数来说 单独的函数名和取地址函数名都表示函数的地址.
函数指针的定义与函数调用的三种表示形式:
函数指针:函数指针也是一种指针,一种指向函数的指针.(数组谈存放,指针谈指向)
int Add(int x, int y)
{
return(x + y);
}
int main()
{
int(*pf)(int, int) = &Add;
int(*pf)(int, int) = Add;
函数调用的三种形式
int ret = (*pf)(2, 3);
int ret = Add(2, 3);
int ret = pf(2, 3);
printf("%d\n", ret);
return 0;
}
来自《C陷阱与缺陷》两个有趣的代码之深度剖析
int main()
{
代码1:
void (*p) () 函数指针类型为void (*) ()
(*(void(*)())0)();
以上代码是一次函数调用 调用的是0作为地址处的函数
1.将0强制类型转换为:无参 返回类型是void的函数的地址
2.调用0地址处的函数
更深层次的理解强制类型转换
将谁强制类型转换就相当于要把强制类型转换的对象看作强制类型转换的数据类型里存放的东西
代码2
void(*signal(int, void(*)(int)))(int);
signal是函数名 上面的代码是一次函数声明
声明的signal函数的第一个参数的类型是int 第二个参数的类型是函数指针 该函数指针指向的函数是int
返回类型是void signal函数的返回类型也是一个函数指针 该函数指针指向的参数是int 返回类型是void
typedef unsigned int uint;
typedef void(*pf_t)(int);把void(*)(int)类型命名为pf_t但要注意pf_t要存放的位置
上述代码简化为
pf_t signal(int, pf_t);
return 0;
}
函数指针的用途:模拟实现计算器
普通版
#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 ret = 0;
int x = 0;
int y = 0;
do
{
menu();
printf("请选择:->");
scanf("%d", &input);
switch (input)
{
case 1:
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
break;
return 0;
}
} while (input);
普通版代码的缺陷:代码冗余部分过多 下面我们用函数指针对其进行优化
函数指针版
函数指针写法 模拟实现计算器
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;
int ret = 0;
printf("请输入两个操作数:->");
scanf("%d %d", &x, &y);
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("退出计算器\n");
default:
printf("选择错误\n");
}
} while (input);
return 0;
}
上述代码用到了回调函数 后面会讲到
函数指针数组的定义:
函数指针存放的是函数的地址,函数指针数组存放的是多个函数的地址.
函数指针数组的表示形式:
int main()
{
int(*pf)(int, int) = Add;pf是函数指针
int(*arr[4])(int, int) = { Add,Sub,Mul,Div };arr就是函数指针数组
函数指针数组的写法:将函数指针名替代成数组
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = arr[i](8, 4);
printf("%d \n", ret);
}
return 0;
}
函数指针数组的用途:
函数指针数组模拟实现计算器
#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(*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
do
{
menu();
printf("请选择:->");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:->");
scanf("%d %d", &x, &y);
ret = pfArr[input](x, y);
printf("%d\n",ret);
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
指向函数指针数组的指针的表示方法:
将数组名和方括号隔开,用星号括起来,变成指针.
注:指向函数指针数组里存放的是取地址函数指针数组的数组名.
#include<stdio.h>
int main()
{
int (*pfArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
int(*(*pfArr)[5])(int, int) = &pfArr;
return 0;
}
冒泡排序的优化:
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int flag = 1;假设数组已经排好序
int j = 0;一趟冒泡排序的过程
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
万能指针:
#include<stdio.h>
int main()
{
int a = 10;
char* pa = (char*)&a;
void* pv = &a;void*的万能之处是可以接收任意类型的地址 但void*是无类型的指针 不能解引用 也不能加减整数
return 0;
}
深入理解qsort函数:
qsort函数的功能:qsort函数可以排序任意类型的数据
qsort函数的头文件:#include<stdlib.h>
qsort函数的参数及返回类型:
__cdecl:函数调用约定
void qsort(void* base,要排序数据的起始位置
size_t num,待排序数据的个数
size_t width,待排序的数据元素的大小
int(__cdecl*cmp)(const void* e1,const void* e2)函数指针 比较函数
)
qsort函数的使用:
#include<stdlib.h>
比较两个整数 e1指向一个整数 e2指向另一个整数
int cmp_int(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
测试使用qsort来排序整型数据
void test1()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
把数组排成升序
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_int);
int i = 0;
for (i = 0; i < sz;i++)
{
printf("%d ", arr[i]);
}
}
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
字符串比较函数strcmp的返回值为>0 ==0 <0
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}
测试使用qsort来排序结构体数据
void test2()
{
struct Stu s[] = { {"zhangsan",15},{"lisi",30},{"wangwu",25} };
int sz = sizeof(s) / sizeof(s[0]);
qsort(s,sz,sizeof(s[0]), cmp_stu_by_name);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char temp = *buf1;
*buf1 = *buf2;
*buf2 = temp;
buf1++;
buf2++;
}
}
int main()
{
test1();
test2();
return 0;
}
注:指向数组的指针称为数组指针 指向函数的指针称为函数指针
strlen形参部分接受的是字符地址 形参字符指针 返回值是size_t
在一维数组中 取地址arr在不加一时和arr是一样的
C语言中的地址都是虚拟地址 经过硬件和软件的计算转化为物理地址
物理地址才是对应到内存每块空间的地址
回调函数:
回调函数就是一个通过函数指针回头再调用所指向的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,,用于对该事件或条件进行响应.
用回调函数模拟实现qsort(冒泡排序版)
int cmp_int(const void* e1, const void* e2)
{
return (*(int*)e1 - *(int*)e2);
}
struct Stu
{
char name[20];
int age;
};
int cmp_stu_by_name(const void* e1, const void* e2)
{
字符串比较函数strcmp的返回值为>0 ==0 <0
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}
void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int flag = 1;
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j+1) * width) > 0)
{
交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
flag = 0;
}
}
if (flag == 1)
{
break;
}
}
}
测试冒泡排序版qsort排序整型
void test3()
{
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], cmp_int));
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
测试冒泡排序版qsort排序结构体数据
void test4()
{
struct Stu s[] = { {"zhangsan",15},{"lisi",30},{"wangwu",25} };
int sz = sizeof(s) / sizeof(s[0]);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
int main()
{
test3();
test4();
return 0;
}
注意:
1.qsort函数默认升序,换句话说qsort函数内部的判断条件默认大于0或者==1,只能通过比较函数的顺序来控制升降序,但判断条件一直是>0或者==1.
2.更深层次的去理解强制类型转换:将谁强制类型转换,就相当于把要强制类型转换的对象卡诺是强制类型转换数据类型存放的那个东西.
#include<stdio.h>
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(&a + 1));//4/8
&a取出的是数组的地址 存放在数组指针中 &a+1是从数组a的地址向后跳过了一个数组的大小 &a+1还是地址 是地址就是4/8个字节
printf("%d\n", sizeof(&a[0]));//4/8
&a[0]就是取出第一个元素的地址 计算的是地址的大小
printf("%d\n", sizeof(&a[0] + 1));//4/8
&a[0]+1是第二个元素的地址 大小是4/8个字节
printf("%d\n", sizeof(a));//16
sizeof(数组名),数组名表示整个数组 计算的是整个数组的大小 单位是字节
printf("%d\n", sizeof(a + 0));//4
a不是单独放在sizeof内部 也没有取地址 所以a就是首元素的地址 a+0还是首元素的地址 是地址大小就是4/8个字节
printf("%d\n", sizeof(*a));//4
*a中的a数组首元素的地址 *a就是对首元素地址解引用 找到的就是首元素 首元素的大小就是4/8个字节
printf("%d\n", sizeof(a + 1));//4
这里的a是首元素的地址 a+1是第二个元素的地址 sizeof(a+1)就是地址的大小
printf("%d\n", sizeof(a[1]));//4
计算的是第二个元素的大小
printf("%d\n", sizeof(&a));//4/8
&a取出的是数组的地址
printf("%d\n", sizeof(*&a));//16
&a拿到的是数组名的地址 类型是int(*)[4] 是一种数组指针 数组指针1解引用找到的是数组
return 0;
}
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6
sizeof(数组名)计算的是整个数组的大小
printf("%d\n", sizeof(arr + 0));//4/8
arr+0表示的是数组首元素的地址
printf("%d\n", sizeof(*arr));//1
*arr得到的是数组的首元素 大小为1个字节
printf("%d\n", sizeof(arr[1]));//1
arr[1]是数组第二个元素
printf("%d\n", sizeof(&arr));//4/8
&arr取出的是整个数组的地址
printf("%d\n", sizeof(&arr + 1));//4/8
&arr+1取出的是数组后的地址
printf("%d\n", sizeof(&arr[0] + 1));//4/8
&arr[0]+1取出的是第二个元素的地址
return 0;
}
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
strlen是求字符串长度的 关注的是字符串的\0 计算的是\0之前出现的字符的个数
printf("%d\n", strlen(arr));//随机值
printf("%d\n", strlrn(arr + 0));//随机值
printf("%d\n", strlen(*arr));//野指针
printf("%d\n", strlen(arr[1]));//野指针
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr + 1));//随机值-6
printf("%d\n", strlen(&arr[0] + 1));//随机值-1
return 0;
}
int main()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));//7
printf("%d\n", sizeof(arr + 0));//4/8
printf("%d\n", sizeof(*arr));//1
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8
printf("%d\n", sizeof(&arr + 1));//4/8
printf("%d\n", sizeof(&arr[0] + 1));//4/8
return 0;
}
int main()
{
char arr[] = "abcdef";
printf("%d\n",strlen(arr));//6
printf("%d\n", strlen(arr + 0));//6
printf("%d\n", strlen(*arr));//野指针
printf("%d\n", strlen(arr[1]));//野指针
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//5
return 0;
}
int main()
{
char* p = "abcdef";
注意栈区字符串和数组相同写法的区别 栈区字符串中&p和p不一样 数组中&arr和arr一样
printf("%d\n", sizeof(p));p里面存储的是字符a的地址 是地址就是4个或8个字节
printf("%d\n", sizeof(p + 1));p+1里面存储的是字符b的地址 是地址就是4个或8个字节
printf("%d\n", sizeof(*p));*p里面存储的是字符a 字符a所占内存大小为1个字节
printf("%d\n", sizeof(p[0]));p[0]相当于*(p+0)里面存储的是字符a 所占内存大小为1个字节
printf("%d\n", sizeof(&p));&p相当于一个二级指针 p里面存储的是字符a的地址 &p是在另一个空间中存储一级指针的地址
printf("%d\n", sizeof(&p + 1));&p+1相当于在二级指针变量的空间内 跳过了整个二级指针
printf("%d\n", sizeof(&p[0] + 1));&和*号抵消 p仍然是一级指针 存储字符a的地址 加一相当于存放的是字符b的地址
printf("%d\n", strlen(p));p里面存储的是字符a的地址 字符串长度为6
printf("%d\n", strlen(p + 1));p+1里面存储的是字符b的地址 字符串长度为5
printf("%d\n", strlen(*p));野指针
printf("%d\n", strlen(p[0]));野指针
printf("%d\n", strlen(&p));在二级指针变量的空间中寻找\0 字符串长度为随机值
printf("%d\n", strlen(&p + 1));在二级指针变量的空间中寻找\0 理论为随机值-4但是在这4个字节的空间内不确定系统会不会放\0因此也为随机值 地址如果是0012ff40小端存储会在
第四个字节处存放00 即字符串结束标志
printf("%d\n", strlen(&p[0] + 1));&和*号抵消 p仍然是一级指针 存储字符a的地址 字符串长度为5
return 0;
}
二维数组
对于一维数组 sizeof(arr)表示整个一维数组的大小 sizeof(arr+1)表示跳过一个元素
对于二维数组来说 arr[0]表示第一行首元素的地址 arr表示二维数组首元素也就是第一行的地址 注意区分
对于二维数组arr[0]单独放到sizeof内部 arr[0]代表第一行整个一维数组的大小 arr[0]+1表示跳过第一行一维数组中的一个元素 指向第二个元素
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));整个二维数组的大小:48个字节
printf("%d\n", sizeof(a[0][0]));第一个元素 4个字节
printf("%d\n", sizeof(a[0]));a[0]是第一行的数组名 代表首元素的地址a[0]等价于*(a+0)但a是二维数组一个*只能解一层引用 单独放到sizeof内部 代表整个第一行 16个字节
printf("%d\n", sizeof(a[0] + 1));a[0]相当于&a[0][0] 所以a[0]+1代表d第一行第二个元素的地址 4或8个字节
printf("%d\n", sizeof(*(a[0] + 1)));a[0]+1解引用表示第一行第二个元素 大小为4个字节
printf("%d\n", sizeof(a + 1));a虽然是二维数组的地址 但是并没有单独放在sizeof内部 也没取地址 所以a表示首元素的地址
而二维数组的首元素是它的第一行 a就是第一行的地址 第一行的地址+1跳过第一行 指向第二行 表示第二行的地址 4个或8个字节 而&a取出整个二维数组的地址加一表示跳过整个二维数组
printf("%d\n", sizeof(*(a + 1)));a+1表示第二行的地址 存放到数组指针中(a+1) 解引用表示整个第二行 16个字节 *(a+1)表示a[1]sizeof(*(a+1))表示16个字节
printf("%d\n", sizeof(&a[0] + 1));对第一行的数组名取地址拿出的是第一行的地址 &a[0]+1得到的是第二行的地址
printf("%d\n", sizeof(*( &a[0] + 1)));&a[0]+1得到的是第二行的地址 再解引用 得到第二行的大小 16个字节
printf("%d\n", sizeof(*a));二维数组的数组名表示首元素的地址 就是第一行的地址 *a拿到的是第一行 16个字节
printf("%d\n", sizeof(a[3]));a[3]是第四行的数组名 单独放到sizeof内部 表示第四行的元素大小 16个字节
return 0;
}
注意:int(*p)[5] = &arr; arr就是一维数组 int*(*)[5] = &arr; &arr相当于一个三级指针 int(*)[5] = arr 二维数组的数组名相当于一个二级指针 int(*)(int, int) = Add;函数指针相当于一个一级指针
指针笔试题解析:
下列程序的运行结果是:
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
下列程序的运行结果为:
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}* p = (struct Test*)0x100000;
int main()
{
printf("%p\n", p + 0x1);结构体指针+1跳过一个结构体
printf("%p\n", (unsigned long)p + 0x1);无溢出
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
假设p的值为0x100000 下列程序的运行结果为:0x100014 0x100001 0x100004
下列程序的运行结果为:
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;
}
程序的输出结果为:0x00000004 0x2000000
下列程序的运行结果为:
int main()
{
int a[3][2] = { (0,1),(2,3),(4,5)};
int* p;
p = a[0];
printf("%d", p[0]);a[0]单独放在sizeof内部才表示二维数组一行的大小 现在表示首元素的地址
return 0;
}
程序的运行结果为:1
下列程序的运行结果为:
int main()
{
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]);
return 0;
}
程序的运行结果为:FFFFFFFC -4
指针和指针相减得到的是两个指针之间的元素个数 但要注意正负号 因为数组的创建是由低地址到高地址存储的
%p打印负数直接将负数的补码4位一隔4位一隔转化成16进制 %u打印负数操作的也是负数的补码 %d打印的是原码
下列程序的运行结果为:
int main()
{
对于二维数组:aa是第一行的地址 aa+1是第二行的地址 对第二行的地址解引用得到的是第二行首元素的地址 放在sizeof内部表示整个第二行
对于二维数组:&aa+1 &和二维数组中两个*号中的一个抵消了 所以只需要解引用一次久能得到二维数组中的值 而二维数组的数组名要解引用两次
int aa[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));//6的地址强制类型转化为整型指针没必要
printf("%d,%d" ,* (ptr1 - 1), *(ptr2 - 1));
return 0;
}
程序的运行结果为:10 5
下列程序的运行结果为:
int*p p+1跳过一个整型而不要只想着跳过四个字节
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;pa++跳过了一个char* 也就是跳过了char*(而不要只想着跳过四个字节)数组中的第一个元素"work"
printf("%s\n", *pa);
return 0;
}
程序的运行结果为: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);
printf("%s\n", cpp[-1][-1] + 1);
return 0;
}
程序的运行结果为:POINT ER ST EW
#include <stdio.h>
int main()
{
double price = 0.0;
int month = 0;
int date = 0;
int flag = 0;
scanf("%lf %d %d %d",&price,&month,&date,&flag);
if(month==11 && date == 11)
{
price = price * 0.7 - flag * 50;
}
else if(month == 12 && date ==12)
{
price = price * 0.8 - flag * 50;
}
if(price<0.0)
{
printf("%.2lf\n",0);
}
else
{
printf("%.2lf\n",price);
}
return 0;
}
#include <stdio.h>
int main()
{
int n = 0;
while(scanf("%d",&n)==1)
{
int i = 0;
int j = 0;
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
if(i+j<n-1)
{
printf(" ");
}
else
{
printf("* ");
}
}
printf("\n");
}
}
return 0;
}
代码1
不合并直接打印
#include <stdio.h>
int main()
{
int n = 0;
int m = 0;
scanf("%d %d", &n, &m);
int arr1[n]; 变长数组
int arr2[m];
输入n个整数
int i = 0;
for (i = 0; i < n; i++)
{
scanf("%d", &arr1[i]);
}
for (i = 0; i < m; i++)
{
scanf("%d", &arr2[i]);
}
合并打印
int j = 0;
int k = 0;
while (j < n && k < m)
{
if (arr1[j] < arr2[k])
{
printf("%d ", arr1[j]);
j++;
}
else
{
printf("%d ", arr2[k]);
k++;
}
}
if (j < n)
{
for (; j < n; j++)
{
printf("%d ", arr1[j]);
}
}
else
{
for (; k < m; k++)
{
printf("%d ", arr2[k]);
}
}
return 0;
}
代码2
合并有序数列数组再打印
int main()
{
int n = 0;
int m = 0;
scanf("%d %d", &n, &m);
int arr1[n];//变长数组
int arr2[m];
int arr3[m + n];
输入n个整数
int i = 0;
for (i = 0; i < n; i++)
{
scanf("%d", &arr1[i]);
}
for (i = 0; i < m; i++)
{
scanf("%d", &arr2[i]);
}
合并打印
int j = 0;
int k = 0;
int r = 0;
while (j < n && k < m)
{
if (arr1[j] < arr2[k])
{
arr3[r++] = arr1[j];
j++;
}
else
{
arr3[r++] = arr2[k];
k++;
}
}
if (j < n)
{
for (; j < n; j++)
{
arr3[r++] = arr1[j];
}
}
else
{
for (; k < m; k++)
{
arr3[r++] = arr2[k];
}
}
for (i = 0; i < m + n; i++)
{
printf("%d ", arr3[i]);
}
return 0;
}
#include <stdio.h>
#include<math.h>
int main()
{
int input = 0;
int sum = 0;
scanf("%d", &input);
int i = 0;
while (input)
{
int bit = input % 10;
if (bit % 2 == 1)
{
sum += 1 * pow(10, i);
i++;
}
else
{
sum += 0 * pow(10, i);
i++;
}
input /= 10;
}
printf("%d\n", sum);
return 0;
}
优化
#include <stdio.h>
#include<math.h>
int main()
{
int input = 0;
int sum = 0;
scanf("%d", &input);
int i = 0;
while (input)
{
int bit = input % 10;
if (bit % 2 == 1)
{
bit = 1;
}
else
{
bit = 0;
}
sum += bit * pow(10, i);
i++;
input /= 10;
}
printf("%d\n", sum);
return 0;
}
#include<stdio.h>
void move_odd_even(int arr[],int sz)
{
int left = 0;
int right = sz - 1;
从左向右找一个偶数 停下来
while (left < right)
{
while ((left < right) && arr[left] % 2 == 1)
{
left++;
}
从右向左找一个奇数 停下来
while ((left < right) && arr[right] % 2 == 0)
{
right--;
}
交换奇偶数
if (left < right)
{
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;这块不加left++ right-- 程序会多循环一次
}
}
}
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
输入
for (i = 0; i < sz; i++)
{
scanf("%d", &arr[i]);
}
调整
move_odd_even(arr, sz);
输出
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}