C语言---指针

C语言---指针,第1张

四要素

内存

指针的初始化与赋值

用对应类型变量的地址

用相同类型的指针

直接用地址

用数组名

字符串

置空

多级指针

指针的加减法

指针偏移

指针的自增自减

通过指针遍历数组

大小端

指针数组

数组指针

typedef

指针函数

函数指针

const

常量指针

指针常量

常量指针常量

内存区划分

代码区

常量区

全局区(静态全局区)

栈区

堆区

void * 指针

动态申请内存

申请和释放

内存泄漏和野指针

动态数组

类似一维数组

类似二维数组

数组扩容

字符串相关函数


  1. 为了方便访问内存中的内容,给每一个内存单元一个编号,我们称这个编号为地址,也就是指针.

  2. 指针也是一种数据类型 所以指针有自己的内存 存储的是地址(编号)

四要素
  1. 指针本身的类型 除去指针名,剩下的就是指针本身的类型

  2. 指针指向的类型 除去指针名和一个*,剩下的就是指针指向的类型

  3. 指针本身的内存 用来存储一个编号(4字节)

  4. 指针指向的内存 可以是各种类型

int num = 0; // 类型:int 
int arr[10] = {}; // 类型:int [10] 
// 去掉名字,剩下的就是类型 

定义

*: 定义时,表示定义的是一个指针 其他的时候表示解析引用(取内容)

&: 取首地址符

#include  
int main() 
{ 
    /* 定义变量 */ 
    // 类型 变量名; 
    int num = 0; 
    /* 指针也是一种数据类型 */ 
    // 类型 变量名; 
    // 指针本身的类型 指针名; 
    // 指针指向的类型* 指针名; 
    // ==> 除去指针名,剩下的就是指针本身的类型 
    // ==> 除去指针名和一个*,剩下的就是指针指向的类型 
    // 定义了一个int*类型的指针 取名叫做p 
    // p这个指针,本身的类型:int* 
    // p这个指针,指向的类型:int
    int* p; 
    // 注意: 现在的指针p 没有指向 
    // 所谓的类型,意思是他可以指向 
    // 定义了一个int******类型的指针 取名叫做p1 
    // p这个指针,本身的类型:int****** 
    // p这个指针,指向的类型:int***** 
    int****** p1; 
    return 0; 
}
内存

所有的指针,不论类型,在内存中都占4个字节的内存

#include  
int main() 
{ 
    char* pch; 
    short* psh; 
    int* pn; 
    float* pf; 
    double* pd; 
    printf("%d\n", sizeof(pch)); // 4 
    printf("%d\n", sizeof(psh)); // 4 
    printf("%d\n", sizeof(pn)); // 4 
    printf("%d\n", sizeof(pf)); // 4 
    printf("%d\n", sizeof(pd)); // 4 
    return 0; 
}

指针的初始化与赋值 用对应类型变量的地址
#include  
int main() 
{ 
    int num = 10; 
    int* pn = # // 初始化 
    float f = 3.14f; 
    float* pf; 
    pf = &f; // 赋值 
    return 0; 
}
用相同类型的指针
#include  
int main() 
{ 
    int num = 10; 
    int* pn = # // 初始化 
    int* p1 = pn; // 初始化
    int* p2; 
    p2 = pn; // 赋值 
    return 0; 
}

直接用地址
#include  
int main() 
{ 
    int* p = (int*)0X36; 
    return 0; 
}
用数组名

一级指针可以接受一维数组的数组名

#include  
int main() 
{ 
    // 数组名就是数组的首地址 
    int arr[5] = { 1, 2, 3, 4, 5 }; 
    int* p = arr; 
    return 0; 
}
字符串
#include  
int main() 
{ 
    // 数组名就是数组的首地址 
    char arr[5] = {'a','b','c' }; 
    char* p = arr; 
    char str[5] = "abcd"; 
    p = str; 
    printf("%s\n", str); 
    printf("%s\n", p); 
    // ==> char*类型的指针可以直接用来打印整个字符串到'\0'停止 
    p = "asdf"; 
    return 0; 
} 
置空
int* p = NULL; 
int* p1 = (int*)0X0; 
/* 
NULL: #define NULL 0 
0地址 
有时候,指针定义好了,但是暂时没有指向 
或者是,指针用完了,没有指向了 
指针不知道指向哪里 
此时的指针,很危险(野指针) 
所以 这些情况下的指针 
统一安排一个地址给他们指向 
指向0地址 
*/
多级指针
#include  
int main() 
{ 
    int num = 10; 
    printf(" num = %d\n", num); 
    printf("&num = %X\n", &num); 
    int* p = # 
    printf("*p = %d\n", *p);
    printf(" p = %X\n", p); 
    printf("&p = %X\n", &p); 
    int** pp = &p; // 一个二级指针 
    printf("**pp = %d\n", **pp); 
    printf(" *pp = %X\n", *pp); 
    printf(" pp = %X\n", pp); 
    printf(" &pp = %X\n", &pp); 
    int*** ppp = &pp; // 一个三级指针 
    printf("***ppp = %d\n", ***ppp); 
    printf(" **ppp = %X\n", **ppp); 
    printf(" *ppp = %X\n", * ppp); 
    printf(" ppp = %X\n", ppp); 
    printf(" &ppp = %X\n", & ppp); 
    return 0; 
}
指针的加减法

