指针

知识点1【数据类型】

本质作用:合理的利用空间

#include
#include
void test01(){
    //基本类型 char short int long float double
    //char 占1字节空间
    char ch;
    printf("sizeof(ch)=%d\n",sizeof(ch));//1字节 (1B)
    int num;
    printf("sizeof(int)=%d",sizeof(num)); //4字节 (2B)
}
int main(){
    test01();
}

1B == 8位(b 二进制位)

1b只能存放0或1

1B == 1地址

知识点2【指针变量】

  1. 编号(地址):内存中每一个字节分配一个号码
  2. 定义一个变量 存放上面的号码 这样的变量 这样的变量叫做指针变量
  3. 在32位的机器上,地址是32个0或1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就是4个字节
  4. 在64位的机器上,一个指针变量的大小是8个字节
  5. 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。比如:char 的指针解引用就只能访问一个字节,而int* 的指针解引用就能访问四个字节。
#include
#include
void test02(){
	int num = 100;
	//取变量的地址 用&
	//&num 代表标量num的起始地址
	printf("%p\n",&num);
	
	//需求:定义一个指针变量 保存num的地址
	//在定义的时候:*说明是指针变量 而不是普通变量
	int *p = NULL;
	printf("sizeof(p)=%d\n",sizeof(p));
	
	//num的地址 与 p 建立关系
	p = #
	printf("num = %d\n",num);
	//使用中:*p 表示取p保存的地址编号 对应空间的内容
	printf("*p = %d\n",*p); //*p-- 为0  --*p 减一
}
int main(){
	test02();
}
void test03(){
	int num = 10;
	
	//指针变量两种类型:自身的类型 指向的类型
	//自身的类型:在指针变量定义的时候 将变量名拖黑 剩下啥类型 指针变量就是啥类型
	    //int *p = NULL; p自身的类型就是int *
	//指向的类型:在指针变量定义的时候 将变量名和离他最近的一个 * 一起拖黑 剩下啥类型 指针变量指向的类型就是啥类型
	    //int *p = NULL; p指向的类型就是int
	//指针变量指向的类型的作用:决定了指针变量 所取空间内容的宽度 决定了指针变量+1跳过的单位跨度
	int *p = NULL;
	p = #
	
	//指针变量的跨度
	printf("&num=%u\n",&num);  // 6684132
	printf("p=%u\n", p);       // 6684132
	printf("p+1=%u\n",p+1);    // 6684136  指向类型是int 
	
	//
	printf("*p = %d\n",*p); //num的值
	
}
void test04(){
    //指针类型决定了指针进行解引用操作的时候,能够访问空间的大小
    //int *p; *p 能够访问4个字节
    //char *p; *p 能够访问1个字节
    //double *p; *p 能够访问8个字节
	int num = 0x01020304;
	int *p1 = #  
	printf("*p1 = %#x\n",*p1); //0x1020304
	
	short *p2 = #  // short *p2 = (short *)#  有些编译器要强制转化
	printf("*p2 = %#x\n", *p2); //0x304
	printf("*p2 = %#x\n", *(p2+1)); //0x201
	
	char *p3 = # x //char *p3 = (char *)#
	printf("*p3 = %#x\n", *p3); //0x4
    
    *p1 = 0;
    printf("num = %d\n",num); //0
}
int main(){
	test04();
}

指针_第1张图片

知识点3【野指针】

野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)

野指针成因

1. 指针未初始化

#inclued 
int main(){
    int *p; //局部变量指针未初始化,默认为随机值
    *p = 20;
    return 0;
}

2. 指针越界访问

#include 
int main(){
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i;i<=11;i++){
        //当前指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
    }
    return 0;
}

3. 指针指向的空间释放

例:

int* test06(){
	int a = 60;
	return &a;
}	
int main(){
	int *p = test06();	//  a 是一个局部变量 函数开始调用a产生,调用结束就a的地址也就还给系统了
	*p = 20; 
	return 0;
}

如何规避野指针

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向空间释放即设置NULL
  4. 指针使用之前检查有效性

知识点4【指针运算 】

  • 指针+-整数
  • 指针 - 指针
  • 指针的关系运算

指针+-整数

