zl程序教程

您现在的位置是:首页 >  IT要闻

当前栏目

C语言:指针

2023-02-18 16:40:14 时间

这是很基础的教程,我只是写给自己看,作为一个学习笔记记录一下,如果正在阅读的你觉得简单,请不要批评,可以关掉选择离开

如何学好一门编程语言

  • 掌握基础知识,为将来进一步学习打下良好的基础。
  • 上机实践,通过大量的例题学习怎么设计算法,培养解题思路。
  • 养成良好的编码习惯,注释一定要写,要不然保你一周后自己写的代码都不认识了。

每一个变量都有一个内存地址,而内存地址可由  & 变量名 方式访问

#include <stdio.h>

int main ()
{
    int var = 10;    // 声明定义初始化
    int *p;              // 声明定义指针变量
    p = &var;    // 访问var_runoob的地址,赋值给指针变量p

    printf("var 变量的地址: %p\n", p);    // 000000000061FE14
    return 0;
}

指针定义

  指针 就是 内存地址指针变量 就是是用来 存储内存地址 的 变量

同样的,我们使用指针存储变量地址之前,需要对指针进行声明。(C语言的特色,凡事需先声明)

注意:​​指针的类型 必须与 变量的类型 一致,整型指针只能指向整型变量的地址

指针声明

指针类型 *指针变量名;
int    *ip;    /* 声明整型 指针变量ip */

指针赋值

int var = 20;
int *ptr;        // 声明 指针变量
ptr = &var;     // 把ptr 指向var的地址

&是地址运算符,ptr和&var的区别在于,ptr是变量,而&var是常量。

可以通过间接运算符 *,找到指针指向的值。

printf("%d", *ptr);    // 20

和所有变量一样,指针变量也有自己的地址和值。指针变量的值 是它指向的地址,指针变量的地址可以通过 &运算符得到。

int urn=1;
int *ptr=&urn;
printf("%d, %p \n",urn,&urn);   // 1, 0x7fffe5a1e87c
printf("%p, %p \n", ptr, &ptr);  // 0x7fffe5a1e87c, 0x7fffe5a1e880

指针加1,指针的值递增它所指向类型的大小(以字节为单位)。

#include <stdio.h>
#define SIZE 2
int main(void)
{
    short dates[SIZE]={1,2};  // short 是2字节
    short * p;
    short index;
    p = dates;    // 把数组地址赋给指针
    for (index = 0; index < SIZE; index++)
        printf("指针 + %d: %10p, %d\n", index, p + index, *(p + index));   // 指针加1,地址+2, 但是取的值是下一个
        // 指针 + 0: 0x7fffe206c274, 1
        // 指针 + 1: 0x7fffe206c276, 2
    return 0;
}

C 中的 NULL 指针

  在指针变量声明的时候,如果没有确切的地址可以赋值,赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针

  NULL 指针是一个定义在标准库中的值为0的常量。请看下面的程序:

#include<stdio.h>

int main() {

    int* ptr = NULL;    //声明定义初始化一个指针变量

    printf("ptr指向的地址是:%p\n", ptr);    //00000000

    return 0;
}

指针变量赋0值和不赋值是不同的,指针变量未赋值时,不能使用;而赋0值,可以使用,只是不指向具体的变量。

指向指针的指针

  指向指针的指针 int **var;  第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

案例

#include <stdio.h>

int main()
{
    int  V=100;     // 声明定义初始化一个变量V
    int* Pt1;   // 声明一个指针变量Pt1
    int** Pt2;  // 声明一个指向指针的变量Pt2

    Pt1 = &V;   // 将 变量V 的地址赋值给 指针变量Pt1
    Pt2 = &Pt1; //将 指针变量Pt1 的地址赋值给 指针变量Pt2

    printf("var = %d\n", V);    // 100
    printf("Pt1 = %p\n", Pt1);  // 00B9FD10 (指向变量V的地址)
    printf("*Pt1 = %d\n", *Pt1);    // 100 (指向地址的变量值)
    printf("Pt2 = %p\n", Pt2);      // 00B9FD04 (指向指针的地址)
    printf("**Pt2 = %d\n", **Pt2);  // 100 (指向地址的变量值 的 地址变量值)

    return 0;
}