指针本身的值(指向) 没有变化

指针偏移
  1. 指针可以加上或者减去一个整数

  2. 指针加上或者减去一个整数后,实际上是进行了偏移

  3. 偏移的范围是加上或减去的整数个单位

单位: 指针指向的类型在内存中所占的字节数

偏移: 指针指向不变,但是可以根据偏移量取内容

#include  
int main() 
{ 
    int num = 10; 
    int* p = # 
    printf("%X\n", p); 
    printf("%X\n", p+1); 
    return 0; 
}
指针的自增自减

自增自减,会改变指针指向

++: 表示指针向后移动一个单位(--: 向前)

单位: 指针指向的类型在内存中所占的字节数

#include  
int main() 
{ 
    int num = 10; 
    num + 10; 
    int* p = # 
    printf("%X\n", p); 
    printf("%X\n", p+1); 
    p + 1; 
    p++; // p = p + 1; 
    return 0; 
}
通过指针遍历数组

遍历一维数组

#include  
int main() 
{ 
    int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 
    for (size_t i = 0; i < 10; i++) 
    { 
        printf("%2d", arr[i]); 
    }
    printf("\n"); 
    int* p = arr; 
    // p和arr,除了arr是一个常量之外,其他几乎是一样的 
    for (size_t i = 0; i < 10; i++) 
    { 
        printf("%2d", p[i]); 
    }
    printf("\n"); 
    printf("%d\n", p[0]); 
    printf("%d\n", *(p + 0)); 
    printf("%d\n", p[1]); 
    printf("%d\n", *(p + 1)); 
    // p[n] <==> *(p+n) 
    return 0; 
}
// p[n]:叫做下标形式
// *(p+n):叫做指针偏移的形式 

遍历二维数组

  1. 二维数组也是数组

  2. 二维数组可以看成元素是一维数组的一维数组

  3. 数组的内存是连续的

#include  
int main() 
{ 
    int arr[3][4] = { 
        {1,2,3,4}, 
        {5,6,7,8}, 
        {9,10,11,12} 
    };
    for (size_t i = 0; i < 3; i++) 
    { 
        for (size_t j = 0; j < 4; j++) 
        { 
            printf("%3d", arr[i][j]); 
        }
        printf("\n"); 
    }
    
// 1 
    int* p0 = arr[0]; 
    int* p1 = arr[1]; 
    int* p2 = arr[2]; 
    printf("\n"); 
    for (size_t i = 0; i < 4; i++) 
    { 
        printf("%3d", p0[i]); 
    }
    printf("\n"); 
    for (int i = -4; i <= 7; i++) 
    { 
        printf("%3d", p1[i]); 
    }
    printf("\n"); 
    for (int i = 0; i < 12; i++) 
    { 
        printf("%3d", arr[0][i]); 
    }
    printf("\n"); 
    // 下标: 保证数组不越界即可 
    
    // 2 
    int* p = &arr[0][0]; 
    for (int i = 0; i < 12; i++) 
    { 
        printf("%3d", arr[0][i]); 
    }
    printf("\n"); 
    for (int i = 0; i < 12; i++) 
    { 
        printf("%3d", *p); 
        p++; 
    }
    printf("\n"); 
    return 0; 
}

 

大小端

如何通过代码确定当前使用的机器是大端还是小端?(使用指针)

小端存储(VS):

存储:从低字节(低地址)存储数据的低位

读取:从高向低读取

大端存储:

存储:从高字节(高地址)存储数据的高位

读取:从低向高读取

指针数组

