字符指针
char ch = 'a';
char* ps = &ch;//pc指向一个字符变量
const char* p = "hello world";//"hello world"是一个常量字符串
//把常量字符串第一个字符h的地址赋值给p
//最好加const
printf("%c", *p);
printf("%s", p);
//%s 打印字符串只需要提供首元素的地址就可以,不需要再解引用
例题
char str1[] = "hello bit.";
char str2[] = "hello bit.";
char* str3 = "hello bit.";
char* str4 = "hello bit.";
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");
常量字符串存放在常量区,不能被修改,两个指针指向同样的常量字符串,这个常量字符串只会存一份
指针数组
int arr[10];//整型数组-存放整型的数组
char arr1[4];//字符数组- 存放字符的数组
//指针数组-存放指针的数组
char* arr2[5];//arr2是存放字符指针的数组
int* arr3[5];//arr3是存放整型指针的数组
指针数组的应用
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* parr[] = { arr1,arr2,arr3 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", *(parr[i]+j));
//parr[i][j]==*(parr[i]+j)
}
printf("\n");
}
parr[i][j]==*(parr[i]+j)
parr是数组名,数组名是数组首元素的地址,也就是int* 类型变量的地址
int** p=parr;//&arr[0]
数组指针
int* p;//整型指针 - 指向整型的指针
char* p;//字符指针 - 指向字符的指针
数组指针:指向数组的指针。
int *p1[10];//p1是个数组,存放的每个元素类型是int*
int (*p2)[10];//p2是个指针,指向的是个数组,数组是十个元素,每个元素的类型是int
数组指针的类型就是去掉名字,剩下的就是类型int (*)[10]
int* arr1[10];
int* (*p2)[10] = &arr1;
接下来看个常见的用&数组名
遍历数组错误
void print1(int(*p)[10],int sz)//错误示范
{
for (int i = 0; i < sz; i++)
{
printf("%d ", p[i]);//p[i]==*(p+i)
}
}
如果想要&数组名
遍历数组,下面这种写法可以
数组指针在二维数组用的较多
在二维数组里,arr是数组名,数组名是首元素的地址,也就是二维数组的第一行的地址,第一行是个数组,数组的地址应该放在一个数组指针
里
//p就是数组指针
void print2(int (*p)[5], int row, int line)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < line; j++)
{
//printf("%d ", *(*(p + i) + j));
prinft("%d ", p[i][j]);
}
printf("\n");
}
}
//遍历二维数组不用数组指针
//参数部分也是二维数组
void print(int arr[3][5], int row, int line)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < line; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
*(p + i)
相当于找到第i行起始位置的地址
思考:int (*parr3[10])[5]
表示什么?
parr3是一个数组,数组有十个元素,每个元素是一个数组指针,该指针指向的数组有5个元素,每个元素是int
数组参数,指针参数
一维数组传参
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int* arr)//ok
{}
int main()
{
int arr[10] = { 0 };
test(arr);
return 0;
}
void test2(int** arr)//ok
{}
void test3(int* arr[])//ok
{}
int main()
{
int* arr2[20] = { 0 };
test2(arr2);
return 0;
}
一维数组传参时,参数可以是数组,也可以是指针,参数如果是数组大小可以省略,因为并不会真正创建一个数组
二维数组传参
void test(int arr[][5])//行可以省略,列不可以
{}
void test(int(*arr)[5])//参数是指向一维数组的指针
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);//数组名是首元素的地址,也就是二维数组第一行的地址
return 0;
}
二维数组首元素,指的是第一行
一级指针传参可以接收什么参数?
void test(int* p)
{}
int main()
{
int a = 10;
int arr[10];
int* p = &a;
test(&a);
test(p);
test(arr);
return 0;
}
二级指针传参可以接收什么参数?
void test(int** ppa)//参数是二级指针
{}
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
int* arr[5];//指针数组
test(ppa);
test(&pa);
test(arr);
return 0;
}
函数指针
函数指针变量 - 存放函数的地址,函数是有地址的
跟数组不一样,数组名 - 首元素地址;&数组名 - 数组的地址
,
函数名==&函数名
用来存放函数地址的指针变量,它的类型是什么样的?
int (*)(int,int)
就是pf的类型,但是不能int (*)(int,int) p2
这么定义
如何使用
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int,int) = &Add;
//int (*pf)(int,int) = Add;
int ret = (*pf)(4, 5);//*写不写无所谓
int ret2 = pf(6, 5);
return 0;
}
pf
前可以不写*
号,*号在这里就是摆设
思考两行代码(*(void (*)())0) ();
和void (*signal(int, void(*)(int)))(int);
(*(void (*)())0) ();
//上面这行代码是一次函数调用
//1.代码中把0强制类型转换为类型为void (*)()的一个函数的地址
//2.解引用0的地址,就是调用0地址处的函数,被调的函数是无参,返回类型是void
void (*signal(int, void(*)(int)))(int);
//上面的代码是一次函数声明
//声明的函数名是signal
//signal函数有两个参数,第一个是int类型,第二个是void(*)(int)
//signal函数的返回类型依然是void(*)(int)
如果函数指针的类型想用typedef
经过简化,signal函数可以写成func signal2(int, func);
函数指针数组
//函数指针的数组 - 存放函数指针的数组
int(*arr[3])(int, int) = {Add,Sub,Mul };
//int(*arr[3])(int,int) = { pf1,pf2,pf3 };
//arr先和[]结合,表面arr是个数组;
//去掉arr[],int(*)(int,int)是数组元素的类型
函数指针数组的用途:
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 main()
{
int input = 0;
int ret = 0;
do
{
int x, y;
scanf("%d", &input);
scanf("%d %d", &x, &y);
//函数指针数组
int (*pfarr[4])(int, int) = { 0,Add,Sub,Mul };
if (input == 0)
{
break;
}
else if (input >= 1 && input <= 3)//根据input的值选择调用哪个函数
{
ret = pfarr[input](x, y);
printf("%d", ret);
}
else
{
printf("选择错误");
}
} while (input);
return 0;
}
也可以用函数的方式,参数是函数指针,让calc有多种功能(回调函数)
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;
}
void Calc(int (*pf)(int, int))//参数是函数指针
{
int x, y, ret;
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d", ret);
}
int main()
{
Calc(Add);
return 0;
}
指向函数指针数组的指针
先回顾数组指针和整形指针数组
int arr[10];
int(*p)[10]=&arr;//p是指向整形数组的指针
int* arr1[10];//整形指针的数组
int* (*pf)[10] = &arr1;//整形指针数组的地址
//pf是指向整形指针数组的指针
而指向函数指针数组的指针如何表示?
回调函数
int main()
{
int a = 10;
float f = 0.22;
//void* -无具体类型指针
//能够接收任意类型的地址
//缺点,不能进行运算,不能加减整数,不能解引用,因为没有具体的
void* p3 = &a;
//p3++;*p3
void* p4 = &f;
return 0;
}
之前我们写过冒泡排序,但它只能比较int类型,而qsort
能排列不同类型的数组
#include<stdlib.h>
int com_int(const void* e1, const void* e2)//回调函数
{
return *(int*)e1 - *(int*)e2;//e1和e2要先强制类型转换成int*
}
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), com_int);
for (int i = 0; i < sz; i++)
printf("%d ", arr[i]);
return 0;
}
qsort
不仅能给整形数组排序,还能给结构体类型的数组排序
struct stu
{
char name[20];
int age;
};
int cmp_name(const void* e1, const void* e2)//e1指向一个结构体类型
{
//return strcmp(((struct stu*)e1)->name, ((struct stu*)e2)->name);
return strcmp((*(struct stu*)e1).name, (*(struct stu*)e2).name);
}
int main()
{
struct stu s[3] = { {"张三",10},{"李思",20},{"王武",21}};
int sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_name);
return 0;
}
模拟实现qsort
int com_int(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;//e1和e2要先强制类型转换成int*
}
void Swap(char* buf1, char* buf2,int width)
{
for (int i = 0; i < width; i++)//一个字节一个字节的交换
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void Bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))//e1和e2可以不写
{
for (size_t i = 0; i < sz - 1; i++)
{
for (size_t j = 0; j < sz - 1 - i; j++)
{
//这里强制类型转换成char*也很巧妙,char*类型加1就是往后跳过一个字节
if (cmp((char*)base+width*j, (char*)base + width*(j+1))>0)
//这里体现width的作用了,void* 类型的指针变量不能运算,必须强制类型转换
//每个元素之间相差width个字节,因此能比较不同类型的变量
{
Swap((char*)base + width * j, (char*)base + width * (j + 1),width);
//把相邻的两个元素交换
}
}
}
}
void test1()
{
int arr[10] = { 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);
for (int i = 0; i < sz; i++)
printf("%d ", arr[i]);
}
int main()
{
test1();
return 0;
}
指针和数组笔试题
数组名是数组首元素的地址,相当于&arr[0]
,
但是有两个例外:
sizeof(数组名)
数组名单独放在sizeof内部 - 数组名表示整个数组,计算的是整个数组的大小,单位是字节&数组名
- 数组名表示整个数组,取出的是整个数组的地址
sizeof
- 操作符 求变量所占空间的大小,求类型创建的变量所占的空间大小
strlen
- 库函数,求字符串长度
整形数组
重点看sizeof(a+0)
和sizeof(*&a)
//指针的大小在32位下是4个字节,在64位下是8个字节
//一维数组
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16 - 数组名单独放在sizeof内部,计算的是整个数组的大小
printf("%d\n", sizeof(a + 0));//4/8 - sizeof内部不仅有数组名,a+0就是数组首元素的地址
printf("%d\n", sizeof(*a));//4 - a是首元素地址,*a就是首元素
printf("%d\n", sizeof(a + 1));//4/8 - a+1是第二个元素的地址
printf("%d\n", sizeof(a[1]));//4 - a[1]就是第二个元素
printf("%d\n", sizeof(&a));//4/8 - &数组名,取出的是数组的地址,但还是地址
printf("%d\n", sizeof(*&a));//16 - &数组名,取出的是数组的地址,解引用就拿到整个数组
//*&a=a
printf("%d\n", sizeof(&a + 1));//4/8 - &a是数组的地址,&a+1,就是数组的地址+1,跳过整个数组,但还是地址
printf("%d\n", sizeof(&a[0]));//4/8 - 首元素的地址
printf("%d\n", sizeof(&a[0] + 1));//4/8 - 第二个元素的地址
字符数组
sizeof
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6
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
strlen
//字符数组
char arr[] = { 'a','b','c','d','e','f' };
//sizeof - 操作符 求变量所占空间的大小,求类型创建的变量所占的空间大小
//strlen - 库函数,求字符串长度
printf("%d\n", strlen(arr));//随机值 arr是首元素地址(没有&也没有单独放在sizeof内部)
printf("%d\n", strlen(arr + 0));//随机值,跟上面一行一样
printf("%d\n", strlen(*arr));//报错 *arr - 'a' -ASCII码值为97
//strlen就以为传进来的'a'的ASCII码值97就是地址
printf("%d\n", strlen(arr[1]));//报错 arr[1] - 'b'- ASCII码值为98
printf("%d\n", strlen(&arr));// 随机值 跟strlen(arr)的随机值一样
//&arr的类型是char(*)[6]
//strlen参数类型是char* ,虽然类型不匹配,但传过去没有问题
printf("%d\n", strlen(&arr + 1));//随机值 数组的地址+1,跳过整个数组
printf("%d\n", strlen(&arr[0] + 1));//随机值
sizeof
//char arr[] = { 'a','b','c','d','e','f' };
//a b c d e f - 数组有6个元素
char arr[] = "abcdef";
//a b c d e f \0 - 数组有七个元素
printf("%d\n", sizeof(arr));//7
printf("%d\n", sizeof(arr + 0));// 4/8 arr+0,arr是首元素地址
printf("%d\n", sizeof(*arr));//1 arr首元素地址,解引用就是'a'
printf("%d\n", sizeof(arr[1]));//1 'b'的大小
printf("%d\n", sizeof(&arr));// 4/8 数组的地址
printf("%d\n", sizeof(&arr + 1));// 4/8 跳过整个数组的地址
printf("%d\n", sizeof(&arr[0] + 1));// 4/8 第二个元素的地址
strlen
char arr[] = "abcdef";
printf("%d\n", strlen(arr));//6 '\0'前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
常量字符串
sizeof
const char* p = "abcdef";
printf("%d\n", sizeof(p));// 4/8 p是指针变量,用来存放字符串首元素的地址
printf("%d\n", sizeof(p + 1));// 4/8 p+1是'b'的地址
printf("%d\n", sizeof(*p));//1
printf("%d\n", sizeof(p[0]));//1 p[0]就是*(p+0)
printf("%d\n", sizeof(&p));// 4/8 &p取出p的地址
printf("%d\n", sizeof(&p + 1));//4/8
printf("%d\n", sizeof(&p[0] + 1));//4/8 'b'的地址
strlen
const char* p = "abcdef";
printf("%d\n", strlen(p));// 6
printf("%d\n", strlen(p + 1));//5
printf("%d\n", strlen(*p));//报错
printf("%d\n", strlen(p[0]));//报错
printf("%d\n", strlen(&p));//随机值
printf("%d\n", strlen(&p + 1));//随机值,跟上面一行的随机值无关
printf("%d\n", strlen(&p[0] + 1));//5 b的地址往后找'\0'
二维数组(重要)
二维数组也是数组
1.sizeof(数组名),计算整个二维数组的大小
2.&数组名,取出的是整个二维数组的地址
除此之外,所有的数组名都是数组首元素的地址
二维数组数组名表示首元素的地址,而二维数组的首元素是它的第一行
//二维数组
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]));//16
//a[0]是第一行的数组名,数组名单独放在sizeof内部,计算整个数组大小
printf("%d\n", sizeof(a[0] + 1));//4/8
//sizeof内部不是只有数组名,a[0]代表数组首元素地址,第一行第一个元素地址,即a[0][0]的地址
//a[0]+1就算第一行第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1)));//4 *(a[0] + 1))就算第一行第二个元素
printf("%d\n", sizeof(a + 1));//4/8
//数组名a并没有单独放在sizeof内部,也没有&,所以a表示首元素(第一行)地址
//所以a+1是第二行的地址,类型int(*)[4],a+1等价于&a[1]
printf("%d\n", sizeof(*(a + 1)));//16 *(a+1)等价于a[1],就是第二行
printf("%d\n", sizeof(&a[0] + 1));//4/8
//&a[0]+1和a+1,一样
//a[0]是第一行数组名,&a[0]拿到第一行地址,&a[0]+1就是第二行的地址
printf("%d\n", sizeof(*(&a[0] + 1)));//16
//*(&a[0] + 1)) - 第二行 - a[1]
//a[1]是第二行的数组名
printf("%d\n", sizeof(*a));//a表示首元素(第一行)地址,*a拿到的就是第一行,相当于第一行数组名
printf("%d\n", sizeof(a[3]));//16 a[3]假设存在,就第四行的数组名,根据他的类型确定大小
//相当于第四行的数组名
//虽然二维数组没有第四行
//sizeof不会去访问不存在的第四行,计算的是它的类型属性
指针笔试题
下面这段程序的结果是什么?打印2 5
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
//&a的类型是int(*)[5],+-1跳过整个数组,被强制类型转换成int*
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));//2 5
return 0;
}
下面这段程序的结果是什么?
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;
int main()
{
//p的类型是struct Test* 而0x100000是个十六进制数,类型是int
//0x100000赋给p要强制类型转换
p = (struct Test*)0x100000;
//0x1是16进制的1,还是1
printf("%p\n", p + 0x1);//0x100014
//p+0x1跳过一个结构体,结构体大小是二十个字节,答案不是0x100020,1是1*16的一次方,4是4*16的0次方
printf("%p\n", (unsigned long)p + 0x1);//0x1000001
//p的类型被强制类型转换成unsignded long,不再是指针了
//整形+1就是+1
printf("%p\n", (unsigned int*)p + 0x1);//0x1000004
return 0;
}
下面这段程序的结果是什么?4 2000000
int main()
{
int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
//%x - 以16进制打印
printf("%x,%x", ptr1[-1], *ptr2);
return 0;
}
下面这段程序的结果是什么?1
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//陷阱:()里的是逗号表达式,结果是最后应该表达式的结果
//所以a[3][2]里真正放的是{1,3,5}
int* p;
p = a[0];//a[0]是第一行的数组名,代表首元素地址
printf("%d", p[0]);//p[0]等价于*(p+0)
return 0;
}
下面这段程序的结果是什么?FFFFFFFC,-4
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]);
// 地址-地址 地址-地址
//-4在内存中的补码是FFFFFFFC
//%p是以十六进制打印地址,没有原反补的概念,所以之间打印-4在内存中的补码FFFFFFFC
return 0;
}
下面这段程序的结果是什么?10,5
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
//(int*),强制类型转换不起作用,因为*(aa+1)的类型就是int*
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10 5
return 0;
}
下面这段程序的结果是什么?at
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
//pa的类型是char**,+1往后跳过一个char*
pa++;
printf("%s\n", *pa);
return 0;
}
(难)下面这段程序的结果是什么?
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);//POINT
printf("%s\n", *-- * ++cpp + 3);//ER
printf("%s\n", *cpp[-2] + 3);//ST
printf("%s\n", cpp[-1][-1] + 1);//EW
return 0;
}
作业
1.打印杨辉三角
int main()
{
int arr[10][10] = { 0 };//用二维数组来表示杨辉三角
for (int i = 0; i < 10; i++)
{
for (int j = 0; j <= i; j++)//每行只考虑j==0到j==i
{
if (j == 0 || i == j)//把每一行第一个和最后一个数初始化为1
arr[i][j] = 1;
if (i > 1 && j > 0)//其余的数等于上一行的数+上一行之前的数
arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1];
}
}
for (int i = 0; i < 10; i++)
{
for (int j = 0; j <= i; j++)
printf("%d ", arr[i][j]);
printf("\n");
}
return 0;
}
2.实现一个函数,可以左旋字符串中的k个字符
#include<stdio.h>
#include<string.h>
void Reverse(char* left, char* right)
{
while (left < right)
{
char tmp = *left;
*left = *right;
*right = tmp;
left++;
right--;
}
}
void left_move(char arr[], int k)//ABCDEF 3
{
int len = strlen(arr);
Reverse(arr, arr + k - 1);//先把ABC逆序,得到CBADEF
Reverse(arr + k, arr + len - 1);//再把DEF逆序,得到CBAFED
Reverse(arr, arr + len - 1);//最后整体逆序得到,DEFABC
}
int main()
{
char arr[] = "ABCDEF";
left_move(arr,3);
printf("%s", arr);
return 0;
}
3.写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串
int Is_left_move(char arr1[], char arr2[])
{
int len1 = strlen(arr1);
int len2 = strlen(arr2);
if (len1 != len2)
return 0;
strncat(arr1, arr1, len1);//strncat在arr1后面追加arr1的字符,个数是len
if (strstr(arr1, arr2) == NULL)
//strstr,判断arr2是不是arr1中的一部分,不是的话返回NULL,是的话返回arr1中arr2那部分的起始地址
return 0;
else
return 1;
}
int main()
{
char arr1[20] = "ABCDEF";
char arr2[] = "CDEFAB";
int ret=Is_left_move(arr1, arr2);
if (ret == 1)
printf("是左旋");
return 0;
}
4.看一个数是否在杨氏矩阵里,杨氏矩阵每一行和每一列都是递增的
void find_k(int arr[3][3], int row, int line, int k)
{
int x = 0, y = line - 1;
while (x < row && y >= 0)
{
//拿每一行最右边的数跟k比较
if (arr[x][y] < k)//小于k就往下一行找
x++;
else if (arr[x][y] > k)//大于k就往上一列找
y--;
else
{
printf("x的下标%d y的下标%d", x, y);
return;
}
}
printf("找不到");
}
int main()
{
int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
int k = 7;
find_k(arr, 3, 3, k);
return 0;
}