int main(){
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);
    int *p = arr;
    for(i;i

指针 - 指针

得到的是中间元素的个数

int main(){
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("%d\n",&arr[9]-&arr[0]); // 9
}
int my_strlen(char* str){
    char *start = str;
    char *end = str;
    while(*end != '\0'){
        end++;
    }
    return end - strart;
}
int main(){
    //strlen - 求字符串长度
    //递归 - 模拟实现了strlen - 计数器的方式1,递归的方式2 
    //
    char arr[] = "bit";
    int len = my_strlen(arr);
    printf("%d\n",len);
    return 0;
}

知识点5【指针数组】

指针数组 - 数组,用来存放指针

int main(){
    int *parr[4]; //存放整形指针的数组 - 指针数组
    char *parr2[4]; //一级字符指针的数组
    char **parr3[5]; //二级字符指针的数组

    int a=10;
    int b=20;
    int c=30;
    int d=40;
    int * arr[4]={&a,&b,&c,&d};

    int i =0;
    for(i=0;i<4;i++){
        printf("%d ",*(arr[i])); //10 20 30 40
    }
}
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[] = {arr1,arr2,arr3};
}

数组指针 - 指针 指向数组的指针 存放数组的地址

int arr[10] =0;
//arr - 首元素地址
//&arr[0] - 首元素地址
//&arr - 数组的地址
int (*p)[10] = &arr;


char* arr[5];
char* (*p)[5] = &arr;

指针_第2张图片

