指向整个数组的指针
int(*p)[5]
其中(*p)说明p是一个指针变量,[5]说明p指向的是一整个数组,5代表元素个数,int 代表指向这个数组里边存放的元素的数据类型
int arr[6] = { 1,2,3,4,5,6 };
int (*p)[6] = &arr;
for (int i = 0; i < 6; i++) {
printf("%d\n", (*p)[i]);
}
对于数组指针,如果想访问数组里的每一个元素,需要像上述代码一样(即*p相当与数组名)
对于二维数组本质的理解:二维数组的每一行其实都是一个一维数组,也就是说二维数组可以理解成是一个一维数组的数组,二维数组的每一个元素都是一个一维数组,二维数组的数组名是首元素的地址,也就是第一行的地址(就是这个一维数组的地址)
void my_print2(int(*arr)[5], int l, int r) {
for (int i = 0; i < l; i++) {
for (int j = 0; j < r; j++) {
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main() {
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
my_print2(arr, 3, 5);
return 0;
}
对于p[i][j]
来说,相当于*( *(p+i)+j )
int (*p)(int, int) = &Add
(*p)说明是指针,(int,int)是函数参数,说明指向的是一个函数,int是函数的返回值类型int res = (*p)(1, 3)
对指针解引用就相当于得到了函数名。int res = p(1, 5);
其实也可以,也就是可以直接用函数地址来进行调用,但推荐使用第一种,易于理解 (* ( void(*) ( ) ) 0 ) ()
对于这个代码的理解: void(*)()
是一种函数指针类型,它指向的函数没有参数,返回值为void (void(*)())0
在括号里边放类型是强制类型转化,也就是将0这个int类型的数据转变为一个函数指针类型(也就是将0看作一个地址) *(void (*)())0
相当于对一个函数指针解引用,得到的是一个函数名 (* ( void(*) ( ) ) 0 ) ()
相当于调用这个函数 整个表达式的目的就是调用地址为0处的函数typedef long long int LL;
int* p1,*p2
,每个指针变量都有自己的*。typedef void (*Pt)(int) ;
只能把重命名的名字(Pt)写到里边一个数组里面存放的元素都是函数指针。
格式:int (*arr[4])(int,int)={Add,Sub};
其中Add,Sub都是函数名称。其实写作int (*)(int,int) arr[4] ={Add,Sub};
更容易接受,但语法规定
注意,只有多个函数有相同的返回值类型,以及相同的参数类型的时候,多个函数名才能放入同一个函数指针数组里面。
应用:转移表(可以大大简化代码,可以起到跳转的作用)
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 (*parr[5])(int, int) = { NULL,add,sub,mul,div };
do {
menu();
printf("请选择一个数字\n");
scanf("%d", &input);
if (input == 0) {
printf("退出程序\n");
}
else if (input > 0 && input < 5) {
printf("请输入两个操作数:");
int a, b;
scanf("%d %d", &a, &b);
int res=(parr[input])(a, b);
printf("res=%d\n", res);
}
else {
printf("请重新输入\n");
}
} while (input);
return 0;
}
回调函数是指通过函数指针而被间接调用的函数。相当于将某一个函数作为参数在函数间进行传递。(在某些时候可以大大简化代码)
qsort函数的使用技巧(回调函数的例子,可以对任何数据类型进行排序)
void qsort(void *base, size_t num, size_t size, int (*cmp)(const void *, const void *));
其中 base
指向待排序数组, num
待排序数组元素个数,size
待排序数组中每个元素所占内存的大小(单位字节),cmp
是一个函数指针,指向一个函数,这个函数可以用来比较base数组里任意两个元素的大小。
比较两个整数
int cmp_int(const void* p1, const void* p2) {
return *(int*)p1 - *(int*)p2;
} //因为void*不能直接解引用,所以必须先进行强制类型转化
int main() {
int arr[] = { 6, 3, 9, 8, 5, 2, 4, 7, 1, 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, 4, cmp_int);
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
return 0;
}
比较两个结构体
struct Stu {
char name[20];
int age;
};
int cmp_struct(const void*p1,const void*p2){
return strcmp((*(struct Stu*)p1).name, (*(struct Stu*)p2).name);
} //因为void*不能直接解引用,所以必须先进行强制类型转化
int main() {
struct Stu arr[3] = { {"bbb",70},{"ccc",18},{"aaa",100} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(arr[0]), cmp_struct);
for (int i = 0; i < 3; i++) {
printf("%s ", arr[i].name);
}
return 0;
}
qsort函数的模拟实现 (核心在于无论什么类型的数据,都可以一个一个字节的交换)
int cmp_int(const void* p1, const void* p2) {
return *(int*)p1 - *(int*)p2;
//return *(int*)p2 - *(int*)p1 ;
}
void my_swap(char* p1,char* p2,size_t n) {
for (int i = 0; i < n; i++) {
char tem = *p1;
*p1 = *p2;
*p2 = tem;
p1++;
p2++;
}
}
void bubble_qsort(void * base,size_t sz,size_t width,int(*cmp)(const void *,const void *)) {
for (int i = 0; i < sz - 1; i++) {
for (int j = 0; j < sz - 1; j++) {
if (cmp(((char*)base + j * width), ((char*)base + (j+1)*width)) > 0) {
my_swap(((char*)base + j * width), ((char*)base + (j + 1) * width),width);
}
}
}
}
int main() {
int arr[] = { 6, 3, 9, 8, 5, 2, 4, 7, 1, 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_qsort(arr, sz, 4, cmp_int);
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
return 0;
}
\0
,计算结果没有区别。并且结果的个数是包含\0
在内的个数\0
来进行计数的,它会返回\0
之前所有字符的个数,不包含\0
。如果没有\0
,则会一直向后查找,直到遇到\0
为止。所以有可能造成越界访问对于数组名,当sizeof(数组名)
只有数组名时,代表整个数组的大小。但如果数组名和其他元素或数字一起放入()里,则表示首元素的地址,不在是那种特例了
arr[4]={1,2,3,4};
sizeof(arr);//结果是16
sizeof(arr+0);//结果是4/8 因为arr不是单独在里面,所以只能表示首元素地址(地址的大小取决与x86/x64)
对于srtlen(arr)来说,它接受的参数必须是一个指针,所以无论传进去什么,都会被当作指针对待,在函数内部会对它进行解引用操作。所以不能传入一个int等类型所以 strlen(*arr)
是一个错误语句。
int a[3]][4]={0};
sizeof(a[0])
a[0]相当于第一行首元素的地址,现在单独放在了sizeof()里边,代表的是整个第一行,所以答案是4*4
sizeof(a[0]+1)
现在a[0]不是单独放在sizeof()里面,所以a[0]只表示第一行第一个元素的地址,+1之后变成a[0][1]的地址
sizeof(a+1)
a不是单独放在sizeof()内部,所以表示的是首元素的地址,即第一行的地址,所以a+1表示第二行的地址,所以结果是4/8(因为其仍然是一个地址)
sizeof()
这个操作符在计算大小的时候,是根据类型推断的,不直接去访问所传入的空间,所以哪怕传入了一个野指针,也不造成越界访问,仅仅根据类型判断。
在x86条件下,该结构体大小是20个字节
struct test{
int a;
char* b;
short c;
short d[4];
char e[2];
}* p=(struct test*)0x100000;
1.p+0x1
2.(unsigned int*)p + 0x1
3.(unsigned long)p + 0x1
1.因为P是一个结构体指针类型,所以当+1之后跳过一个结构体的大小(即20个字节,答案为0x100014(16进制))
2.因为p被强转成了int*类型,所以p+1之后跳过一个整型的大小,答案为0x100004(16进制))
3.p被强转成long 类型之后,p就从一个指针变量变成了一个long型的变量,+1就相当与两个数字之间进行运算,所以答案为0x100001(16进制))
int arr[3][2]={(0,1),(2,3),(4,5)}
int* p;
p = a[0];
printf("%d\n",p);
对于数组arr来说,(0,1)其实是一个逗号表达式,它的结果是最后一个元素的值,即(0,1)==1。所以其实在数组arr里只放入了{1,3,5}.所以p是arr第一行第一个元素的地址。
都在头文件< ctype.h > 里面
strlen的返回值是size_t,是一种无符号整数。(对于无符号整数来说,是没有负数的,如果一个负数被转变为无符号整数,会直接将这个负数的补码看作一个无符号整数,也就是会变成一个很大的正数(因为负数首位符号位是1))
模拟实现(不创建临时变量)(使用递归)
size_t my_strlen(const char* str) {
if (*str != '\0') {
return 1 + my_strlen(str + 1);
}
else {
return 0;
}
}
int main() {
char arr[] = "abcdefaaa";
size_t len = my_strlen(arr);
printf("%zd ", len);
return 0;
}
strcpy(arr2, arr1)
将arr1的内容放入arr2里
\0
\0
也会被拷贝进入arr2模拟实现
版本一:
void my_strcpy(char* dest,char* src) {
while (*src != '\0') {
*dest = *src;
dest++;
src++;
}
*dest = *src;
}
版本二:
char* my_strcpy(char* dest, char* src) {
char* res=dest;
assert(dest&&src);
while (*dest++ = *src++) {
;
}
return res;
}
int main() {
char arr1[] = "asjdflks";
char arr2[20];
my_strcpy(arr2, arr1);
printf("%s", arr2);
return 0;
}
使用:在给定字符串后面追加另一个字符串
strcat(char * destination,const char* source)
模拟实现
char* my_strcat(char * dest,const char* src) {
char* res = dest;
while (*dest != '\0') {
dest++;
}
while (*dest++ = *src++) {
;
}
return res;
}
int main() {
char arr[20] = "hello ";
char arrr[] = "world";
my_strcat(arr, arrr);
printf("%s", arr);
return 0;
}//(注意,不可以追加两个相同的字符串,不然会因为‘\0’的丢失造成死循环)
比较的不是长度,是对应字符的ASCII码的大小
在传递时,如果传递的是“abc”,直接传递这样的常量字符串,也是可以使用char*的指针接收的。
模拟实现
int my_strcmp(const char* str1,const char*str2) {
assert(str1 && str2);
while (*str1 == *str2) {
if (*str1 == '\0') {
return 0;
}
str1++;
str2++;
}
return (*str1 - *str2);
}
int main() {
int res = my_strcmp("ax", "ade");
printf("%d", res);
return 0;
}
在字符串里查找一个字串第一次出现的地方
const char* strstr(const char* st1,const char* str2)
在str1里找str2
模拟实现
char* my_strstr(const char* str1,const char*str2) {
char* s1 = NULL;
char* s2 = NULL;
char* cur = str1;
while (*cur) {
s1 = cur;
s2 = str2;
while (*s1 && *s2 && *s1 == *s2) {
s1++;
s2++;
}
if (*s2=='\0') {
return cur;
}
cur++;
}
return NULL;
}
int main() {
char arr[] = "abcsdfhdf";
char arrr[] = "sd";
char* p = my_strstr(arr, arrr);
printf("%s", p);
return 0;
}
用于分割字符串(以所提供的字符为分隔符)
char* strtok(char* str,char* sep)
sep指向一个字符串,包含所有用于分割字符串的字符
如果待切割的字符串里有多个分割符,则调用一次该函数,会将从左边数第一个分隔符变成’\0’,并且每一次调用该函数会存下来这次操作到的位置,下一次在调用的时候,会直接从这个位置开始向后寻找下一个分隔符。除了第一次调用,其他时候调用时,str只需要传入NULL即可
示例:
int main() {
char arr[] = "[email protected]";
char a[] = "@.";
for (char* r = strtok(arr, a); r != NULL;r=strtok(NULL,a)) {
printf("%s\n", r);
}//用于分割字符串
return 0;
}
应该是内部实现的时候有一个static修饰的变量(只在第一次调用函数时初始化,之后再次调用该函数时,不会再次初始化,而是保留上一次调用结束时的值。)
每⼀个错误码(库函数的错误码)都是有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回。
char* strerror()
可以用于查看错误信息,在errno变量里
for (int i = 0; i < 20; i++) {
printf("%d:%s\n", i, strerror(i));
}
void perror(char* str)
它这个函数会自动打印错误信息,这个str参数是一个提示用的信息,自己输入的perror(“错误信息是:”)
打印结果就是:错误信息是:No such file or directory