指针运算

  指针其实是一个用数值表示的地址,指针可以进行4种算术运算: ++--+- ,以 ptr++ 为例, ptr 每增加一次,他就会指向下一个整数位置,即当前位置往后移4字节

递增一个指针

#include<stdio.h>

const int MAX = 3;        // 定义一个常量

int main() {
    int var[] = { 10,100,200 };    //声明并初始化一个数组
    int i, * ptr;        // 声明一个变量i,和一个指针变量ptr

    // ptr指向的是一个地址
    ptr = var;    // 将数组名赋值给一个指针变量(数组名就是第一个元素的地址)
    for (i = 0; i < MAX; i++) {

        printf("存储地址:var[%d] = %p\n", i, ptr);
        printf("存储值:var[%d] = %d\n", i, *ptr);

        ptr++;    /* 指向下一个位置 */
    }

    return 0;
}
存储地址:var[0] = 0096FE24
存储值:var[0] = 10
存储地址:var[1] = 0096FE28
存储值:var[1] = 100
存储地址:var[2] = 0096FE2C
存储值:var[2] = 200
运行结果

递减一个指针

#include<stdio.h>

const int MAX = 3;        // 定义一个常量

int main() {

    int var[] = {10,100,200};    //初始化一个数组
    int i, * ptr;

    ptr = &var[MAX - 1];    //数组的最后一个地址赋值给指针变量ptr
    for (i = MAX; i > 0; i--) {
        printf("存储地址:var[%d] = %p\n", i - 1, ptr);
        printf("存储值:var[%d] = %d\n", i - 1, *ptr);

        ptr--;    /* 指向下一个位置 */
    }
    return 0;
}
存储地址:var[2] = 0118FACC
存储值:var[2] = 200
存储地址:var[1] = 0118FAC8
存储值:var[1] = 100
存储地址:var[0] = 0118FAC4
存储值:var[0] = 10
结果

指针的比较

只要变量指针 所指向的地址小于或等于数组的最后一个元素的地址 &var[MAX - 1],则把变量指针进行递增:

#include <stdio.h>

const int MAX = 3;

int main()
{
    int  var[] = { 10, 100, 200 };  //声明并初始化一个数组
    int  i, * ptr;  //声明一个变量和一个指针变量

    
    ptr = var;  // 指针中第一个元素的地址
    i = 0;
    while (ptr <= &var[MAX - 1])
    {

        printf("存储地址:var[%d] = %p\n", i, ptr);
        printf("存储值:var[%d] = %d\n", i, *ptr);

        /* 指向上一个位置 */
        ptr++;
        i++;
    }
    return 0;
}
//存储地址:var[0] = 00B7F864
//存储值:var[0] = 10
//存储地址:var[1] = 00B7F868
//存储值:var[1] = 100
//存储地址:var[2] = 00B7F86C
//存储值:var[2] = 200
结果

指针与数组

指针指向数组

  数组比较特别,因为 数组名是一个指向数组第一个元素的指针(即数组名代表第一个元素的地址),例:

double balance[50];

balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。因此,下面的程序片段把  balance 的第一个元素的地址 赋值给 p :

double *p;    // 声明一个指针
double balance[10];    // 声明一个数组

p = balance;    // 将数组中第一个元素的地址 赋值 给 指针变量p

因此我们也可以通过 *(balance + 4) 访问 balance[4] ,这种方式反而更加常用。下面的就是指针取变量的案例:

#include <stdio.h>

int main(){
    /* 初始化 带有 5 个元素的整型数组 */
    double balance[5] = { 1000.0, 2.0, 3.4, 17.0, 50.0 };
    double* p;  //声明指针p
    int i;

    p = balance;    //将balance数组的第一个地址赋值给p

    /* 输出数组中每个元素的值 */
    printf("使用指针的数组值\n");
    for (i = 0; i < 5; i++){
        printf("*(p + %d) : %f\n", i, *(p + i));
    }

    printf("使用 balance 作为地址的数组值\n");
    for (i = 0; i < 5; i++){
        printf("*(balance + %d) : %f\n", i, *(balance + i));
    }

    return 0;
}
View Code
使用指针的数组值
*(p + 0) : 1000.000000
*(p + 1) : 2.000000
*(p + 2) : 3.400000
*(p + 3) : 17.000000
*(p + 4) : 50.000000
使用 balance 作为地址的数组值
*(balance + 0) : 1000.000000
*(balance + 1) : 2.000000
*(balance + 2) : 3.400000
*(balance + 3) : 17.000000
*(balance + 4) : 50.000000
结果

