目录
字符指针
-
字符指针的另一种使用方式
#include <stdio.h> int main() { //字符指针的使用 char ch = 'q'; char * pc = &ch; //本质上是把这个字符串的首字符地址存储在了指针变量ps中 const char * ps = "hello world"; //数组存储字符串时,是把整个字符串都存储在其中。 char arr[] = "hello world"; printf("%c\n",*ps);//h printf("%s\n",ps);//hello world %s打印,遇到字符串结束符\0停止 printf("%s\n",arr);//hello world return 0; }
-
因为常量字符串不可以被修改,所以相同的常量字符串,在内存中只会存储一份。
#include <stdio.h> int main() { //每个数组初始化都会开辟空间,即使两个数组中保存的是相同的数据,也会另外开辟空间,其内存地址不同。 char str1[] = "hello world."; char str2[] = "hello world."; // char * str3 = "hello world."; // char * str4 = "hello world."; //这里的"hello world."是一个常量字符串。不能使用*解引用操作修改。 //因为不能使用*修改,所以一般要使用const修饰,是用于修饰*str3或*str4,所以const要写在*的左边 const char * str3 = "hello world."; const char * str4 = "hello world."; //*str3 = "hehe"; 程序会挂掉,不能正常执行。 //因为常量字符串是不可以被修改的,而这两个字符串都一样,所以内存中只存储一个"hello world."字符串。 //str3和str4中保存的都是这个字符串首字符的地址。他们指向的是同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针,指向同一个字符串的时候,他们实际会指向同一块内存。所以str3==str4 if(str1 == str2) { printf("str1 与 str2 相同\n"); } else { printf("str1 与 str2 不相同\n"); } if(str3 == str4) { printf("str3 与 str4 相同\n"); } else { printf("str3 与 str4 不相同\n"); } //str1 与 str2 不相同 //str3 与 str4 相同 return 0; }
指针数组
int main()
{
int arr[10]; //整型数组:存放整型数据的数组
char ch[5]; //字符数组:存放字符的数组
//指针数组:存放指针的数组
int* parr[5]; //存放整型指针的数组
char* pch[5]; //存放字符型指针的数组
char** arrr[3]; //存放二级字符指针的数组
}
指针数组的使用
#include <stdio.h>
int main()
{
//这种使用方式很少见,没什么太大意义。
// int a = 10;
// int b = 20;
// int c = 30;
// int * arr[3] = {&a,&b,&c};
// int i;
// for(i=0 ; i<3 ; i++)
// {
// printf("%d ",*(arr[i]));//10 20 30
// }
int a[] = {1,2,3,4,5};
int b[] = {1,3,5,7,9};
int c[] = {2,4,6,8,10};
//数组名存储的是数组首元素的地址。
int * arr[3] = {a,b,c};
int i,j;
for(i=0 ; i<3 ; i++)
{
for(j=0 ; j<5 ; j++)
{
//解引用的方式
printf("%d ",*(arr[i]+j));
//arr[i]取出数组,然后[j]是数组中元素的下标。
//printf("%d ",arr[i][j]);
}
printf("\n");
}
return 0;
}
数组指针
-
数组指针如何定义
/* * 数组指针,是一种指向数组的指针。 * 数组指针的定义:指向的数组类型 (*数组指针变量名)[指向的数组中元素个数] = &数组名; */ int main() { //整型指针,指向整型的指针; int i = 10; int *pi = &i; // 字符指针,是指向字符的指针。 char c = 'c'; char * pc = &c; //数组指针 int arr[10] = {0}; //数组名是数组首元素的地址,arr存储数组首元素地址;&arr,取出的是整个数组的地址。 //因为[]下标引用符的优先级比*解引用操作符的优先级高,所以这样写就成了指针数组: //int * parr[10] = &arr; //所以要先让*与parr结合,这样就是指针了,结合之后再与[10]结合,就叫做数组指针 //parr就是一个数组指针,其中存放的是一个数组的地址。int表示指向的数组是int类型的,[10]表示指向的数组中存储了10个元素 int (*parr)[10] = &arr; //d是一个指针数组,其中存储的是一级浮点型指针 double * d[5]; //取d的地址,是取出d这个数组。d这个数组是double*类型的 ,有5个元素。 //又因为是存储数组的地址,所以pd是一个数组指针。加()先与*结合,再与[]结合 double* (*pd)[5] = &d; return 0; }
-
数组名 与 &数组名 的区别
/* * 数组名与&数组名的区别: * 数组名是数组首元素的地址 * 但是有两个例外: * - sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组大小,单位是字节。 * - &数组名,数组名表示整个数组,取出的是整个数组的地址。 */ #include <stdio.h> int main() { int arr[9] = {0}; //arr和&arr分别是什么? int * p1 = arr; int (*p2)[9] = &arr; //数组名arr表示的是数组首元素的地址,arr+1,加了4个字节,也就是加一个int类型大小。 printf("%p\n",p1);//0000009cbf1ff780 printf("%p\n",p1+1);//0000009cbf1ff784 //&arr,取出的是整个数组,&arr+1,加的是0x24,十进制就是36个字节,加的是整个数组大小。 printf("%p\n",p2);//0000009cbf1ff780 printf("%p\n",p2+1);//0000009cbf1ff7a4 return 0; }
-
数组指针使用示例,并不建议这样使用。
#include <stdio.h> int main() { int arr[10] = {0,1,2,3,4,5,6,7,8,9}; int (*pa)[10] = &arr; int i; for(i=0 ; i<10 ; i++) { //*(*pa)解引用,找到pa指向的数组arr,相当于直接使用数组名,+i再解引用,找到对应元素 //但是一般不这么使用,不如直接使用数组名进行操作。 printf("%d ",(*(*pa) + i)); //printf("%d ",*(arr + i)); } return 0; }
-
数组指针的使用
#include <stdio.h> print1(int arr[3][5]) { int i,j; for(i=0 ; i<3 ; i++) { for(j=0 ; j<5 ; j++) { printf("%d ",arr[i][j]); } printf("\n"); } } //传过来的是二维数组的首元素地址,这个地址可以说是指向一个一维数组,所以用数组指针来接收然后进行操作。 //p是一个数组指针,指向的数组是int类型的,并且有5个元素。 print2(int (*p)[5],int m) { int i,j; for(i=0 ; i<m ; i++) { for(j=0 ; j<5 ; j++) { //*(p+i)先找到其中的一维数组地址,再+j然后解引用,找到的是一维数组中的元素的地址。 printf("%d ",*(*(p+i)+j)); } printf("\n"); } } int main() { int arr[3][5] = {{1,2,3,4,5},{5,4,3,2,1},{2,3,3,3,3}}; //print1(arr); //二维数组的数组名,也表示首元素的地址,二维数组的首元素是{1,2,3,4,5} print2(arr,3); return 0; }
-
存储数组指针的数组
#include <stdio.h> int main() { //整型数组 //int arr[5]; //因为[]的优先级比*高,所以parr1先与[]结合,再与*结合。这是一个指针数组,其中存储int *类型的指针。整型指针数组。 int *parr1[10]; //(*parr2)中parr2先与*结合,也就是说这是一个指针,再与[10]结合。 //也就是说,parr2是一个数组指针,该指针指向一个int类型数组,这个数组中存储10个元素。 int (*parr2)[10]; //(*parr3[10])中parr3先与[10]集合,是一个数组。 //去掉数组名[],就是数组中元素的类型。剩下的是int(*)[5],所以说其中的每个元素是int * [5]类型的。 // 也就是说parr3是一个存储数组指针的数组,这个数组可以存储10个数组指针。每个指针指向一个5元素的int类型数组。 int (*parr3[10])[5]; //parr4先与*结合,说明是一个指针。再与[10]结合。 //parr4是一个数组指针,该指针指向一个int类型数组,这个数组中存储了5个元素。 int (*parr4)[5]; int arr[2] = {1,2}; //p是一个数组指针,指向arr数组 int (*p)[2] = &arr; //pfun是一个存放数组指针的数组。 int (*pfun[2])[2] = {p}; //*pf是一个指向数组指针数组的指针,指向存放数组指针的数组pfun int (*(*pf)[2])[2] = &pfun; //因为[]的优先级比*高,pf[0]可以找到pfun数组。pfun[0]存储的是p,p是一个数组指针,指向arr的地址。p[0]就是arr。 // 对arr进行解引用,找到的是数组首元素。 printf("%d\n",*pf[0][0][0]); //数组首元素地址+1就是数组arr下标为1的地址,我们就可以通过*(arr+i)来访问数组。 printf("%d\n",*(pf[0][0][0]+1)); return 0; }
数组传参、指针参数
-
一维数组传参
//元素个数可以省略,写了也没什么意义。写不写都可以 //void test(int arr[]){} //void test(int arr[10]){} //可以写成指针。数组名传参,传来的是首元素的地址,而这里是int数组,所以用int类型指针接收即可。 void test(int * arr){} //arr2是整型指针数组,所以用整型指针数组类型来接收。20可以省略 //void test2(int *arr[20]){} //用指针接收。因为是指针数组,所以首元素是一个指针,也就是说我们可以定义一个二级指针来保存这个指针的地址。 void test2(int * *arr){} int main() { int arr[10]= {0}; int * arr2[20] = {0}; test(arr); test2(arr2); return 0; }
-
二维数组传参
//二维数组传参,只能省略第一个[]中的数组,不能省略第二个[]里面的数字。 // 对于一个二维数组,可以不知道有多少行,但是必须知道一行有多少元素。然后通过元素的个数,再通过一行中的元素个数,就可以计算出有几行。 //void test(int arr[3][5]){} //void test(int arr[][]){} //这个写法错误 //void test(int arr[][5]){} //二维数组的首元素地址,是第一行(一维数组)的地址。也就是说这里应该是一个数组指针。因为一行有五个,所以应该是一个指向5元素数组的指针。 void test(int (*arr)[5]){} int main() { int arr[3][5] = {0}; test(arr); }
-
一级指针传参
#include <stdio.h> void print(int * ptr, int sz) { int i ; for(i=0 ; i<sz ; i++) { printf("%d ",*(ptr+i)); } } void test(char* p) { } int main() { int arr[10] ={1,2,3,4,5,6,7,8,9,10}; int* p = arr; int sz = sizeof(arr)/sizeof(arr[0]); //指针传参 print(p,sz); char ch = 'q'; char* pc = &ch; //如果函数接收一个指针,则我们可以传递指针,也可以直接传递取出的地址 test(&ch); test(pc); return 0; }
-
二级指针传参
#include <stdio.h> void test(int** p) { //*p解引用找到其存储的一级指针,再*解引用就可以找到一级指针指向的变量。 **p = 20; } int main() { int a = 10; int* pa = &a; // 一级指针 int** ppa = &pa; //二级指针 //把二级指针进行传参 test(ppa); printf("%d\n",a); //二级指针中保存的是一级指针的地址,所以可以取一级指针的地址传递进去。 test(&pa); printf("%d\n",a); //arr是一个指针数组。其中存储一级指针,是int*类型的。 int* arr[10] = {0}; //arr是数组名,存储的是数组首元素的地址,而这是一个指针数组。也就是说arr中存储的是一级指针的地址。 //所以如果函数的参数是一个二级指针,我们也可以传入指针数组的数组名。 //但这里,因为方法内执行逻辑不是为数组准备的,所以运行有错。 //test(arr); return 0; }
-
学过的指针与数组
/* * 一级指针 * - int * p; 整型指针,指向整形的指针 * - char * pc; 字符指针,指向字符的指针 * - void * pv; 五类型的指针 * * 二级指针 * - char** p; * - int** p; * * 数组指针:指向数组的指针 * - int (*p)[4] p指向一个4元素的int数组。 * * 数组 * - 一维数组 * - 二维数组 * - 指针数组:存放指针的数组 */
函数指针
-
函数指针语法
//函数返回类型 (*函数指针变量名)(函数参数[,函数参数......]) = 函数名/&函数名 #include <stdio.h> int Add(int x,int y) { return x+y; } void test(char* str){} int main() { //函数指针 —— 存放函数地址的指针。 //&函数名:取出函数的地址。 printf("%p\n",&Add);//00007ff6562617a1 //函数名,表示的也是函数的地址。 函数名==&函数名 printf("%p\n",Add);//00007ff6562617a1 //pf就是一个函数指针变量。 //因为()优先级要比*高,所以要写成(*pf),意思是pf是一个指针,指向参数为(int,int),返回类型为int的函数。 //int (*pf)(int,int) = &Add; //printf("%p\n",*pf);//00007ff6562617a1 //test函数指针 void (*pt)(char*) = &test; //通过指针调用函数: //先解引用(*pf)找到这个函数然后传递参数(3,5)。然后使用变量接收返回值。 //int ret = (*pf)(3,5); //printf("%d",ret);//8 //因为&函数名与函数名等效。也就是说Add的地址直接放到了pf中 //函数Add保存的是函数本身的地址,pf保存的是Add函数的地址。所以Add === pf int (*pf)(int,int) = Add; //平时我们调用函数: int ret = Add(3,9); printf("%d\n",ret);//12 //使用指针解引用调用 ret = (*pf)(10,33); printf("%d\n",ret);//43 //因为Add===pf,所以我们也可以这样调用: ret = pf(22,44); printf("%d\n",ret);//66 //所以调用时,(*pf)===pf===Add return 0; }
-
《C陷阱和缺陷》中的两段代码解读
/* * 调用0地址处的函数:(*(void (*)())0)(); 改函数没有参数,返回类型是void * - void (*)()是函数指针类型。 * void (*p)()是函数指针变量,p是变量名,指向的函数返回类型是void类型,函数没有参数。去掉变量名p就是函数指针类型。 * - (void (*)())0 ,对0进行强制类型转换,0就成为函数指针类型 * 想要调用地址为0位置的函数,就需要把0这个数字转换为指针类型。但事实上地址都是由编译器分配的,地址的分配不是固定的。 * 编写这个语句的程序员是为了模拟开机启动的情形,所以将这里的地址耦合为0,但如果要使用这种形式,那么这里的地址最好是动态的,不能是一个常量,因为可能这个地址没有被开辟出来。 * - (*(void (*)())0) ,0被转换为指针类型之后,就可以解引用,找到0所在位置的函数。 * - (*(void (*)())0)(),找到0位置的函数之后,()进行函数调用。 * * */ /* * void (*signal(int,void(*)(int)))(int); * - void(*)(int)是一个函数指针类型,指向参数为int,返回类型为void的函数。作为signal函数的参数。 * - signal(int,void(*)(int),因为()优先级高,所以signal先与()结合,就是一个函数。剩下void(*)(int)是一个函数指针类型 * - 所以signal函数的返回类型也是一个函数指针。该指针指向参数为int,返回类型为void的函数 * - signal是一个函数声明,既不是定义也不是调用。这里有函数的名字、函数的参数、还有函数的返回类型:void(*)(int)。 * * 函数声明语法:返回返回类型 函数名(函数参数[,函数参数...]); * - 该函数声明本来应该是:void(*)(int) signal(int,void(*)(int)); 但是语法规定不可以这样写。 * 语法规定:函数的返回类型如果是一个指针,那么*必须与函数名连接在一起。所以就成为了:void (*signal(int,void(*)(int)))(int); * * - 如何简化呢? */ int main() { //使用typedef对类型进行重定义 //typedef void(*)(int) pfun_t; 指针类型的*要与名字写在一起,所以就成为: typedef void(*pfun_t)(int); //将void(*)(int)函数指针类型,重命名为pfun_t pfun_t signal(int,pfun_t); return 0; }
函数指针数组
-
函数指针数组的定义
//函数指针数组:存放函数指针的数组 //函数指针数组定义语法:指向的函数的返回类型 (*函数指针数组变量名[函数指针数组元素个数])(函数参数); int Add(int x, int y) { return x+y; } int Sub(int x, int y) { return x-y; } int main() { int (*pf1)(int,int) = Add; int (*pf2)(int,int) = Sub; //函数指针数组,也是一个数组,所以直接在变量名后面加[2]。pfArr[2]就表示是一个数组 //剩下的int (*)(int,int)是数组元素类型。也就是说每个元素都指向一个参数为(int,int),返回类型为int的函数。 int(*pfArr[2])(int,int) = {Add, Sub}; return 0; }
-
函数指针数组的应用:计算器
//计算器,计算整型变量的加减乘除。 #include <stdio.h> 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 menu() { printf("------------------------------------\n"); printf("------输入菜单序号执行对应功能---------\n"); printf("------------- 0.退出 ----------------\n"); printf("------------- 1.加法 ----------------\n"); printf("------------- 2.减法 ----------------\n"); printf("------------- 3.乘法 ----------------\n"); printf("------------- 4.除法 ----------------\n"); printf("-------------------------------------\n"); } int main() { //条件 int input = 1; //函数指针数组 int(*pfArr[5])(int,int) = {0,Add,Sub,Mul,Div}; //循环 while(input) { menu(); printf("请选择:"); scanf("%d",&input); if(input >0 && input<5) { int x,y,ret; printf("输入要进行操作的两个数:"); scanf("%d %d",&x,&y); //ret = (*pfArr[input])(x,y); //*可以省略。 ret = pfArr[input](x,y); printf("ret=%d\n",ret); } else if (input == 0) { printf("退出程序"); } else { printf("输入有误请重新输入\n"); } } return 0; }
指向函数指针数组的指针
/*
* - 整型数组: int arr[5]
* 将这个整型数组存储到指针中,就叫做整型数组指针:int (*p)[5] = &arr;
*
* - 整型指针数组:int* arr[5]
* 存储整型指针数组的指针,就叫做:指向整型指针数组的指针。int *(*p)[5] = &arr,p2是一个数组指针,指向的数组类型是int * [5]。
*
* - 函数指针: int (*p)(int,int);
* 函数指针数组:int (*p2[5])(int,int); p2数组中存储了5个指针,指针类型是int (*)(int,int),每个指针都指向参数为(int,int),返回类型为int的函数。
* - &p2,取出的是函数指针数组的地址。 int (*(*p3)[5])(int,int) = &p2;
* p3先与*结合,表示p3是一个指针,再与[5]结合,表示指向的是一个数组。
* 剩下的int(*)(int,int)是函数指针类型,表示该数组中存储的是函数指针。也就是说p3是指向函数指针数组的指针。
*/
//一个数组,如 int arr[10]
//去掉数组名,是数组类型:int [10] ; 去掉数组名[],是数组中的元素类型:int
#include <stdio.h>
void test(const char* str)
{
printf("%s\n",str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针数组pfunArr
void (* pfunArr[5])(const char*);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char *) = &pfunArr;
return 0;
}
回调函数
-
回调函数是什么?
/* * 回调函数是一个通过函数指针调用的函数。 * - 如果你把函数指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这时回调函数。 * - 回调函数不是由该函数的实现方直接调用,而是在特定的时间或条件发生时由另一方调用,用于对该事件/条件进行响应。 */
-
使用回调函数改进以下程序
#include <stdio.h> 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 menu() { printf("------------------------------------\n"); printf("------输入菜单序号执行对应功能---------\n"); printf("------------- 0.退出 ----------------\n"); printf("------------- 1.加法 ----------------\n"); printf("------------- 2.减法 ----------------\n"); printf("------------- 3.乘法 ----------------\n"); printf("------------- 4.除法 ----------------\n"); printf("-------------------------------------\n"); } int main() { int x, y; int input = 1; int ret = 0; do { menu(); printf( "请选择:" ); scanf( "%d", &input); switch (input) { case 1: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = Add(x, y); printf( "ret = %d\n", ret); break; case 2: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = Sub(x, y); printf( "ret = %d\n", ret); break; case 3: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = Mul(x, y); printf( "ret = %d\n", ret); break; case 4: printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = Div(x, y); printf( "ret = %d\n", ret); break; case 0: printf("退出程序\n"); break; default: printf( "选择错误\n" ); break; } } while (input); return 0; }
改进
#include <stdio.h> 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 menu() { printf("------------------------------------\n"); printf("------输入菜单序号执行对应功能---------\n"); printf("------------- 0.退出 ----------------\n"); printf("------------- 1.加法 ----------------\n"); printf("------------- 2.减法 ----------------\n"); printf("------------- 3.乘法 ----------------\n"); printf("------------- 4.除法 ----------------\n"); printf("-------------------------------------\n"); } void calc(int (*pf)(int,int )) { int x, y,ret; printf( "输入操作数:" ); scanf( "%d %d", &x, &y); ret = pf(x, y); printf( "ret = %d\n", ret); } int main() { int input = 1; 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"); break; default: printf( "选择错误\n" ); break; } } while (input); return 0; }
-
复习:冒泡排序的实现
#include <stdio.h> void bubble_sort(int arr[], int sz) { int i,j; //冒泡排序的趟数:有10个元素,就要排序9趟,剩下最后一个元素不用再与别的元素比较。趟数=元素个数-1 for(i=0 ; i<sz-1 ; i++) { //一趟冒泡排序。每趟要比较的次数=元素个数-1-i ,每次排完序最后一个元素的顺序就确定下来,所以随着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[10] = {9,8,7,6,5,4,3,2,1,0}; int sz = sizeof(arr)/sizeof(arr[0]); bubble_sort(arr,sz); int i; for(i=0 ; i<sz ; i++ ) { printf("%d ",arr[i]); } }
-
qsort排序函数的使用
/* * qsort函数() * - 函数原型:void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*)); * - 作用:对数组元素进行排序。 * 对base指向的数组的num个元素进行排序,每个元素大小为字节。使用比较函数确定顺序。 * 此函数使用的排序算法通过调用指定的比较函数并将指向他们的指针作为参数来比较元素。 * 该函数不返回任何值,但会修改由base指向的数组的内容,按照compar定义对其元素重新排序。 * * 参数: * - base:指向要排序的数组的第一个对象的指针。并将其转换为void *类型。 * void *,表示无具体类型,什么类型都可以传入。 * - num:base指向的数组中的元素个数。size_t是无符号整数类型 * - size:数组中每个元素的大小(以字节为单位)。size_t是无符号整数类型 * - compar:指向比较两个元素的函数的指针。qsort会反复调用这个函数比较两个元素。 * 该函数原型:int compar (const void* p1, const void* p2); * 将两个指针作为参数(都转换为const void *类型),该函数通过返回值定义元素顺序: * - 返回值<0 , p1指向的元素在p2指向的元素之前 ,p1<p2 * - 返回值=0 , p1指向的元素等价于p2指向的元素 , p1=p2 * - 返回值>0 , p1指向的元素在p2指向的元素之后 , p1>p2 * */ #include <stdio.h> #include <stdlib.h> #include <string.h> void print(int arr[],int sz) { int i; for(i=0 ; i<sz ; i++ ) { printf("%d ",arr[i]); } } int cmp_int(const void* p1,const void* p2) { //因为p1与p2都是void* 无类型指针。没有办法比较。 //而我们知道传入的数据是int类型的,所以我们可以将其先强制类型转换为int*类型的指针(int*)p1、(int*)p2 //然后再解引用,就可以进行比较了。*(int*)p1 - *(int*)p2 //如果p1大于p2,就会将这个值返回,返回的是一个大于0的数,就表示p1>p2;如果小于,返回的是一个小于0的数,就表示p1<p2 //如果相等,就返回0,0就表示这两个数相等; // return *(int*)p1 - *(int*)p2; //升序就是p1-p2;反之,降序是:p2-p1 return *(int*)p2 - *(int*)p1; } void qsort_int() { 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]),cmp_int); //打印 print(arr,sz); } //定义结构体 struct Stu { char name[20]; int age; }; //结构体打印函数 void print_stu(struct Stu * s,int sz) { int i; for(i=0 ; i<sz ; i++) { printf("%s %d\n",(s+i)->name,(s+i)->age); } } int cmp_age(const void* p1,const void* p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } int cmp_name(const void* p1,const void* p2) { /* * strcmp函数返回值: * - str1 < str2,返回值小于0 * - str1 = str2,返回值等于0 * - str1 > str2,返回值大于0 * 比较这两个字符串相同位置的字符的ASCII码,一旦出现不匹配的,就比较这两个字符。ASCII码低的就小,高的就大。 * 如abcq与adc进行比较,两个字符串第一个字符相同,比较第二个字符,d比b大,所以adc>abcq。 * 比较的是对应位置的字符的大小,而不是比较字符串长度。 * - 正好与qsort()函数的比较函数规定发返回值判断相符。 */ return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name); } void qsort_stu() { //使用qsort函数为结构体数据排序 struct Stu s[] = {{"zhangsan",30},{"lisi",34},{"wangwu",20}}; int sz = sizeof(s)/sizeof(s[0]); printf("排序前:\n"); print_stu(s,sz); //按照年龄排序 //qsort(s,sz, sizeof(s[0]),cmp_age); //按照名字排序 qsort(s,sz,sizeof(s[0]),cmp_name); printf("排序后:\n"); print_stu(s,sz); } int main() { //整型数据的排序 //qsort_int(); //结构体数据的排序 qsort_stu(); }
-
模拟qsort实现一个冒泡排序算法
#include <stdio.h> #include <string.h> Swap(char* buf1,char* buf2,int size) { int i; for(i=0 ; i<size ; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; //当前字节交换完之后,交换下一个字节。 buf1++; buf2++; } } void bubble_sort(void* base,int sz,int size,int (*cmp)(const void * e1,const void * e2)) { int i,j; for(i=0 ; i<sz-1 ; i++) { for(j=0 ; j<sz-1-i ; j++) { //因为不知道传入的数据是什么类型的,所以接收一个比较函数的指针。 //调用cmp()就可以得到函数返回值。根据返回数据的大小来决定如何排序 //应该是arr[j]与arr[j+1]比较。传进来的是数组首元素地址,我们需要通过这个地址来获取到每次进行比较的两个元素 //将其指针强制类型转换为char*指针类型。并且通过传入的字节大小,来决定每次跳过的字节数。 //首元素地址:base 。 假如是int类型,j=0时就是base 与base+4比较;j=1时,就是base+4与base+4*2比较 //arr[j] 就可以写为:(char*)base+j*size ; arr[j+1]可以写为:(char*)base+(j+1)*size //强制类型转换,转换的是base,转换完之后base就成为了char*类型指针。char类型占一个字节,所以指针+几,就往后跳过几个字节。 if(cmp((char*)base+j*size,(char*)base+(j+1)*size)>0) { //交换 //交换的是两个指针指向的内容。但是我们只是知道地址,而不知道其是什么类型的数据。 //解决:不管其是什么类型的数据,我们把他们的每个字节的内容都进行交换,这样也相当于交换了内容。 Swap((char*)base+j*size,(char*)base+(j+1)*size,size); } } } } int cmp_int(const void* p1,const void* p2) { //因为p1与p2都是void* 无类型指针。没有办法比较。 //而我们知道传入的数据是int类型的,所以我们可以将其先强制类型转换为int*类型的指针(int*)p1、(int*)p2 //然后再解引用,就可以进行比较了。*(int*)p1 - *(int*)p2 //如果p1大于p2,就会将这个值返回,返回的是一个大于0的数,就表示p1>p2;如果小于,返回的是一个小于0的数,就表示p1<p2 //如果相等,就返回0,0就表示这两个数相等; // return *(int*)p1 - *(int*)p2; //升序就是p1-p2;反之,降序是:p2-p1 return *(int*)p1 - *(int*)p2; } void print(int arr[],int sz) { int i; for(i=0 ; i<sz ; i++ ) { printf("%d ",arr[i]); } } void test() { //整型排序的数据 int arr[] = {1,3,5,7,9,0,2,4,6,8}; int sz = sizeof(arr)/sizeof(arr[0]); //排序 bubble_sort(arr,sz,sizeof(arr[0]),cmp_int); print(arr,sz); } //定义结构体 struct Stu { char name[20]; int age; }; //结构体打印函数 void print_stu(struct Stu * s,int sz) { int i; for(i=0 ; i<sz ; i++) { printf("%s %d\n",(s+i)->name,(s+i)->age); } } int cmp_age(const void* p1,const void* p2) { return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age; } int cmp_name(const void* p1,const void* p2) { /* * strcmp函数返回值: * - str1 < str2,返回值小于0 ;相等,则返回值等于0 ;str1 > str2,返回值大于0。 * 比较这两个字符串相同位置的字符的ASCII码,一旦出现不匹配的,就比较这两个字符。ASCII码低的就小,高的就大。 * 如abcq与adc进行比较,两个字符串第一个字符相同,比较第二个字符,d比b大,所以adc>abcq。 * 比较的是对应位置的字符的大小,而不是比较字符串长度。 * - 正好与qsort()函数的比较函数规定发返回值判断相符。 */ return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name); } void qsort_stu() { //使用qsort函数为结构体数据排序 struct Stu s[] = {{"zhangsan",30},{"lisi",34},{"wangwu",20}}; int sz = sizeof(s)/sizeof(s[0]); printf("排序前:\n"); print_stu(s,sz); //按照年龄排序 //bubble_sort(s,sz, sizeof(s[0]),cmp_age); //按照名字排序 bubble_sort(s,sz,sizeof(s[0]),cmp_name); printf("排序后:\n"); print_stu(s,sz); } int main() { //test(); qsort_stu(); return 0; }
-
void *类型
#include <stdio.h> int main() { int a = 10; char ch = 'w'; //void为无具体类型,void*就是无具体类型指针。其指针变量中,什么类型的变量地址都可以存储。 void* p = &a; p = &ch; //但是在引用的时候不能直接引用,因为void*是无具体类型的,所以解引用或进行指针运算,编译器不知道访问几个字节。 //*p解引用或p++,这样的用法都是没有意义的。必须是我们将其强制转换为具体的类型才可以使用。 printf("%c",*(char*)p); //w return 0; }
练习
数组名的意义
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名表示整个数组,取出的是整个数组的大小
- 除此之外的所有数组名都表示首元素的地址
-
int数组中关于sizeof()求大小
/* * 除了这两种情况,剩下的数组名都表示数组首元素地址 * - sizeof(数组名),这里的数组名,代表整个数组。计算的是整个数组的大小 * - &数组名,数组名表示整个数组,取出的是整个数组的地址。 */ #include <stdio.h> int main() { int a[] = {1,2,3,4}; //sizeof(数组名),计算整个数组的大小。int类型数组中有4个元素。4*4=16 printf("%d\n",sizeof(a));//16 //a+0,a中存储数组首元素地址,首元素地址+0:代表的还是首元素地址。sizeof(a+0)计算地址的大小 //在32位平台下,地址大小是4个字节;在64位平台下,地址是8个字节。 printf("%d\n",sizeof(a+0));//4/8 //数组名a中存储的是数组首元素的地址,解引用找到的是数组首元素:1。1是int类型数据,所以应该是4 printf("%d\n",sizeof(*a));//4 //a+1,a中存储数组首元素地址,首元素地址+1:代表的数组中第二个元素的地址。sizeof(a+1)计算地址的大小 printf("%d\n",sizeof(a+1));//4/8 //计算数组中第二个元素的大小。 printf("%d\n",sizeof(a[1]));//4 //&a,取出整个数组地址。这是一个指向数组的指针。sizeof(地址) 4/8 printf("%d\n",sizeof(&a));//8 //&a,取出整个数组地址。解引用找到数组。也就是计算整个数组大小 printf("%d\n",sizeof(*&a));//16 //&a,取出整个数组地址。数组地址+1,跳过整个数组之后的一块地址。还是一个地址,所以这里是计算地址的大小。4/8 printf("%d\n",sizeof(&a+1));//8 //a[0]是数组首元素,&a[0]取出数组首元素地址。是一个地址,4/8 printf("%d\n",sizeof(&a[0]));//8 //a[0]是数组首元素,&a[0]取出数组首元素地址,&a[0]+1,数组首元素地址+1,就是数组第二个元素地址。是一个地址,4/8 printf("%d\n",sizeof(&a[0]+1));//8 return 0; }
-
字符数组(不存放字符串结束符),sizeof以及strlen的计算
/* * 除了这两种情况,剩下的数组名都表示数组首元素地址 * - sizeof(数组名),这里的数组名,代表整个数组。计算的是整个数组的大小 * - &数组名,数组名表示整个数组,取出的是整个数组的地址。 */ #include <stdio.h> #include <string.h> int main() { char arr[] = {'a','b','c','d','e','f'}; /* //计算数组大小。char类型占一个字节,arr中有6个元素。 printf("%d\n",sizeof(arr));//6 //arr是数组首元素地址;arr+0,还是首元素地址。地址的大小:4/8 printf("%d\n",sizeof(arr+0));//8 //arr是数组首元素地址,*arr找到数组首元素。也就是计算char类型大小 printf("%d\n",sizeof(*arr));//1 //arr[1]是数组首元素,计算数组首元素大小,也就是计算char类型大小 printf("%d\n",sizeof(arr[1]));//1 //&arr,取出整个数组地址。整个数组地址,也是一个地址,地址大小:4/8 printf("%d\n",sizeof(&arr));//8 //&arr,取出整个数组地址。&arr+1,跳过整个数组之后的一块地址。还是地址,计算地址的大小。4/8 printf("%d\n",sizeof(&arr+1));//8 //arr[0]是数组首元素,&arr[0]取出数组首元素地址,&arr[0]+1,数组首元素地址+1,就是数组第二个元素地址。是一个地址,4/8 printf("%d\n",sizeof(&arr[0]+1));//8 */ //计算字符串长度,strlen计算字符串长度时,直到遇见字符串结束符'\0'才结束 //arr是首元素地址,会一直往后取。直到碰到\0才结束。所以这里是个随机值 printf("\n%d\n",strlen(arr));//随机值 //首元素地址+0,还是首元素地址。然后往后取,直到碰到\0才结束。与以上相同,是个随机值。 printf("%d\n",strlen(arr+0));//随机值 //strlen函数的原型:size_t __cdecl strlen(const char *_Str); //其参数是一个指针,也就是说参数应该是一个指针,或者直接传地址过去。 //&arr,取出的是整个数组地址,该地址是char (*)[6]类型。然后放入strlen函数,被强制类型转换为char*类型。 //也会从数组首元素开始取,直到碰到字符串结束符结束 printf("%d\n",strlen(&arr));//随机值 //&arr+1,紧接着数组地址后的一块地址。该地址也是char (*)[6]类型。然后放入strlen函数,被强制类型转换为char*类型。 //会从数组后第一个元素开始取,直到碰到字符串结束符结束。也就是说要比以上随机值少6个字符。 printf("%d\n",strlen(&arr+1));//随机值-6 //arr[0]是数组首元素,&arr[0]取出数组首元素地址,&arr[0]+1,数组首元素地址+1,就是数组第二个元素地址。 //从数组第二个元素取直到取到字符串结束符\0,少了数组首元素。所以应该是随机值-1 printf("%d\n",strlen(&arr[0]+1));//随机值-1 //*arr解引用找到数组首元素'a',a的ASCII码是97。97传入strlen函数,该函数需要一个地址。 //可能被解析为:是要找97所在位置的数据,但是我们并不知道97位置有没有数据,所以这里是报错的。 printf("%d\n",strlen(*arr));// 报错 //arr[1]是数组首元素'b',b的ASCII码是98。找98位置数据,同样报错。 printf("%d\n",strlen(arr[1]));//报错 return 0; }
字符数组(存放字符串结束符),sizeof以及strlen的计算
#include <stdio.h> #include <string.h> int main() { //因为这个字符串自带一个字符串结束符。所以arr数组中存储的元素实际上是7个:a b c d e f \0 char arr[] = "abcdef"; /* //计算数组大小。char类型占一个字节,arr中有7个元素。 printf("%d\n",sizeof(arr));//7 //arr是数组首元素地址;arr+0,还是首元素地址。地址的大小:4/8 printf("%d\n",sizeof(arr+0));//8 //arr是数组首元素地址,*arr找到数组首元素。也就是计算char类型大小 printf("%d\n",sizeof(*arr));//1 //arr[1]是数组首元素,计算数组首元素大小,也就是计算char类型大小 printf("%d\n",sizeof(arr[1]));//1 //&arr,取出整个数组地址。地址大小:4/8 printf("%d\n",sizeof(&arr));//8 //&arr,取出整个数组地址。&arr+1,跳过整个数组之后的一块地址。还是地址,计算地址的大小。4/8 printf("%d\n",sizeof(&arr+1));//8 //arr[0]是数组首元素,&arr[0]取出数组首元素地址,&arr[0]+1,数组首元素地址+1,就是数组第二个元素地址。是一个地址,4/8 printf("%d\n",sizeof(&arr[0]+1));//8 */ //注意:字符串长度,不计算最后的字符串结束符\0 //计算字符串长度,strlen计算字符串长度时,直到遇见字符串结束符'\0'才结束 //arr是首元素地址,会一直往后取。直到碰到\0才结束。这里是6 printf("\n%d\n",strlen(arr));//6 //首元素地址+0,还是首元素地址。然后往后取,直到碰到\0才结束。与以上相同,是6 printf("%d\n",strlen(arr+0));//6 //strlen函数的原型:size_t __cdecl strlen(const char *_Str); //其参数是一个指针,也就是说参数应该是一个指针,或者直接传地址过去。 //&arr,取出的是整个数组地址,该地址是char (*)[6]类型。然后放入strlen函数,被强制类型转换为char*类型。 //也会从数组首元素开始取,直到碰到字符串结束符结束 printf("%d\n",strlen(&arr));//6 //&arr+1,紧接着数组地址后的一块地址。该地址也是char (*)[6]类型。然后放入strlen函数,被强制类型转换为char*类型。 //会从数组后第一个元素开始取,直到碰到字符串结束符结束。也就是说是个随机值 printf("%d\n",strlen(&arr+1));//随机值 //arr[0]是数组首元素,&arr[0]取出数组首元素地址,&arr[0]+1,数组首元素地址+1,就是数组第二个元素地址。 //从数组第二个元素取直到取到字符串结束符\0,少了数组首元素。所以应该是6-1=5 printf("%d\n",strlen(&arr[0]+1));//5 //*arr解引用找到数组首元素'a',a的ASCII码是97。97传入strlen函数,该函数需要一个地址。 //可能被解析为:是要找97所在位置的数据,但是我们并不知道97位置有没有数据,所以这里是报错的。 printf("%d\n",strlen(*arr));// 报错 //arr[1]是数组首元素'b',b的ASCII码是98。找98位置数据,同样报错。 printf("%d\n",strlen(arr[1]));//报错 return 0; }
-
常量字符串存储到指针中
#include <stdio.h> #include <string.h> int main() { //p指针变量中存储的a的地址 char* p = "abcdefgh"; /* //p是一个指针。在32位平台下,指针占4个字节;64位平台下,指针占8个字节。 4/8 printf("%d\n",sizeof(p));//8 //p+1,指针+1,指向的是b。但是p+1是个地址,地址大小:4/8 printf("%d\n",sizeof(p+1));//8 //*p解引用找到a,a是字符类型,占一个字节大小 printf("%d\n",sizeof(*p));//1 //p[0] == *(p+0),可以找到a,char类型。 printf("%d\n",sizeof(p[0]));//1 //&p,取出p的地址。地址大小:4/8 printf("%d\n",sizeof(&p));//8 //&p+1,是一个地址。地址大小:4/8 printf("%d\n",sizeof(&p+1));//8 //p[0]找到a,取出其地址,&p[0]+1,是一个地址。地址大小:4/8 printf("%d\n",sizeof(&p[0]+1));//8 */ //strlen函数的原型:size_t __cdecl strlen(const char *_Str); // 其参数是一个指针,也就是说参数应该是一个指针,或者直接传地址过去。 //p是一个指针,指向a。一直取到字符串结束,是6 printf("%d\n",strlen(p));//6 //p是指向a的char类型指针,p+1,跳过1个字节,就是指向b。从b开始取到字符串结束,6-1=5 printf("%d\n",strlen(p+1));//5 //&p,取出的是p的地址。&p是一个char**类型,放入strlen函数,被强制转换为char*类型。 //&p是一个地址,占8个字节。 地址的每个字节都被当作一个字符。直到取到\0(或者是十六进制的00)才会结束,其后面不知道什么时候才能取到字符串结束符,所以这里是一个随机值。 printf("&p:%d\n",strlen(&p));//随机值 //&p,取出的是p的地址。&p+1,是&p后面紧接着的那个地址。其后面不知道什么时候才能取到字符串结束符,所以这里是一个随机值 printf("&p+1:%d\n",strlen(&p+1));//随机值 //这两个地址都是随机值,有没有什么联系呢? //可能有联系、可能没有联系 //没有联系的情况: //p变量的地址(&p):0x 00 00 7f f7 60 31 a0 10 //因为内存中采用小端存储形式,也就是低位低地址。所以&p存储到内存中是:10 a0 31 60 f7 7f 00 00。 //本来使用指针来取,则是倒着取出来。但是传入strlen函数,被强制转换为char*类型 // 每次取出一个字节,10,a0,31,60,f7,7f,然后遇到字符串结束符00。00也是字符串结束符,所以这里是6。 //紧接着10 a0 31 60 f7 7f 00 00后的8个字节就是&p+1的中存储的内容,从开头开始取,直到取到字符串结束符。不管什么时候取到字符串结束符,也与&p的长度没有关系 //此时&p与&p+1就没有联系 //有联系的情况: //p变量的地址(&p):0x11 22 7f f7 60 31 a0 10 //因为内存中采用小端存储形式,也就是低位低地址。所以&p存储到内存中是:10 a0 31 60 f7 7f 22 11。 //本来使用指针来取,则是倒着取出来。但是传入strlen函数,被强制转换为char*类型 //从开头开始取,每次取出一个字节,10,a0,31,60,f7,7f,22,11,没有遇到字符串结束符。 //紧接着10 a0 31 60 f7 7f 22 11后的8个字节就是&p+1的中存储的内容,继续取,直到取到字符串结束符。 //不管什么时候遇到结束符,此时生成的随机值关系:&p的随机值减去8就是&p+1的随机值。 //因为是在64位平台下,所以是减去8。如果是32平台下,那么地址的长度是4个字节,就是减去4。 //p[0]找到a,&p[0]取出a的地址,&p[0]+1,也就是b所在的地址,然后取到字符串结尾。应该是6-1=5 printf("%d\n",strlen(&p[0]+1));//5 //*p解引用找到数组首元素'a',a的ASCII码是97。97传入strlen函数,该函数需要一个地址。 //可能被解析为:是要找97所在位置的数据,但是我们并不知道97位置有没有数据,所以这里是报错的。 printf("%d\n",strlen(*p));//报错 //p[0]==*(p+0),还是首元素a,与以上原理相同 printf("%d\n",strlen(p[0]));//报错 return 0; }
-
二维数组
#include <stdio.h> int main() { int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; //printf("%d\n",*(a[0]+1)); //a是数组名,在这里表示整个数组。4*3*4=48 printf("%d\n",sizeof(a));//48 //a[0][0]表示二维数组中首元素的首元素,是一个int类型元素。 printf("%d\n",sizeof(a[0][0]));//4 //a[0]==*(a+0)是二维数组中的首元素。二维数组的首元素,也就是首行。a[0]就可以理解位第一行的数组名。 //数组名单独放在sizeof中,代表这个数组。第一行有4个元素,每个元素占4个字节。4*4=16 printf("%d\n",sizeof(a[0]));//16 //a[0]是二维数组首行地址。a[0]单独使用时是代表首行四个元素。 //a[0]+1,此时a[0]不再代表首行地址,而是首行的首个元素地址。a[0]+1,是指针运算,就表示首行的第二个元素的地址。地址大小:4/8 printf("%d\n",sizeof(a[0]+1));//8 //a[0]+1是首行的第二个元素。所以其大小是:4 printf("%d\n",sizeof(*(a[0]+1)));//4 //a作为二维数组数组名,没有&,也没有单独放在sizeof内部。 //所以这里a代表二维数组第一行,a+1是二维数组第二行的地址。地址大小:4/8 printf("%d\n",sizeof(a+1));//8 //a+1是二维数组第二行的地址。解引用可以找到二维数组第二行。 printf("%d\n",sizeof(*(a+1)));//16 //a[0]是二维数组第一行的数组名,&a[0],取出二维数组第一行地址。&a[0]+1,就表示二维数组第二行的地址。地址大小:4/8 printf("%d\n",sizeof(&a[0]+1));//8 //&a[0]+1,二维数组第二行的地址。*(&a[0]+1),解引用找到二维数组第二行。这一行大小:16 printf("%d\n",sizeof(*(&a[0]+1)));//16 //a是二维数组数组名,其中存储的是首行地址。*a解引用找到第一行。第一行大小:16 printf("%d\n",sizeof(*a));//16 //我们定义的二维数组只有三行,而a[3]是数组的第四行了,也就是说数组访问越界了。 //但是在C语言中,编译器不检查数组越界。而我们有说过一个表达式有两种属性,一种是值属性、一种是类型属性。 //而sizeof()中的表达式在编译阶段就执行了,其中的表达式并不会真正计算,不是真正去访问a[3],从而也不会出现数组越界的情况。 //所以其运用的就是表达式的类型属性。a[3]是int [4]类型的,所以4*4=16,a[3]的大小是:16。 //计算了其类型大小之后,这个表达式在运行阶段也不会再执行。 printf("%d\n",sizeof(a[3]));//16 //复习 short s = 5; int i = 4; //这里最后的运算结果是放在s中,所以是计算s的大小,s是short类型,占两个字节。 //而其中的表达式在编译阶段,只是计算大小,不会执行其表达式。 // 并且在运行阶段,这里的sizeof(s=a+6)就已经被计算好的大小2替换掉了,也就是说其中的表达式并不会执行。所以s的值并没有改变 printf("\n%d\n",sizeof(s=a+6));//2 printf("%d\n",s);//5 return 0; }
指针笔试题
-
程序的输出结果
#include <stdio.h> int main() { int a[5] = {1,2,3,4,5}; //&a,取出的是整个数组地址,&a+1,跳过a数组的紧接着的这个地址。强制转换为int*类型,放入ptr指针中 int * ptr = (int*)(&a+1); //a是数组首元素地址,a+1,是数组第二个元素的地址。*(a+1)找到数组的第二个元素,就是2。 //ptr是数组后的那个地址,因为被强制转换为了int*指针,所以ptr-1得到的是数组最后一个元素的地址。 // *(ptr-1) 解引用找到的就是数组最后一个元素,就是5。 printf("%d %d",*(a+1),*(ptr-1));//2 5 return 0; }
-
表达式的值
#include <stdio.h> //该结构体的大小是:32位平台下,20个字节;64位平台下32个字节 struct Test { int Num; char* paName; short sDate; char cha[2]; short sBa[4]; }* p; //p是一个指针变量,类型是:struct Test * int main() { printf("%d\n",sizeof(struct Test)); //32 //假设p的值为0x100000,如下表达式的值分别是多少。 //因为p是结构体类型指针,并且其结构体大小是20/32个字节。指针+1,就表示跳过一个结构体大小。 // 十进制20转换为十六进制就是:0x14。十进制的32转换为十六进制是:0x20 //p+0x1 就是:0x100014 0x100020 printf("%p\n",p+0x1);//+十六进制的0x20 //p的值为0x100000,强制类型转换为unsigned long之后,+0x1。就是0x100001 printf("%p\n",(unsigned long)p+0x1);//+数字1 //p的值为0x100000,强制类型转换为unsigned int *之后,因为是unsigned int*指针,+0x1,就是加一个int类型大小:4。就是0x100004 printf("%p\n",(unsigned int*)p+0x1);//+4 return 0; }
-
指针的使用,内存的读取
#include <stdio.h> int main() { int a[4] = {1,2,3,4}; //&a,取出整个数组的地址。&a+1,就表示紧挨着数组的后面的那个地址。强制类型转换为int*类型指针,存储到ptr1中 int *ptr1 = (int *)(&a+1); //a表示数组首元素,强制转换为int类型后,此时a是一个数字,+1后再强制类型转换为int*指针 //int *ptr2 = (int *)((int)a+1);//这个没有办法运行,报错:无法将指针转换为整数、无法将整数转换为指针。 //取出数组首元素地址,将其转换为char*类型指针,这时候+1与数字+1是一样的效果。 int *ptr2 = (int *)((char*)&a+1); //%x表示以十六进制打印。 //ptr[-1],在运行时被解析为*(ptr1+(-1)),也就是*(ptr1-1)。因为已经是int*指针,所以往回走4个字节,是数组中最后一个元素。输出4。 //ptr2指向的地址是数组首元素的地址+1个字节。 //a数组在内存中存储为(小端存储:低位低地址):01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 //此时prt2指向的就是01后面的那个00,因为ptr2被强制转换为int*类型,所以解引用时访问4个字节。也就是:00 00 00 02 //取出时倒着往回取,所以就是0x02000000,因为内存中存储的就是十六进制,又以十六进制形式打印,所以打印出来就是2000000 printf("%x,%x",ptr1[-1],*ptr2);//4 2000000 return 0; }
-
二维数组,数组名[0]表示二维数组首行的第一个元素
#include <stdio.h> int main() { //这里的(0,1)是逗号表达式。如果想要两个数都存储到数组中,需要使用{}。 //逗号表达式的结果是其中的最后一个表达式的结果,所以实际存储进数组中的数是:{1,3,5} int a[3][2] = {(0,1),(2,3),(4,5)}; int * p; //a是数组名,其中存储的是数组首行的地址。a[0]就是首行的第一个元素。 p = a[0]; //p[0] == *(p+0),*p解引用,找到a[0],也就是二维数组第一行的第一个元素:1。 printf("%d",p[0]);//1 return 0; }
-
指针-指针
#include <stdio.h> int main() { int a[5][5]; //p是int(*)[4]类型变量,是一个数组指针,指向一个4元素的int类型数组 int(*p)[4]; //a是二维数组数组名,其中存储的数组首行,也就是指向一个5元素的int类型数组。 //将其地址存储到p指针变量中。 p = a; //p[4][2]可以解析为:*(*(p+4)+2),p是int(*)[4]类型,所以p+4就是往后移动16个int大小,解引用后再往后移动两个int。 //也就是往后移动18个int,原来p是指向a的首元素的,此时p指向a[3][3]的首地址。 //指针-指针,得到的是这两个指针之间的元素个数:4。相隔的元素:a[3][3]、a[3][4]、a[4][0]、a[4][1] //因为&p[4][2]处于低地址处,而&a[4][2]处于高地址处。所以以%d打印是-4 //-4的原码:10000000 00000000 00000000 00000100 //-4的原码:11111111 11111111 11111111 11111011 原码符号位不变,其余取反。 //-4的补码:11111111 11111111 11111111 11111100 反码+1得到补码,在内存中-4存储为补码。 //%x是以十六进制打印:FFFFFFFC //%p,是以地址形式打印,64位平台下,地址占8个字节。也就是16位十六进制:fffffffffffffffc printf("%d %x %p\n",&p[4][2]-&a[4][2],&p[4][2]-&a[4][2],&p[4][2]-&a[4][2]); return 0; }
-
二维数组指针运算
#include <stdio.h> int main() { int aa[2][5] = {1,2,3,4,5,6,7,8,9,10}; //&aa是取出二维数组的地址,&aa+1,是整个二维数组后面紧跟着的那个地址。然后强制转换为int*类型,存储到ptr1中 int *ptr1 = (int*)(&aa+1); //aa是二维数组数组名,表示二维数组首行地址。aa+1,表示二维数组第二行的地址,相当于第二行数组名。 // *数组名 得到的是其首元素地址,*(aa+1)解引用找到的是第二行首元素地址。其本身就是int*指针,强制类型转换是个幌子,然后存储到ptr2变量中 int *ptr2 = (int *)(*(aa+1)); //ptr1中存储的是数组后的地址。因为ptr是int*类型指针,所以ptr-1得到的就是数组最后一个元素的地址。解引用就是这个元素的值:10 //ptr2中存储的是第二行首元素地址,因为ptr2是int*类型指针,所以ptr2-1得到的就是首行的最后一个元素。解引用得到值:5 printf("%d %d",*(ptr1-1),*(ptr2-1));//10 5 return 0; }
-
二级指针与整数运算
#include <stdio.h> int main() { //a是char*类型指针数组 char* a[] = {"work","at","alibaba"}; //a中存储的是数组首元素的地址。将其地址存储到二级指针中 char** pa = a; //pa+1,pa中存储的是数组首元素的地址,首元素地址+1,得到的是数组中第二个元素的地址。 pa++; //pa中存储的数组中第二个元素的地址,*pa找到数组第二个元素,然后进行打印,直到碰到字符串结束符才结束。 printf("%s\n",*pa); return 0; }
-
二级指针,三级指针
#include <stdio.h> int main() { char *c[] ={"ENTER","NEW","POINT","FIRST"}; //cp是二级指针数组,其中存储指向一级指针数组c中元素的二级指针 char** cp[] = { c+3,c+2,c+1,c}; //cpp中存储的是cp数组中首元素的地址 char*** cpp = cp; //++cpp,得到的是cp数组中第二个元素的地址。*++cpp找到cp数组中第二个元素 //**++cpp再解引用,也就是*(c+2),c是指针数组c的首元素地址,c+2是c数组中第3个元素的地址 //所以*(c+2)得到的是:POINT printf("%s\n",**++cpp);//POINT //经过运算,cpp指向的是cp数组中第二个元素 //++以及--比*的优先级高,*的优先级又比+高。 //*++cpp找到cp数组中第三个元素c+1,--*++cpp,cp数组第三个元素-1,也就是(c+1)-1=c。 //也就是cp[2],不再指向c+1这个指针,而是指向c这个指针。 //*--*++cpp再解引用,也就是*c,c是指针数组c的首元素地址,*c就找到数组首元素指向的”ENTER“中E的地址 //+3,也就是指向了"ENTER"中第二个"E",从这里开始取,取到字符串结尾,所以输出:ER printf("%s\n",*--*++cpp+3);//ER //经过++以及--运算。 //cpp此时指向的是cp数组中下标为2的元素地址。而cp数组中下标为2的元素本来指向(c+1),现在指向了c //cpp[-2],也就是*(cpp-2),cpp本来指向cp数组中下标为2的元素,此时指向cp数组下标为0的元素。 //cpp[-2]找到c+3,*cpp[-2]解引用,也就是**(cpp-2),找到c+3指向的c数组中第四个元素。 //*cpp[-2]+3,本来c数组第四个元素中保存的地址,指向的是F,经过+3后,指向了S。从S开始取,取到字符串结尾,所以输出:ST printf("%s\n",*cpp[-2]+3);//ST //cpp[-1][-1]+1,也就是*(*(cpp-1)-1)+1,*(cpp-1)找到cp数组第二个元素c+2;简化为*((c+2)-1)+1 //*(c+2-1),也就是*(c+1),找到数组c中下标为1的元素。 //也就是*(c+1)+1,*(c+1)找到c数组下标为1的元素后,它指向的是N。+1后指向E,从E开始取,取到字符串结尾,所以输出:EW printf("%s\n",cpp[-1][-1]+1);//EW return 0; }
-
定义一个函数指针
/* * 定义一个函数指针,指向的函数有两个int形参,并且返回一个函数指针。返回的指针指向一个返回类型为int、参数为一个int类型的函数 * - 函数返回类型 (*函数指针变量名)(函数参数[,函数参数......]) = 函数名/&函数名 * 返回的函数指针类型,int (*)(int) * 这个函数指针应该是:int (*)(int) (*F)(int,int) * - 语法规定:函数的返回类型如果是一个指针,那么*必须与函数名连接在一起。 * 所以就成为:int (*(*F)(int,int))(int) * * - 验证:int (*(*F)(int,int))(int) * 首先*F表示这是一个指针,*F(int,int)表示这是一个函数指针,指向的函数有两个int参数 * 剩下的int(*)(int),是该函数指针的返回类型。返回一个函数指针,指向一个返回类型为int、参数为一个int类型的函数。 */ /* * 定义一个参数为int*,返回类型为int的函数指针 * - int (*F)(int*) */
-
杨氏矩阵
/* * 杨氏矩阵 * 有一个数字矩阵,每行从左到右递增,从上到下递增。(不一定非要是有序的递增,只要是递增的就可以) * 编写程序在这样的矩阵中查找某个数字是否存在。要求:时间复杂度小于o(N) * 什么是o(N)? * - 比如在一个数组中查找一个数字,这个数组中有N个元素。如果我们要查找第N个元素,最坏的情况下就是要把整个数组遍历一遍才能找到 * 也就是要查找N次、2N次、3N次,这个时候时间复杂度就是o(N) * - 如果要查找的次数是N的N次方、N的N-1次方,此时时间复杂度就是o(N^2); * - 如果遍历的次数与元素的个数没有关系,不管多少个元素都是遍历固定的次数,那么时间复杂度就是o(1) * * 例如: * 1 2 3 4 * 2 3 4 5 * 4 5 6 7 * * 又例如: * 1 2 3 * 4 5 6 * 7 8 9 * * - 如果要找一个数a,我们把a与第一行的最后一个比较,如果a比这个数大,就把a与第二行的最后一个比较,如果a还大,就一直跟下一行的最后一个比较 * 直到遇到比a大的数,就与这一行倒数第二个比较、倒数第三个、倒数第四个....直到找到或比较完。 * - 也可以将a与最后一行的第一个数字比,如果a比这个数组小,就与倒数第二行第一个比。直到遇到比a大的。 * 然后把a与这一行第二个比、第三个比...直到找到与a相等的,或比较完了还是没有,就是没有这个数。 */ #include <stdio.h> void find_num(int arr[3][3],int a,int b,int num) { int x = 0; int y = b-1;//把num与每一行的最后一列上的数进行比较 //只要行数x<行数,就说明还没到最后一行;并且y≥0,说明还没到第一列。就可以继续寻找 while(x<a && y>= 0) { //如果num比这一行最后一个数都大, 说明比这一行所有的数都大。 if(arr[x][y] < num) { x++; //x+1,与下一行比较 } else if(arr[x][y] > num) {//bum比这一行最后一个数大,说明num在这个数的左边。 y--;//与倒数第二个、第三个...比较 } else { //运行到这里,说明找到了,此时这个数与arr[x][y]相等 //输出下标 printf("这个数字所在下标是:%d %d\n",x,y); //找到之后结束方法 return; } } //如果循环结束都没有找到,说明矩阵中没有这个数,返回0。 printf("没有这个数\n"); } int main() { int arr[3][3] = {1,2,3,4,5,6,7,8,9}; //查找一个数字,比如说找7 int k = 7; find_num(arr,3,3,k); // //遍历数组查找——时间复杂度=o(N),不能使用这种方法 // int i,j; // for(i=0 ; i<3 ; i++) // { // for(j=0 ; j<3 ; j++) // { // if(arr[i][j] == 7){} // } // } return 0; }
找到之后,如何在main中就可以打印出找到的数字的下标?
#include <stdio.h> int find_num(int arr[3][3],int* px,int* py,int num) { int x = 0; int y = *py-1;//把num与每一行的最后一列上的数进行比较 //只要行数x<行数,就说明还没到最后一行;并且y≥0,说明还没到第一列。就可以继续寻找 while(x<*px && y>= 0) { //如果num比这一行最后一个数都大, 说明比这一行所有的数都大。 if(arr[x][y] < num) { x++; //x+1,与下一行比较 } else if(arr[x][y] > num) {//bum比这一行最后一个数大,说明num在这个数的左边。 y--;//与倒数第二个、第三个...比较 } else { //找到后将x与y,将其值保存到指针指向的变量,并且返回1 *px = x; *py = y; return 1; } } return 0; } int main() { int arr[3][3] = {1,2,3,4,5,6,7,8,9}; //查找一个数字,比如说找7 int k = 7; //如何获取到坐标呢? //传入指针进去,这样操作的就是同一数了 int x = 3; int y = 3; int ret = find_num(arr,&x,&y,k); if(ret == 1) { printf("找到了\n下标是:%d,%d",x,y); } else { printf("没这个数\n"); } return 0; }
-
声明一个指向函数指针数组的指针
/* * 声明一个指向10元素数组的指针,元素是函数指针,指向的函数返回类型是int,参数是int*。 * 指向10元素数组的指针: (*p)[10] * 数组元素类型 int (*)(int *) * 指向函数指针数组的指针:int (*(*p)[10])(int *) */
-
字符串左旋
/* * 实现一个函数,可以左旋字符串中的k个字符 * 例如:ABCD,左旋一个字符,得到BCDA * 例如:ABCD,左旋两个字符,得到CDAB */ #include <stdio.h> #include <string.h> void left_rotate(char* str,int k) { int i,j; //求字符串长度,也就是传入的数组中有多少个字符 int n = strlen(str); //要左旋几个字符,就循环几次 for(i=0 ; i<k ; i++) { //每次左旋一个字符 //左旋思路:将首个字符取出来,放到中间变量中。其余都放到该元素的前一个坐标上。 char tmp = *str; //将首字符放入中间变量 //因为首字符被我们取了出来,也就是说如果有n个字符,我们就放n-1次 for(j=0 ; j<n-1 ; j++) { //将后一个字符放入当前的str中。然后循环放下一个 *(str+j) = *(str+j+1); } //将首字符放入最后一个位置。最后一个字符的位置:str+n-1 *(str+n-1) = tmp; } } int main() { char arr[10] = "ABCDEF"; int k = 2; left_rotate(arr,k); printf("%s",arr); return 0; }
自己想出来的(改变了首元素指针指向,不推荐使用)
#include <stdio.h> #include <string.h> void left_rotate(char* str,int k) { int i,j; //求字符串长度,也就是传入的数组中有多少个字符 int n = strlen(str); //要左旋几个字符,就循环几次。 for(i=0 ; i<k ; i++) { //左旋思路:将首个字符取出来,放到中间变量中。其余都放到该元素的前一个坐标上。 char tmp = *str; //将首字符放入中间变量 //因为首字符被我们取了出来,也就是说如果有n个字符,我们就放n-1次。每次放完之后,就把地址+1。然后循环放下一个。 for(j=0 ; j<n-1 ;str++,j++) { //将后一个字符放入当前的str中。 *str = *(str+1); // 注意不能把++省略到前面, //*str++ = *(str+1); //该表达式的运算顺序:*str++,因为是后置++,所以先运算后++。 //先*str解引用,然后得到的是首字符的地址。然后str++,此时str指向第二个字符的地址 //然后赋值运算计算=右边的表达式赋值给左边,*(str+1),str+1指向第三个字符的地址。 //然后解引用,找到第三个字符,将其放入了首字符的地址。 //这时,与我们期望的结果,就出现了差错。 //该循环运行一次,ABCDEF ,变成了:CDEF\0\0 //所以要把str++单独写出来。 } //将首字符放入最后一个位置 *str = tmp; //因为改变了*str的指向,现在str是指向f的位置,也就是字符串最后一个元素的位置。 //我们在每次循环之后将其复原为原来指向的下标为0的元素,也就是str=str-n+1 str=str-n+1; } } int main() { char arr[10] = "ABCDEF"; int k = 2; left_rotate(arr,k); printf("%s",arr); return 0; }
-
字符串左旋的另一种方法
/* * 实现一个函数,可以左旋字符串中的k个字符 * 例如:ABCD,左旋一个字符,得到BCDA * 例如:ABCD,左旋两个字符,得到CDAB * * 比如是左旋两个字节: * ABCDEF,应该变为:CDEFAB * - 第一步:逆序左边两个字符,变为:B A C D E F * - 第二步:逆序剩下的四个字符,变为:B A F E D C * - 第三步:逆序整个字符串,就得到:C D E F A B */ #include <stdio.h> #include <assert.h> #include <string.h> reverse(char* left,char* right) { //断言:left和right不为空指针。如果他们不是空,则为非0;如果为NULL,则是0,assert(0)就会不会往下运行,并给出错误信息。 assert(left); assert(right); while(left<right) { char tmp = *left; *left = *right; *right = tmp; left++; right--; } } void left_rotate(char* str,int k) { //字符串长度。 int n = strlen(str); //如果要左旋的字符数要比字符串长度更长 while(k>n) { k -= n;//如果k大于字符串长度,就减去n,直到k<n时进行逆序。 } //逆序左边 //起始地址:首字符地址,结束地址:首字符地址+要左旋的字符数-1 reverse(str,str+k-1); //起始地址:剩下的字符首地址,结束地址:最后一个字符地址 reverse(str+k,str+n-1); //起始地址:首字符地址,结束地址:最后一个字符地址 reverse(str,str+n-1); } int main() { char arr[10] = "ABCDEF"; int k = 1; left_rotate(arr,k); printf("%s",arr); return 0; }
-
判断某个字符串是否由另一个字符串旋转而来
/* * 写一个函数,判断一个字符串是否是另一个字符串旋转之后的字符串。例如: * - 给定s1=AABCD和s2=BCDAA ,就返回1 * - 给定s1=ABCD 和s2=ACBD ,返回0 * * AABCD左旋一个字符串得到ABCDA * AABCD左旋两个字符串得到BCDAA * AABCD右旋一个字符得到DAABC */ #include <stdio.h> #include <string.h> int is_rotate(char* str1,char* str2) { int i,j; //求字符串长度,也就是传入的数组中有多少个字符 int n = strlen(str1); //有多少个字符,就左旋多少次,每旋转一次判断一下是否相等。 for(i=0 ; i<n ; i++) { //每次左旋一个字符 //左旋思路:将首个字符取出来,放到中间变量中。其余都放到该元素的前一个坐标上。 char tmp = *str1; //将首字符放入中间变量 //因为首字符被我们取了出来,也就是说如果有n个字符,我们就放n-1次 for(j=0 ; j<n-1 ; j++) { //将后一个字符放入当前的str中。然后循环放下一个 *(str1+j) = *(str1+j+1); } //将首字符放入最后一个位置。最后一个字符的位置:str+n-1 *(str1+n-1) = tmp; //一旦有一次是相等的,就说明是通过该字符串旋转而来。 if(strcmp(str1,str2) == 0) { return 1; } } //都不想等,就返回0 return 0; } int main() { char arr1[] = "AABCD"; char arr2[] = "BCDAA"; int ret = is_rotate(arr1,arr2); if(ret == 1) { printf("是旋转而来"); } else { printf("不是旋转而来"); } return 0; }
另一种方法
/* * 写一个函数,判断一个字符串是否是另一个字符串旋转之后的字符串。例如: * - 给定s1=AABCD和s2=BCDAA ,就返回1 * - 给定s1=ABCD 和s2=ACBD ,返回0 * * AABCD左旋一个字符串得到ABCDA * AABCD左旋两个字符串得到BCDAA * AABCD右旋一个字符得到DAABC */ /* * 字符串拼接函数strcat() * 函数原型:char * strcat(char* dest,const char* src); * 作用:把src指向的字符串,追加到dest所指向的字符串的结尾。dest字符串中第一个字符串结束符被src第一个字符覆盖。将整个源字符串,包括结束符\0,都拼接到dest字符串后。如果拼接的源字符串没有结束符\0,则程序会down掉。 * 参数:dest:指向目标数组,该数组中包含了一个C字符串,并且足够容纳追加后的字符串。src:要追加的字符串,该字符串不会覆盖目标字符串。 * 返回值:返回dest字符串指针 * * 指定长度字符串拼接函数strncat() * 函数原型:char * strncat(char* dest,const char* src,size_t num); * 作用:将src的前num个字符附加到dest结尾。dest字符串中第一个的字符串结束符被src第一个字符覆盖。并且在拼接后的字符串末尾加上字符串结束符 * 如果src中字符串的长度小于num,则仅复制直到终止空字符串的内容。 * 参数: * - dest:指向目标数组,应该包含一个C字符串,且足够容纳追加后的字符串,包括额外的空字符 * - src:要追加的字符串 * - num:要附加的最大字节数。size_t无符号整型。 * 返回值:返回一个只想最终目标字符串dest的指针。 * * 定位子串函数strstr() * 函数原型:const char* strstr(const char* str1,const char* str2); * 作用:在str1中查找第一次出现str2的指针,如果str2不是str1的一部分,则返回空指针。匹配过程中不包括字符串结束符'\0',但它会停在那里。 * 参数:- str1:要扫描的长字符串。 -str2:短字符串,在str1中查找是否出现的字符串 * 返回值:该函数返回str2第一次出现在str1中的指针。如果str2不在str2中,也就是未在str1中找到str2,就返回NULL。 */ /* * 刚刚我们是通过穷举法判断的,每一个我们都列举了出来: * - ABCDA * - BCDAA * - CDAAB * - DAABC * - AABCD * - 如果此时有这么一个字符串:AABCDAABCD,我们发现,在这个字符串中可以找到所有由AABCD旋转了的字符串 * 所以,我们只需要拿另一个字符串,跟这个字符串作比较就可以。 */ #include <stdio.h> #include <string.h> int is_rotate(char* str1,char* str2) { //如果两个字符串长度不相等,则直接返回0 if(strlen(str1) != strlen(str2)) { return 0; } //1、str1字符串的后面追加一个str1 //AABCD变为AABCDAABCD strcat(str1,str1); //判断str2是否是str1中的字符串。如果是,则返回首次出现的指针,如果不是,则返回NULL。所以我们直接返回 return strstr(str1,str2); } int main() { //将arr1开辟的空间变大一点 char arr1[20] = "AABCD"; char arr2[] = "CDAAB"; int ret = is_rotate(arr1,arr2); if(ret == 0) { printf("不是旋转而来"); } else { printf("是旋转而来"); } return 0; }
-
test.c中包括如下语句
//文件定义的四个变量,哪个不是指针类型? 变量b不是 //定义宏,使用的时候,INT_PTR 会被int*替换掉 #define INT_PTR int* //typedef是将int*类型,重命名为int_ptr了 typedef int* int_ptr; //INT_PTR 会被int*替换掉 就成为:int * a,b; //注意这里的*表示a是指针变量,所以*给了a,而b没有*,所以a是int*指针类型,而b是int类型 //所以定义指针变量的时候,不能使用连续声明。如果要连续声明,则需要int *a,*b;这样a和b就都是int*指针类型了。 INT_PTR a,b; //typedef是将int*类型,重命名为int_ptr了 //所以这里c与d都是int_ptr类型,也就是int*指针类型。 int_ptr c,d;