是数组: 数组元素的类型是指针

  1. 关于数组元素访问

  2. 关于指针取内容

  3. 组合到一起使用

#include  
int main() 
{ 
    /* 指针数组 */ 
    // 1 定义几个int类型的变量 
    int a, b, c; 
    a = 1; 
    b = 2; 
    c = 3; 
    // 2 定义几个int*类型的指针 
    int *pa, *pb, *pc; 
    pa = &a; 
    pb = &b; 
    pc = &c; 
    // 3 定义一个数组 数组元素是int*类型的指针 长度为3 
    // 元素类型 数组名[长度] = {}; 
    int* arr[3] = { pa, pb, pc }; 
    // 访问数组元素(通过数组名arr修改b的值) 
    // arr[0] <==> pa *pa <==> a 
    // arr[1] <==> pb *pb  b 
    // arr[2] 
pc *pc

c printf("b = %d\n", b); b = 20; printf("b = %d\n", b); *pb = 30; printf("b = %d\n", b); *arr[1] = 40; printf("b = %d\n", b); return 0; }

 
数组指针 
是指针: 指向数组的指针 
<====><====>#include <====> 
int main() 
{ 
    /* 数组指针 */ 
    // 1 定义一个数组 
    int arr[5] = { 1, 2, 3, 4, 5 }; 
    // 2 定义一个指针指向数组arr 
    // 指针指向的类型* 指针名 ; 
    // int [5] ===> 怎么加*? ==>() 
    // (int [5])* *(int [5]) 
    // int [5]* int* [5] 
    // int (*p) [5] 
    int(*p)[5] = &arr; 
    // 定义了一个指针p 
    // p指向的类型:int [5] (是一个数组) 
    // p本身的类型:int(*)[5] 
    // 通过数组指针p 访问数组元素arr[2] 
    // p <==> &arr 
    // *p  arr 
    // (*p)[2] 
arr[2] printf("(*p)[2] = %d\n", (*p)[2]); // 结论: p[n]

*(p+n) printf("*(*p+2) = %d\n", *(*p + 2)); printf("p[0][2] = %d\n", p[0][2]); printf("*(p[0] + 2) = %d\n", *(p[0] + 2)); return 0; }

  1. (一维数组)数组名:

  2. 数组的名字

  3. 整个数组的首地址

数组第0个元素的首地址

注意

数组指针的定义(类型的推导)
 
数组元素类型 (*) 数组长度 
typedef 
#include

/* typedef : 给类型取别名 */ typedef int I; // 给数组类型取别名 typedef int ArrType[5]; // 给数组指针类型取别名 typedef int(*PArrType)[5]; ​ int main() { int a = 10; printf("%d\n", a); I num = 999; printf("%d\n", num); int arr[5] = { 1, 2, 3, 4, 5 }; int arrr[6]; int(*p)[5] = &arr; PArrType p0 = &arr; ArrType arr1 = { 1, 2, 3, 4, 5 }; ArrType* p1 = &arr1; /* 注意: 数组元素类型不同,或者长度不同,都属于不同类型的数组 int arr[5] ; int arrr[6]; */ return 0; }

 
指针函数 
是函数: 返回值类型是指针 
#include