另外字符串是特殊的字符数组(结尾的最后一个字符是"\0"),因为指针是指向地址的,字符数组的一个字符占一个地址,所以当指针变量 指向 字符串变量地址时,其实指针变量指向的是字符串第一个字符的地址。除此之外数组名也代表第一个数组变量的地址。 

指针数组

定义:包含 指针变量 的 数组

类型说明符 (*指针变量名)[长度]
// 类型 *指针变量名[长度] // 也可以不用括号 int (* pax)[2]; // pz指向一个内含两个int类型值的数组

[]的优先级高于*,先与pax结合,所以pax成为一个内含两个元素的数组。然后*表示pax数组内含两个指针。

案例:声明一个由 MAX 个 整形指针 组成的数组 ptr

int main(void) {
    int zippo[4][2] = {{2, 4},
                       {6, 8},
                       {1, 3},
                       {5, 7}};
    int(*pz)[2];  //声明指针数组,可以存放2个指针
    pz = zippo;

    printf("   pz = %p,    pz + 1 = %p\n", pz, pz + 1);
    // zippo = 0x7fffd15143c0,    zippo + 1 = 0x7fffd15143c8
    printf("pz[0] = %p, pz[0] + 1 = %p\n", pz[0], pz[0] + 1);
    // zippo[0] = 0x7fffd15143c0, zippo[0] + 1 = 0x7fffd15143c4
    printf("  *pz = %p,   *pz + 1 = %p\n", *pz, *pz + 1);
    // *zippo = 0x7fffd15143c0,   *zippo + 1 = 0x7fffd15143c4
    printf("pz[0][0] = %d\n", pz[0][0]);    // zippo[0][0] = 2
    printf("  *pz[0] = %d\n", *pz[0]);      // *zippo[0] = 2
    printf("    **pz = %d\n", **pz);        // **zippo = 2
    printf("      pz[2][1] = %d\n", pz[2][1]);  // zippo[2][1] = 3
    printf("*(*(pz+2) + 1) = %d\n", *(*(pz + 2) + 1));  // *(*(zippo+2) + 1) = 3

    return 0;
}

 

指针数组并不一定是存放地址,也可以存放字符

#include <stdio.h>

int main() {
    // const char*表示的是字符串,string
    const char *name[]={"A","B","C"};   // 字符串数组

    for (int i = 0; i < 3; i++) {
        printf("names[%d]=%s\n",i,name[i]);
    }
    /*names[0]=A
    names[1]=B
    names[2]=C*/

    return 0;
}
View Code

指针与函数

指针传入函数

  指针传入函数,其实就是地址传入函数,这时 形参需要声明为指针类型 

#include <stdio.h>
#include <time.h>

// 函数使用前需先声明
void getSeconds(unsigned long* par);

int main()
{
    unsigned long sec;    // 声明局部变量sec
    getSeconds(&sec);    //把sec的地址 传给 函数getSeconds

    printf("秒速: %ld\n", sec);    // 1626178397

    return 0;
}

void getSeconds(unsigned long* par) {
    // 用指针变量*par 接 sec的地址 ==> par = &sec;

    /* 获取当前的秒数 */
    *par = time(NULL);    // *par 指向地址的变量值
}

保护指针数据

  当给函数传参时,要选择是 传递值 还是 传递指针。通常都是直接传递数值,只有程序需要在函数中改变该数值时,才会传递指针。对于数组别无选择,必须传递指针,因为这样做效率高。但是传递地址 在函数中 容易更改原始值。如果不想该原始值被更改 可以对形参使用const

int sum(const int ar[], int n);

int sum(const int ar[], int n) /* 函数定义 */
{
    int i;
    int total = 0;

    for( i = 0; i < n; i++)
        total += ar[i];
    return total;
}

const告诉编译器,该函数不能修改ar指向的数组中的内容,使用const并不是要求原数组是常量,而是该函数在处理数组时将其视为常量,不可更改。