int main(){
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;
    int i =0;
    for(i=0;i<10;i++){
        printf("%d ", (*p)[i]); //1 2 3 4 5 6 7 8 9 0
    }

    for(i=0;i<10;i++){
        // 1 2 3 4 5 6 7 8 9 0
        printf("%d ", *(*p+i)); //*p == arr 
    }
}
//参数是指针的形式
void printf2(int (*p)[5],int x,int y){
    int i =0;
    for(i=0;i
int main(){
    int i=0,arr[10]={1,2,3,4,5,6,7,8,9,0}, *p=arr;
    for(i=0;i<10;i++){
        printf("%d ",arr[i]); // arr[i] == *(arr+i) == *(p+i) == p[i] 
    }
}
int arr[5];      // arr是一个5个元素的整形数组
int *parr1[10];  // parr1是一个数组,数组有10个元素,每个元素的类型是int *,parr1是指针数组
int (*parr2)[10];// parr2是一个指针,该指针指向了一个数组,每个元素的类型是int
int (*arr3[10])[5]; // parr3是一个数组,该数组有10个元素,每个元素的类型是数组指针,该数组指针指向的数组有5个元素,每个元素是int
void test05(){
	int arr[10] = {0};
	int *p = arr; //数组名-首元素的地址
	// 数组每个元素的值变为1
	int i= 0;
	for(i=0;i<10;i++){
		*(p+i) = 1;
	}
}
int main(){
    int arr[10] = {0};
    printf("%p\n",arr);// 地址-首元素地址 00EFF8E0
    printf("%p\n",arr+1); //00EFF8E4
    
    printf("%p\n",&arr[0]);// 地址-首元素地址  00EFF8E0
    printf("%p\n",&arr[0]+1); //00EFF8E4
    
    printf("%p\n",&arr); //地址-整个数组地址 从 00EFF8E0 开始
    printf("%p\n",&arr+1); // 00EFF904
    // 2个例外
    //1. &arr - &数组名 - 数组名不是首元素地址 - 数组名表示整个数组 - &数组名 取出的是整个数组的地址
    //2. sizeof(arr) - sizeof(数组名) - 数组名表示整个数组 - sizeof(数组名)计算的是整个数组的大小
}
int main(){
    int arr[10] = { 0 };
    int *p = arr;
    int i =0;
    for(i=0;i<10;i++){
        *(p+i) = i;
    }
    for(i=0;i<10;i++){
        printf("%d ", *(p+i));
    }
}
int main(){
    int a = 10;
    int b =20;
    int c = 30;
    
    int *arr[3] = {&a, &b,&c}; //指针数组
    int i = 0;
    for(i;i<3;i++){
        printf("%d ", *(arr[i]));
    }
}

知识点6【二级指针】

int main(){
    int a = 10;
    int *pa = &a;
    int **ppa = &pa; //ppa就是二级指针
    printf("%d\n",**ppa); //10
}

知识点7 【字符指针】

int main(){
    char arr[] = "abcdef";
    char* p = arr;
    printf("%s\n",arr); //abcdef
    printf("%s\n",p); //abcdef
}
int main(){
    char ch = 'w';
    char *p = &ch;
    char *p2="abcdef"; //指针p2存放的是a的首地址,这是一个常量字符串不允许被修改
}

知识点8 【传参】

一维数组传参

指针_第3张图片

二维数组创参

void test1(int a[3][5]){}
void test2(int a[][5]){}
void test3(int (*a)[5]){}
int main(){
    int a[3][5]={0};
    test1(a);
    test2(a);
    test3(a);
}

一级指针传参

void test(int *p){}
void test2(char *p){}
int main(){
    int a = 10;
    int *p = &a;
    test1(p); //ok
    test1(&a); //ok

    int ch = 'w';
    int *pc = &ch;
    test2(pc); //ok
    test2(&ch); //ok
}

二级指针传参

void test(int **p){}
int main(){
    int *ptr;
    int **pp = &ptr;
    test(&ptr);
    test(pp);
    int *arr[10]; //指针数组
    test(a);
}

知识点9 【函数指针】

函数指针 - 指针 - 存放函数地址的指针

int add(int x,int y){
    int z = 0;
    z = x+y;
    return z;
}
void Print(char *str){
    printf("%s\n",str);
}
int main(){
    int a = 10;
    int b = 20;
    int arr[10] = {0};

    //&函数名 和 函数名 都是函数的地址
    printf("%p\n",&add);
    printf("%p\n",add);

    //函数指针
    int (*pa)(int,int) = add;
    printf("%d\n", (*pa)(2,3)); //5

    void (*pstr)(char *) = Print;
    (*)("hello bit");  //hello bit
    return 0;
}
//把0强制类型转换成:void(*)() 函数指针类型 -0 就是一个函数的地址
//调用0地址处的该函数
(*(void (*)())0)(); 

//signal是一个函数声明
//signal函数的参数有2个,第一个是int,第二个是函数指针,该函数指向的函数的参数是int,返回类型是void
//signal函数的返回类型也是一个函数指针:该函数指针指向的函数的参数是int,返回类型是void
void(* signal(int,void(*)(int))(int);
//简化
typedef void(*pfun_t)(int);
pfun_t signal(int,pfun_t);

int add(int x,int y){
    int z = 0;
    z = x+y;
    return z;
}
void Print(char *str){
    printf("%s\n",str);
}
int main(){
    
    int (*pa)(int,int) = add;
    printf("%d\n", (*pa)(2,3)); //5
    printf("%d\n", (pa)(2,3));  //5
    printf("%d\n", add(2,3));   //5
    return 0;
}

知识点10 【函数指针数组】

用途:转移表

int Add(int x,int y){
    int z = 0;
    z = x+y;
    return z;
}

int Sub(int x,int y){
    int z = 0;
    z = x-y;
    return z;
}
int Mul(int x,int y){
    int z = 0;
    z = x*y;
    return z;
}

int Div(int x,int y){
    int z = 0;
    z = x/y;
    return z;
}
int main(){
    //指针数组
    int *arr[5]; 
    //指针函数
    int (*pa)(int,int) = Add;
    //需要一个数组,这个数组可以存放4个函数的地址 - 函数指针的数组 
    int (*parr[4])(int,int) = {Add , Sub , Mul , Div};	

    int i =0;
    for(i=0;i<4;i++){
        printf("%d\n",parr[i](2,3)); //5 -1 6 0
    }
    return 0;
}

知识点11【指向函数指针数组的指针】

int Add(int x,int y){
    int z = 0;
    z = x+y;
    return z;
}

int main(){
    int arr[10] = {0};
    int (*p)[10] = &arr;

    //pfarr是一个数组,函数指针的数组
    int (*pfArr[4])(int,int) = {Add};
    //ppfArr是一个数组指针,指针指向的数组有四个元素
    //指向的数组的每个元素的类型都是函数指针 int (*)(int,int)
    int (*(*ppfArr)[4])(int,int) = &pfArr;
}

知识点12【回调函数】

回调函数就是一个通过函数指针调用的函数。

void printf(char *str){
    printf("hehe:%s",str);
}

void test(void (*p)(char*)){
    printf("test\n");
    p("bit");
}

int main(){
    test(printf);
    //test
    //hehe:bit
    return 0;
}

案例qsort函数


int main(){

    int a = 10;
    void * p =&a;

    //void* 类型的指针 可以接收任意类型的地址
    //void* 类型的指针 不能进行解引用操作 eg: *p = 0 是错误的
    //void* 类型的指针 不能进行+=整数的操作

    //典型案例 qsort函数
}
void test(){
    int arr[] = {1,3,6,8,0,9,7,5,4,2};
    int sz = sizeof(arr)/sizeof(arr[0]);
    qsort(arr,sz,sizeof(arr[0]),cmp_int);
    //第一个参数:待排序数组的首元素地址
    //第二个参数:待排序数组的元素个数
    //第三个参数:待排序数组的每个元素的大小-单位是字节
    //第四个参数:是函数指针,比较两个元素的所用函数的地址
    //             函数指针的两个参数是:待比较的两个元素的地址
}

int cmp_int(const void* e1,const void* e2){
    return *(int*)e1 - *(int*)e2; // (int*)强制类型转换
}

int cmp_float(const void* e1,const void* e2 ){
    if(*(float*)e1 == *(float*)e2){
        return 0;
    }
    else if(*(float*)e1 > *(float*)e2){
        return 1;
    }
    else return -1;
}
//按照结构体中的元素排序
typedef struct Stu{
    char name[20];
    int age;
}

int cmp_Stu_age(const void* e1,const void* e2){
    return ((Stu*)e1)->age - ((Stu*)e2)->age;
}

int cmp_Stu_name(const void* e1,const void* e2){
    return strcmp(((Stu*)e1)->name , ((Stu*)e2)->name); //strcmp 字符串比较函数 需要引用 stdlib.h
}

void test(){
    Stu arr[] = {{"zhangsan",27},{"wangwu",18},{"lisi",19}};
    int sz = sizeof(arr)/sizeof(arr[0]);
    //qsort(arr,sz,sizeof(arr[0]),cmp_Stu_age);
    qsort(arr,sz,sizeof(arr[0]),cmp_Stu_name);
}

自定义实现冒泡

int cmp_int(const void* e1,const void* e2){
    return *(int*)e1 - *(int*)e2; // (int*)强制类型转换
}

void Swap(char* buf1,char* buf2,int width){
    int i =0;
    for(i=0;i0){
                //交换
                Swap((char*)base+j*width,(char*)base+(j+1)*width);
            }
        }
    }
}

int main(){
    int arr[] = {1,3,6,8,0,9,7,5,4,2};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
}

指针判断

sizeof

int main(){
    //数组名是首元素的地址
    //1. sizeof(数组名) - 数组名表示整个数组
    //2. &数组名 - 数组名表示整个数组
    //
    //一维数组
    int a[] = { 1,2,3,4}; //4*4 = 16
    printf("%d\n",sizeof(a)); //sizeof(数组名) - 计算的是数组的总大小 - 单位是字节-16
    printf("%d\n",sizeof(a+0));//4/8 -数组名这里表示的是首元素的值,a+0是首元素地址,地址的大小是 4/8个字节 
    printf("%d\n",sizeof(*a)); //4 - 数组名这里表示首元素地址,*a是首元素
    printf("%d\n",sizeof(a+1));//4/8 -数组名这里表示的是首元素的值,a+1是第二个元素地址,地址的大小是 4/8个字节
    printf("%d\n",sizeof(a[1]));//4 -第二个元素的地址
    printf("%d\n",sizeof(&a));  //4/8 -  &a是取出数组的地址,但是数组的地址也是地址,地址大小就是 4/8个字节
    printf("%d\n",sizeof(*&a)); //16 - &a是数组的地址,数组的地址解引用访问的数组,sizeof计算的就是数组的大小单位是字节
    printf("%d\n",sizeof(&a+1));//4/8 - &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是第二个元素的地址

    //字符数组
    char arr[] = {'a','b','c','d','e','f'};
    printf("%d\n",sizeof(arr));//sizeof计算的数组的大小 6*1 = 6 字节
    printf("%d\n",sizeof(arr+0));//4/8 arr是首元素的地址,arr+0还是首元素的地址 地址的大小是4/8字节
    printf("%d\n",sizeof(*arr)); //1   arr是首元素的地址,*arr就是首元素,首元素是字符大小是一个字节
    printf("%d\n",sizeof(arr[1]));//1
    printf("%d\n",sizeof(&arr));  //4/8 &arr虽然是数组的地址,但还是地址,地址大小是4/8个字节
    printf("%d\n",sizeof(&arr + 1));//4/8 &arr+1 是跳过整个数组后的地址,地址大小是4/8个字节      
    printf("%d\n",sizeof(&arr[0] + 1));//4/8 第二个元素的地址
}

strlen

int main(){
    char arr[] = {'a','b','c','d','e','f'};

    printf("%d\n",strlen(arr)); //随机值
    printf("%d\n",strlen(arr+0))//随机值
    //printf("%d\n",strlen(*arr)) //err
    //printf("%d\n",strlen(arr[1])) //err
    printf("%d\n",strlen(&arr)) //随机值
    printf("%d\n",strlen(&arr + 1)) //随机值-6
    printf("%d\n",strlen(&arr[0] + 1)) //随机值-1
}

你可能感兴趣的:(C++,c语言,c++)