int* function(); int main() { int* p = function(); printf("%d\n", *p); printf("%d\n", *p); return 0; } ​ int* function() { int num = 0; int* pn = # return pn; }

注意:

使用指针函数,不能返回指向栈区的指针
 
函数指针 
是指针: 指向函数的指针 
#include

void fun() { printf("fun is been used!\n"); } ​ int function(char a,int n) { printf("fun1 is been used!\n"); return n; } ​ int main() { // 返回值类型(*函数指针名)(形参类型) int(*pfunction)(char,int) = function; char ch = 0; int num = 0; pfunction(ch ,num); fun(); // 指针指向的类型* 指针名; // void () ==> void (*) () void(*pfun1) () = &fun; // pfun1 &fun ==> *pfun1 fun void(*pfun2) () = fun; // pfun2 fun (*pfun1)(); pfun1(); (*pfun2)(); pfun2(); return 0; }

const

声明一个常量
 
关键字const用来告诉编译器一个一旦被初始化过的变量就不能再修改

int a; // 碗 const int n; // 一个又大又圆的碗

常量指针

指针指向一个常量
 
修饰指针指向 
#include

int main() { // 常量指针 const int num = 10; // num = 100; int* p1 = # *p1 = 100; printf("%d\n", num); // 可以通过p1修改num // 不可以通过p2,p3修改num const int* p2 = # //*p2 = 100; printf("%d\n", num); int const* p3 = # //*p3 = 100; printf("%d\n", num); return 0; } // 不能通过指针修改指向的内容(必须初始化变量) // 可以改变指针指向

指针常量

指针本身是一个常量
 
修饰指针本身

// 指针常量: int a = 0; int* const pa = &a; // pa = NULL; *pa = 100; // 可以通过指针修改指向的内容 // 不能改变指针指向(必须初始化指针)

常量指针常量

指针本身是一个常量,指向一个常量
 
同时修饰

// 常量指针常量 const int b = 0; const int * const pb = &b; // pb = NULL; // *pb = 0; // 不能通过指针修改指向的内容(必须初始化变量) // 不能改变指针指向(必须初始化指针)

内存区划分 代码区

存储代码
    常量区
  1. 存常量:数值常量 字符常量 字符串常量 符号常量 地址常量....

    全局区(静态全局区)

  2. 静态变量(static) static: 静态的

全局变量
 
#include

int b = 0; // 普通全局变量 static int d = 0; // 静态全局变量 int main() { int a = 0; // 普通局部变量 static int c = 0; // 静态局部变量 return 0; }

  1. 作用、区别

  2. 作用域: 起作用的区域

生存周期(生命周期): 起作用的时间
 
#include

// 生存周期: 程序开始到程序结束 int b = 0; // 普通全局变量 // 作用域: 项目作用域 extern static int d = 0; // 静态全局变量 // 作用域: 文件作用域 int main() { // 作用域: 当前语块 int a = 0; // 普通局部变量 // 生存周期: 当前语块 static int c = 0; // 静态局部变量 // 生存周期: 程序开始到程序结束 return 0; } ​ void test() { /*a = 10; b = 10; c = 10; d = 10;*/ /* a,c: 不认识 b,d: 认识 ==> 局部变量和全局变量的作用域的区别 */ }

静态全局区: 从程序开始系统自动分配内存,到程序结束系统自动回收内存

栈区

存储 普通局部变量

栈区: 从定义开始系统自动分配内存,出了函数系统自动回收临时内存

堆区

由程序员手动申请,手动释放
 
void * 指针 
也是指针: 空类型的指针 
#include
    int main() { void* p = NULL; // p + 1; // p++; // ==> p不能进行+-也不能++-- int* pn = NULL; // pn = p; p = pn; short* psh = NULL; // psh = p; p = psh; return 0; }
  1. 不能偏移

  2. 不能自增自减

  3. 可以接受其他类型的指针给值

  4. 不能直接给其他类型的指针值(可以强转)

不能直接取内容

==> void*类型的指针不知道自己的长度 
动态申请内存 
申请和释放 
#include 
#include

int main() { /* 申请 */ /* 参数: (要在堆区手动申请的)字节数 void * malloc( size_t _Size); malloc(); 参数: 类型长度 个数 void * calloc( size_t _Count, size_t _Size); calloc(); */ /* 释放 */ /* 参数: 地址 void free(void * _Memory); free(); */ return 0; }

注意:申请的内存,必须释放。释放之后指针必须置空

内存泄漏和野指针

内存泄漏: 申请的内存没有进行释放

野指针: 指针指向了不该指向的地方
 
举例 
#include 
#include
 
int main() 
{ 
    // 1.1 使用malloc申请一个int类型大小的内存(4字节) 
    
    // 1 申请 
    int* p = (int*)malloc(sizeof(int)); // (int*)malloc(4); 
    
    // 2 使用 
    *p = 100; 
    
    // 3 释放 
    free(p); 
    
    // 4 置空. 
    p = NULL; 
    
    // 1.2 使用malloc申请十个int类型大小的内存(4*10=40字节) 
    
    // 1 申请 
    int* p1 = (int*)malloc(sizeof(int)* 10); // (int*)malloc(40); 
    
    // 2 使用 
    p1[0] = 1; 
    p1[2] = 10; 
    
    // 3 释放 
    free(p1); 
    
    // 4 置空 
    p1 = NULL; 
    
    // 2 使用calloc申请一个int类型大小的内存(4字节) 
    
    // 1 申请 
    int* p2 = (int*)calloc(sizeof(int), 1); 
    
    // 2 使用 
    *p2 = 100; 
    
    // 3 释放 
    free(p2); 
    
    // 4 置空 
    p2 = NULL; 
    return 0; 
} 
动态数组 
类似一维数组 
< len; i++) 
        { 
            arr[i] = i; 
        }
    for (size_t i = 0; i < len; i++) 
        { 
            printf("%2d", arr[i]); 
        }
    printf("\n"); 
    // 3 释放 
    free(arr); 
    // 4 置空 
    arr = NULL; 
    return 0; 
}#include 
#include
    int main() { // 使用malloc申请十个int类型大小的内存(4*10=40字节) // 1 申请 int len = 5; int* arr = (int*)calloc(sizeof(int), len); // 2 使用 for (size_t i = 0; i
  1. 类似二维数组

  2. 分段申请

分段释放
 
< 3; i++) 
    { 
    pp[i] = (int*)calloc(sizeof(int), 4); 
    }
    // ==>#include < 3; i++) 
    { 
    for (size_t j = 0; j < 4; j++) 
    { 
    pp[i][j] = i * 10 + j; 
    printf("%3d", pp[i][j]); 
    }
    printf("\n"); 
    }
    printf("\n"); 
    
    // 3 释放 
    for (size_t i = 0; i < 3; i++) 
    { 
    free(pp[i]); 
    pp[i] = NULL;
    }
    free(pp); 
    
    // 4 置空 
    pp = NULL; 
    return 0; 
} 
#include 
int main() { // 申请类似于int arr[3][4]; // 1 申请 // 1.1 申请3个指针 int** pp = (int**)calloc(sizeof(int*), 3); // 1.2 分3次申请4个int大小的内存 for (size_t i = 0; i
 不一定像真正的二维数组一样内存连续 
    
    // 2 使用 
    for (size_t i = 0; i  
数组扩容 
< len) 
        { 
            ptemp[i++] = num; 
        }
        // 2 长度不够,需要扩容 
        else 
        { 
            // 扩容规则 
            len *= 2; 
            p = (int*)calloc(sizeof(int), len); // 重新申请一段内存 
            // 拷贝之前内存中的数据 注意:i 
            for (size_t j = 0; j < i; j++) 
                { 
                    p[j] = ptemp[j]; 
                }
            p[i++] = num; // 添加当前需要添加的数据 
            free(ptemp); 
            ptemp = p; // ptemp重新指向新内存 
        } 
    }
    // 写一套输出 检查一下 注意:i 
    for (size_t j = 0; j < i; j++) 
    {
        printf("%2d", ptemp[j]); 
    }
    printf("\n当前内存长度:%d\n", len); 
    printf("当前数据个数:%d\n", i); 
    free(p); // 释放 
    // free(ptemp); // 不要重复释放 
    p = ptemp = NULL; 
    return 0; 
}#include 
#include
 