补充知识:把const数据或非const数据的地址初始化为指向const的指针或为其赋值是合法的

double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double locked[4] = {0.08, 0.075, 0.0725, 0.07};
const double * pc = rates;    // 有效
pc = locked;                  //有效

函数返回指针

  这个时候我们需要 声明函数返回类型为指针 ,函数返回指针就是返回地址。

int * myFunction()
    {...}

补充知识:C语言不支持调用函数时返回局部变量的地址,除非定义局部变量为 static 变量

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int* getRandom() {
/*
getRandom是一个返回指针的函数
功能:返回随机数的函数
*/
    // C 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
    static int  r[10];
    int i;

    srand((unsigned)time(NULL)); // 设置随机数种子
    for (i = 0; i < 10; ++i) {
        r[i] = rand();
        printf("r[%d] = %d\n", i, r[i]);
    }
    // 数组名是指向第一个元素的地址,
    return r;
}

int main() {
    int* p; // 声明一个指向整数的指针
    int i;  // 声明变量

    p = getRandom();    // 调用函数,返回的是一个地址,用指针变量去接
    for (i = 0; i < 10; i++) {
        printf("*(p + %d) : %d\n", i, *(p + i));
    }

    return 0;
}
3885
3046
10367
24274
2611
1269
9776
13671
30908
16058
*(p + [0]) : 3885
*(p + [1]) : 3046
*(p + [2]) : 10367
*(p + [3]) : 24274
*(p + [4]) : 2611
*(p + [5]) : 1269
*(p + [6]) : 9776
*(p + [7]) : 13671
*(p + [8]) : 30908
*(p + [9]) : 16058
结果

指向指针的指针传入函数

  还有一个特殊情况就是 指向指针的指针传入函数,哎,这个我还没弄懂

#include <stdlib.h>

void getSeconds(char **buffer) {
    int BUF_SIZE = 1024;
    *buffer = malloc(BUF_SIZE * sizeof(char));

}

int main() {
    char *buffer;

    getSeconds(&buffer);    //把sec的地址 传给 函数getSeconds

    return 0;
}

函数指针

  定义一个指针变量A,指向函数B的函数名(函数名本身就是地址)指针变量A称为函数指针。下次我们想使用函数B的时候,就可以用函数指针A进行调用。

int (*函数指针变量A)(int, int) = 函数名B; // 声明一个函数指针
//或者
int (*函数指针变量A)(int, int);   // 声明一个函数指针A
A= 函数名B;        // 将函数名赋值给A

讲再多理论不如看案例,上代码:声明一个函数指针变量p,指向函数max

#include <stdio.h>

int max(int x, int y){
    return x > y ? x : y;
}

int main(void){
    /* p 是函数指针 */
    int (*p)(int, int) = &max; // &可以省略,因为函数名本身就是一个地址
    int a, b, c, d;     // 声明变量

    printf("请输入三个数字:");
    scanf_s("%d %d %d", &a, &b, &c);

    /* 与直接调用函数等价,d = max(max(a, b), c) */
    d = p(p(a, b), c);

    printf("最大的数字是: %d\n", d);

    return 0;
}
请输入三个数字:1 2 3
最大的数字是: 3
结果

函数调用函数

当一个函数A调用 函数B 时,函数A就为回调函数,函数A的实参需要传入函数名形参需要为 函数指针

#include <stdlib.h>  
#include <stdio.h>

// 回调函数
void populate_array(int* array, size_t arraySize, int (*getNextValue)(void)){
/*
    参数1:实参为数组名(地址),形参需要为 指针
    参数3:实参为函数名(地址),形参需要为 函数指针
*/
    for (size_t i = 0; i < arraySize; i++) {
        array[i] = getNextValue();
    }
}

// 获取随机值
int getNextRandomValue(void) {
    return rand();
}

int main(void) {
    int myarray[10];    // 数组声明
    /* 数组名和函数名都是地址*/
    populate_array(myarray, 10, getNextRandomValue);    // 调用回调函数
    for (int i = 0; i < 10; i++) {
        printf("%d ", myarray[i]);
    }
    printf("\n");
    return 0;
}
41 18467 6334 26500 19169 15724 11478 29358 26962 24464
结果

 

参考

菜鸟教程