int main() 
{ 
    // 1 给定默认长度 
    // 2 可以添加数据 
    // 3 如果装满了,就扩容 
    // 默认初始长度:10 
    int len = 10; 
    // 申请长度为len(10)个int大小的内存 
    int* p = (int*)calloc(sizeof(int), len); 
    int* ptemp = p; 
    int i = 0; // 下标 
    int num = 0; // 用来接收每一次的输入 
    // 添加数据(重复输入) 
    while (scanf("%d",&num),num!=666) 
    { 
        // 1 长度足够 
        if (i 
字符串相关函数 #include // 断言 // assert(条件) 如果条件为真 继续程序 为假 中断 // char *strcpy( char *to, const char *from ); char* Mystrcpy(char* pstr, const char* psrc) { assert(*psrc != ''); char* ptemp = pstr; while ((*ptemp++ = *psrc++) != ''); return pstr; } // 思考: // 1 为什么第二个参数用 const // 防止psrc被修改 // 2 为什么要返回一个指针? // 为了实现链式 *** 作(套娃) // size_t strlen(char *str); ​ size_t Mystrlen(char *str) { size_t len = 0; // 从str处向后移动指针 char* ptemp = str; while (*ptemp++ != '') len++; return len; } // char *strcat( char *str1, const char *str2 ); ​ char* Mystrcat(char *str1, const char *str2) { // 判断str2是否为空 if ('' == *str2) return str1; char* ptemp = str1; // 找到str1(ptemp)中的'' while (*ptemp != '') ptemp++; // 将str2在ptemp处开始拷贝 while ((*ptemp++ = *str2++) != ''); return str1; }

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/789798.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-05
下一篇2022-05-05

发表评论

登录后才能评论

评论列表(0条)

    保存