C语言:深入了解指针1

内存和地址

1. 酒店房间类比内存和地址

场景描述

把计算机的内存想象成一家酒店,每个房间就是一个内存单元,每个房间都有一个唯一的房间号,这个房间号就相当于内存地址。房间里可以存放客人的行李等物品,这些物品就好比存储在内存中的数据。

对应到 C 语言代码
#include 

int main() 
{
    int num = 10;
    int *ptr = #

    printf("变量 num 存储的内容(相当于房间里的物品): %d\n", num);
    printf("变量 num 的地址(相当于房间号): %p\n", (void *)&num);
    printf("指针 ptr 存储的地址(相当于记录了房间号的纸条): %p\n", (void *)ptr);
    printf("通过指针 ptr 访问存储的内容(相当于根据纸条找到房间并查看里面的物品): %d\n", *ptr);

    return 0;
}
解释
  • 房间(内存单元):房间里放着一个行李箱,行李箱上写着 “10”,代表变量 num 的值。
  • 房间号(内存地址)&num 就是获取 num 变量所在的内存地址,就如同获取酒店房间的房间号(例如 621)。
  • 记录房间号的纸条(指针):指针 ptr 存储了 num 的地址,类似于一张记录了房间号的纸条。我们可以通过这张纸条(指针)找到对应的房间(内存单元)并查看或修改里面的物品(数据)。

  • 我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存是 8GB/16GB/32GB 等,那这些内存空间如何⾼效的管理呢?
  • 其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。
    计算机中常⻅的单位(补充):⼀个⽐特位可以存储⼀个2进制的位1或者0
    计算机中的单位:
    bit - 比特位       
    Byte -字节       1 Byte = 8  bit
    KB   -千字节      1 KB = 1024 Byte
    MB   - 兆字节     1 MB = 1024 KB
    GB   - 吉字节     1 GB = 1024 MB
    TB   - 太字节     1 TB = 1024 GB
    ....         ..............
    其中,每个内存单元,相当于⼀个酒店房间,⼀个字节空间⾥⾯能放8个⽐特位,就好⽐酒店里面放的行李(规定)一样,每个行李是⼀个⽐特位。每个内存单元也都有⼀个编号(这个编号就相当于酒店房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。⽣活中我们把⻔牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语⾔中给地址起了新的名字叫: 指针
                                        内存单元的编号 == 地址 == 指针

2. 指针变量和地址

     2.1 取地址操作符(&)和解引⽤操作符(*)
int main()
{
	int a = 4;
	&a;// & -- 取地址操作符,拿到变量a的地址 

	printf("%p\n", &a);//%p是专门打印地址

	int* pa = &a;//pa是一个变量,这个变量pa是用来存放地址(指针)的,pa叫指针变量

    *pa = 200; * -- 解引用操作符(间接访问操作符)

   // int* 是pa的类型   ,pa是指针变量的名字

   // *表示pa是指针变量

   //int表示pa指向的变量a的类型是int

	return 0;
} 

3. 指针变量的⼤⼩

//                          32位平台下地址是32个bit位(即4个字节)
//                          64位平台下地址是64个bit位(即8个字节)
//     注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。
int main()
{  //在64位平台下地址是64个bit位(即8个字节)
   printf("%zd\n", sizeof(char*));  //8
   printf("%zd\n", sizeof(int*));   //8
   printf("%zd\n", sizeof(short*)); //8
   printf("%zd\n", sizeof(double*));//8

 return 0;

}

3.1 指针的解引⽤

/* 调试我们可以看到,代码1 会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。

   结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。

   ⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。*/

//代码1:
int main()
{
	int a = 0x12435644;//十六机进制每二位为一个字节12 43 56 44
	int* pc = &a;
	*pc = 0;//每次访问四个字节,所以一次性改了四个字节

	return 0;
}
//代码2:
int main()
{
	int a = 0x12435644;
	char* pc = &a;
	*pc = 0;//每次访问一个字节,所以一次性改了一个字节

	return 0;
}

3.2 指针+-整数

  
我们可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。指针+1,其实跳过1个指针指向的元素。指针可以+1,那也可以-1。
int main()
{
	int a = 20;
	int* pa = &a;
	char* pc = &a;

	printf("  &a = %p\n", &a);       //&a = 00BAFC28
	printf("  pa = %p\n", pa);       //pa = 00BAFC28
	printf("  pc = %p\n", pc);       //pc = 00BAFC28

	printf("&a+1 = %p\n", &a + 1); //&a+1 = 00BAFC2C    跟&a相差4个字节(多)
	printf("pa+1 = %p\n", pa + 1); //pa+1 = 00BAFC2C    跟pa相差4个字节(多)
	printf("pc+1 = %p\n", pc + 1); //pc+1 = 00BAFC29    跟pc相差1个字节(多)

	printf("&a-1 = %p\n", &a-1);   //&a-1 = 00BAFC24    跟&a相差4个字节(少)
	printf("pa-1 = %p\n", pa-1);   //pa-1 = 00BAFC24    跟&a相差4个字节(少)
	printf("pc-1 = %p\n", pc-1);   //pc-1 = 00BAFC27    跟&a相差1个字节(少)

	return 0;
}
                 结论 :指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

                int* pa;             pa+1  -->  +1*sizeof(int)
                                        pa+n  -->  +n*sizeof(int)
 

               char* pa;           pa+1  -->  +1*sizeof(char)
                                        pa+n  -->   +n*sizeof(char)

总结

看了这么多了是不是还是有一点不懂?那我在这里跟你们举个例子:

int a =20;

int* pa = &a;

pa ,*pa ,&pa这三个有什么区别?

 

1. pa

含义

pa 是一个指针变量,它存储的是另一个变量的内存地址。在上述代码中,pa 存储的是变量 a 的内存地址,即 pa 指向变量 a

用法
  • 作为地址传递:可以将 pa 作为参数传递给函数,让函数能够通过这个地址访问或修改所指向的变量。例如:
    #include 
    
    void modifyValue(int *ptr) //传递过来的是地址所以必须得要指针来接
    {
        *ptr = 100;
    }
    
    int main() {
        int a = 20;
        int b = 100
        int *pa = &a;
    
             pa = &b;//pa换了一个地址,这时pa存储的就是b的地址
    
        modifyValue(pa);//pa本身就是地址所以不用’&‘
        printf("a 的值: %d\n", a);  // 输出 100
        return 0;
    }

    2. *pa

  • 含义

    * 是解引用运算符,*pa 表示访问 pa 所指向的内存地址中存储的值。在上述代码中,*pa 访问的就是变量 a 的值。

    用法
  • 读取所指变量的值:可以通过 *pa 获取 pa 所指向变量的值。例如:
    #include 
    
    int main()
     {
        int a = 20;
        int *pa = &a;
        printf("*pa 的值: %d\n", *pa);  // 输出 20
        return 0;
    }
  • 修改所指变量的值:可以对 *pa 进行赋值操作,从而修改 pa 所指向变量的值。例如:
    #include 
    
    int main() {
        int a = 20;
        int *pa = &a;
    
        *pa = 50;
    
        printf("a 的值: %d\n", a);  // 输出 50
        return 0;
    }

    3. &pa

    含义

    & 是取地址运算符,&pa 表示获取指针变量 pa 本身自己的内存地址。也就是说,&pa 是指向指针 pa 的指针。

    用法
  • 二级指针:可以将 &pa 赋值给一个二级指针(指向指针的指针)。例如:
    #include 
    
    int main()
     {
        int a = 20;
    
        int *pa = &a;
    
        int **ppa = &pa;
    
        printf("通过二级指针访问 a 的值: %d\n", **ppa);  // 输出 20
        return 0;
    }

    在上述代码中,ppa 是一个二级指针,它存储的是指针 pa 的地址,通过两次解引用 **ppa 可以访问到变量 a 的值。

     

    综上所述,pa 是指针,存储的是地址;*pa 是解引用操作,用于访问指针所指向的值;&pa 是取指针本身的地址,可用于创建二级指针等操作。 

3.3 void* 指针

void* 指针是一种可以指向任何类型数据的指针。它不指定所指向数据的具体类型,只是表示一个内存地址。例如,可以定义一个  void* 指针如下:
                                                          void* ptr;

 

特点
  • 通用性:可以指向任意类型的数据,包括基本数据类型(如 intfloat 等)和自定义数据类型(如结构体、联合体等)。这使得 void* 指针在一些通用的数据处理函数或数据结构中非常有用,能够实现对不同类型数据的统一操作。
  • 类型无关性:由于 void* 指针不指定具体的数据类型,所以在使用它进行指针运算或访问所指向的数据时,需要进行显式的类型转换。因为编译器无法根据 void* 指针确定所指向数据的大小和类型,所以不能直接通过 void* 指针进行解引用操作来访问数据。
常见用途
  • 函数参数:在函数参数中使用 void* 指针,可以使函数能够接受任意类型的数据指针作为参数,从而实现函数的通用性。例如,C 标准库中的 memcpy 函数就是使用 void* 指针来实现对任意类型数据的内存复制操作,其函数原型为 void* memcpy(void* dest, const void* src, size_t n);,可以将 src 指向的内存区域中的 n 个字节复制到 dest 指向的内存区域中。
  • 数据结构:在一些数据结构的实现中,void* 指针可以用来存储不同类型的数据。例如,链表节点可以使用 void* 指针来存储节点的数据,这样链表就可以存储任意类型的数据。
注意事项
  • 类型转换:在使用 void* 指针访问数据或进行指针运算时,必须进行显式的类型转换,将 void* 指针转换为正确的数据类型指针,否则可能会导致错误的访问或运算结果。
  • 内存管理:由于 void* 指针可以指向任意类型的数据,在进行内存分配和释放时要特别小心,确保正确地分配和释放内存,避免内存泄漏或悬空指针等问题。

下面是一个简单的示例代码,演示了 void* 指针的基本用法:

#include 
#include 

// 函数接受void*指针作为参数,并根据传入的类型进行相应的操作
void printData(void* data, char type) 
{
    if (type == 'i') 
{
        // 将void*指针转换为int*指针,然后解引用输出
        int* intPtr = (int*)data;
        printf("Integer value: %d\n", *intPtr);
    }
    else if (type == 'f') 
{
        // 将void*指针转换为float*指针,然后解引用输出
        float* floatPtr = (float*)data;
        printf("Float value: %f\n", *floatPtr);
    }
}

int main()
 {
    int intValue = 10;
    float floatValue = 3.14f;

    // 使用void*指针指向int类型数据
    void* voidPtr1 = &intValue;
    printData(voidPtr1, 'i');//i:代表 int

    // 使用void*指针指向float类型数据
    void* voidPtr2 = &floatValue;
    printData(voidPtr2, 'f');//f:代表 float

    return 0;
}

在上述代码中,printData 函数接受一个 void* 指针和一个字符类型的参数 type,根据 type 的值将 void* 指针转换为相应类型的指针,并输出数据。在 main 函数中,分别使用 void* 指针指向一个 int 类型数据和一个 float 类型数据,并调用 printData 函数进行输出。

4. const 修饰指针(被修饰的变量,并不是变成常量,而是本质还是变量,只是不能被修改罢了

指向常量的指针(const type* 或 type const*

  • 定义及特点:这种指针指向的内容是常量,不能通过该指针去修改所指向的数据,但指针本身可以指向其他地址。例如:

const int* p;

int const* p; // 与上面的定义等价

  • 示例
    #include 
    
    int main() {
        int num = 10;
        const int* p = #//因为*p被修饰限制了,但p并没有
    
        // *p = 20;  // 错误,不能通过指向常量的指针修改数据
    
        int anotherNum = 30;
    
        p = &anotherNum;  // 正确,指针本身可以指向其他地址
        return 0;
    }

    常量指针(type* const

  • 定义及特点:常量指针本身的值是一个常量,即它所指向的地址不能被改变,但可以通过该指针去修改所指向的数据。例如:

 int* const p;

这里定义了一个 int 类型的常量指针 p,在初始化后,p 就只能指向它初始化时所指向的那个地址,不能再指向其他地址,但可以通过 p 来修改其所指向的 int 类型数据的值。 

#include 

int main() 
{
    int num1 = 10;
    int num2 = 20;
    int* const p = &num1;

    *p = 30;  // 正确,可以通过常量指针修改所指向的数据

    // p = &num2;  // 错误,常量指针不能改变指向
    return 0;
}

指向常量的常量指针(const type* const

  • 定义及特点:这种指针既不能改变指针本身所指向的地址,也不能通过该指针去修改所指向的数据。例如:

const int* const ptr;

  • 示例
    #include 
    
    int main() 
    {
        int num = 10;
        const int* const ptr = #//这里p和p*都被限制
    
        // *p = 20;  // 错误,不能通过指针修改数据
    
        // p = &anotherNum;  // 错误,指针不能改变指向
        return 0;
    }

    5. 指针运算

指针的基本运算有三种,分别是:
  • 指针+- 整数
  • 指针-指针
  • 指针的关系运算

    1. 指针 ± 整数

    运算规则:当一个指针加上或减去一个整数时,实际上是在内存中向前或向后移动了若干个所指向数据类型大小的位置。移动的字节数等于整数乘以指针所指向数据类型的字节数。

    示例代码

    #include 
    
    int main() 
    {
        int arr[5] = {1, 2, 3, 4, 5};
        int *ptr = arr;  // 指针ptr指向数组arr的首元素
    
        // 指针加整数
        int *ptr1 = ptr + 2;  // 指针向后移动2个int类型的位置
        printf("ptr1指向的值: %d\n", *ptr1);  // 输出3
    
        // 指针减整数
        int *ptr2 = ptr1 - 1;  // 指针向前移动1个int类型的位置
        printf("ptr2指向的值: %d\n", *ptr2);  // 输出2
    
        return 0;
    }

    代码解释:在上述代码中,int 类型通常占 4 个字节。ptr + 2 表示指针向后移动 2 * sizeof(int) 个字节,即指向数组中的第 3 个元素;ptr1 - 1 表示指针向前移动 1 * sizeof(int) 个字节,即指向数组中的第 2 个元素。

    2. 指针 - 指针(相加其实没有实际用途,比较少)


    运算规则只有当两个指针指向同一个数组中的元素时,才能进行指针相减运算。指针相减的结果是两个指针之间相隔的元素个数,其类型为 ptrdiff_t(在  头文件中定义)。 示例代码

    #include 
    #include 
    
    int main() 
    {
        int arr[5] = {1, 2, 3, 4, 5};
        int *ptr1 = &arr[0];
        int *ptr2 = &arr[3];
    
        ptrdiff_t diff = ptr2 - ptr1;  // 计算两个指针之间相隔的元素个数
        printf("ptr2和ptr1之间相隔的元素个数: %td\n", diff);  // 输出3
    
        return 0;
    }

    代码解释ptr2 指向数组的第 4 个元素,ptr1 指向数组的第 1 个元素,它们之间相隔 3 个元素,所以 ptr2 - ptr1 的结果为 3。

    3. 指针的关系运算


    运算规则:指针的关系运算包括 <><=>=== 和 != 等。这些运算通常用于比较两个指针是否指向同一个位置,或者判断一个指针是否在另一个指针之前或之后。同样,只有当两个指针指向同一个数组中的元素时,这些关系运算才有意义。

    示例代码

    #include 
    
    int main() 
    {
        int arr[5] = {1, 2, 3, 4, 5};
        int *ptr1 = &arr[0];
        int *ptr2 = &arr[3];
    
        if (ptr1 < ptr2) 
        {
            printf("ptr1在ptr2之前\n");
        } 
       else 
        {
            printf("ptr1不在ptr2之前\n");
        }
    
        if (ptr1 != ptr2) 
        {
            printf("ptr1和ptr2不指向同一个位置\n");
        }
        else 
        {
            printf("ptr1和ptr2指向同一个位置\n");
        }
    
        return 0;
    }

    代码解释:在上述代码中,由于 ptr1 指向数组的第 1 个元素,ptr2 指向数组的第 4 个元素,所以 ptr1 < ptr2 条件成立,ptr1 != ptr2 条件也成立。

    6. 野指针

    定义

    野指针是指指向一个无效的、未分配或者已经释放的内存地址的指针。这种指针没有明确指向一个合法的内存区域,对其进行解引用操作(即访问其所指向的内存内容)会产生不可预期的结果。

    产生原因

    1. 指针未初始化

    当定义一个指针变量但没有对其进行初始化时,该指针会包含一个随机的内存地址,这个地址可能并不指向任何有效的内存区域,从而成为野指针。

    #include 
    
    int main()
     {
        int *ptr;  // 定义指针但未初始化
    
        // *ptr = 10;  // 错误:对野指针进行解引用操作
        return 0;
    }
    2. 指针所指向的内存被释放后未置为 NULL

    当使用 malloc()calloc() 或 realloc() 等函数动态分配内存,并使用指针指向这块内存后,如果后续使用 free() 函数释放了该内存,但没有将指针置为 NULL,那么这个指针就会变成悬空指针也被称为野指针。

    #include 
    #include 
    
    int main() 
    {
        int *ptr = (int *)malloc(sizeof(int));//申请动态内存
    
        if (ptr == NULL)
       {
            return 1;
        }
        *ptr = 10;
    
        free(ptr);  // 释放内存
    
        // ptr 现在是野指针   
    
        // *ptr = 20;  // 错误:对野指针进行解引用操作
    
        return 0;
    }
    3. 指针越界访问

    当指针指向数组元素时,如果对指针进行了超出数组范围的操作,使得指针指向了数组之外的内存区域,那么该指针就可能成为野指针。

    #include 
    
    int main() 
    {
        int arr[5] = {1, 2, 3, 4, 5};
        int *ptr = arr;
    
        ptr = ptr + 10;  // 指针越界,指向了数组之外的内存区域
    
        // *ptr = 10;  // 错误:对野指针进行解引用操作
    
        return 0;
    }
    4.局部变量超出作用域
     

    当一个指针指向一个局部变量时,如果这个局部变量所在的函数执行完毕,局部变量的生命周期结束,其占用的内存会被释放。但此时指针仍然保留着该局部变量的地址,就会成为悬空指针。

    #include 
    
    int* add(int x,int y) 
    {
        int z = x + y;
        return &z;  // 返回局部变量的地址
    }
    
    int main() 
    {
         int a = 3;
         int b = 5;
        int *ptr = add(a,b);
    
        // 此时ptr成为悬空指针,因为z已经超出作用域
    
        // *ptr = 20;  // 错误:对悬空指针进行解引用操作
        return 0;
    }
     

    危害

    程序崩溃:对野指针进行解引用操作时,可能会访问到操作系统保护的内存区域,从而导致程序崩溃。

    数据损坏:野指针可能指向了其他程序或数据结构正在使用的内存区域,对其进行写操作会破坏这些数据,导致程序出现不可预期的错误。

    避免方法

    1. 初始化指针

    在定义指针变量时,尽量对其进行初始化。如果暂时不知道要指向哪里,可以将其初始化为 NULL

    #include 
    
    int main() 
    {
        int *ptr = NULL;  // 初始化指针为NULL
    
        int num = 10;
    
        ptr = #  // 让指针指向有效的内存地址
    
        *ptr = 20;
    
        printf("%d\n", *ptr);
    
        return 0;
    }
    2. 释放内存后将指针置为 NULL
     

    在使用 free() 函数释放动态分配的内存后,及时将指针置为 NULL,避免再次使用该指针时产生错误。

    #include 
    #include 
    
    int main() 
    {
        int *ptr = (int *)malloc(sizeof(int));//申请动态空间
        if (ptr == NULL) 
        {
            return 1;
        }
        *ptr = 10;
    
        free(ptr);
    
        ptr = NULL;  // 将指针置为NULL
    
        // 此时再使用ptr就可以先判断是否为NULL
    
        if (ptr != NULL) 
        {
            *ptr = 20;
        }
        return 0;
    }
    3. 避免指针越界访问
     

    在使用指针访问数组元素时,要确保指针不会超出数组的有效范围。可以通过合理的边界检查来避免指针越界。

    #include 
    
    int main() 
    {
        int arr[5] = {1, 2, 3, 4, 5};
    
        int *ptr = arr;
    
        for (int i = 0; i < 5; i++) 
        {
            printf("%d ", *(ptr + i));  // 确保指针在数组有效范围内
        }
        return 0;
    }

    7. assert 断⾔

    assert 是一个宏,定义在   头文件中。其原型如下:
      void assert(int expression);

    这里的 expression 是一个需要检查的条件表达式,如果该表达式的值为 0(即条件为假),则 assert 会触发断言失败,程序会终止运行;如果表达式的值为非 0(即条件为真),则程序会继续正常执行。

    使用方法

    以下是一个简单的使用 assert 的示例代码:

    #include 
    #include 
    
    // 一个简单的除法函数
    int divide(int a, int b) 
    {
        // 使用assert检查除数是否为0
        assert(b != 0);
        return a / b;
    }
    
    int main()
    {
        int result1 = divide(10, 2);
        printf("10 / 2 = %d\n", result1);
    
        // 这会触发断言失败
        int result2 = divide(10, 0);
        printf("10 / 0 = %d\n", result2);
    
        return 0;
    }

    在上述代码中,divide 函数使用 assert 检查除数 b 是否为 0。当调用 divide(10, 2) 时,条件 b != 0 为真,程序会正常计算并输出结果;而当调用 divide(10, 0) 时,条件 b != 0 为假,assert 会触发断言失败,程序会终止运行,并输出类似下面的错误信息:

    Assertion failed: b != 0, file test.c, line 6

 

该信息会指出断言失败的条件、所在的文件名和行号,方便开发者定位问题。

工作原理

在程序编译时,assert 宏会被展开为一段代码。当程序运行到 assert 语句时,会先计算传入的条件表达式的值。如果值为 0,则会调用 abort() 函数终止程序,并输出包含断言条件、文件名和行号的错误信息;如果值为非 0,则程序会继续正常执行。

禁用 assert

在发布版本的程序中,为了避免 assert 带来的性能开销,可以通过定义 NDEBUG 宏来禁用 assert。例如:

#define NDEBUG

#include 
#include 

int main() {
    int num = 10;
    assert(num == 20);  // 由于NDEBUG被定义,此断言不会生效
    printf("Program continues...\n");
    return 0;
}

在上述代码中,由于在包含  头文件之前定义了 NDEBUG 宏assert 宏会被展开为空代码,不会进行条件检查,从而提高程序的运行效率。

优缺点

优点
  • 方便调试assert 可以在程序运行时自动检查条件,当条件不满足时会立即终止程序并给出详细的错误信息,帮助开发者快速定位和解决问题。
  • 代码简洁:使用 assert 可以在代码中简洁地表达对程序状态的预期,使代码更易读和维护。
缺点
  • 性能开销:在程序的每个 assert 语句处都需要计算条件表达式的值,这会带来一定的性能开销,尤其是在条件检查比较复杂或者程序中有大量 assert 语句时。
  • 仅用于调试:由于 assert 可以通过定义 NDEBUG 宏来禁用,因此它只能用于调试阶段,不能用于处理程序运行时可能出现的错误(如用户输入错误、文件打开失败等),这些错误需要使用其他机制(如错误码、异常处理等)来处理。

8. 指针的使⽤和传址调⽤

    8.1 strlen的模拟实现

库函数strlen的功能是求字符串⻓度,统计的是字符串中 \0 之前的字符的个数。
函数原型如下:
size_t strlen ( const char * str );
  • size_t 是无符号整数类型,通常用于表示对象的大小或长度。
  • const char *str 表示传入的是一个指向常量字符的指针,这意味着函数内部不会修改字
实现思路
    参数str接收⼀个字符串的起始地址,然后开始统计字符串中 \0 之前的字符个数,最终返回⻓度。 如果要模拟实现只要从起始地址开始向后逐个字符的遍历,只要不是 \0 字符,计数器就+1,这样直到 \0 就停⽌。
#include 

// 模拟实现strlen函数
size_t my_strlen(const char *str) 
{
    const char *ptr = str;  // 定义一个指针ptr指向字符串的首地址

    while (*ptr != '\0') // 当指针指向的字符不是字符串结束符时
    {  
        ptr++;  // 指针向后移动一位
    }
    return ptr - str;  // 计算指针ptr和str之间的差值,即为字符串的长度
}

int main() 
{
   char str[] = "Hello, World!";

   size_t length = my_strlen(str); //调用自定义的my_strlen函数计算字符串的长度,数组名就是地址

   printf("字符串 \"%s\" 的长度是: %zu\n", str, length);
   return 0;
}

函数实现

const char *ptr = str;

while ( *ptr !=  '\0' ) {

   ptr++;

}  

return ptr - str;

 

  • 首先,定义一个指针 ptr 并将其初始化为 str,即指向字符串的首地址。
  • 然后,使用 while 循环遍历字符串,当 ptr 指向的字符不是字符串结束符 '\0' 时,将 ptr 向后移动一位。
  • 最后,计算 ptr 和 str 之间的差值,即为字符串的长度。

传址调用:

size_t length = my_strlen(str);

  • 在 main 函数中,将字符串 str 的首地址传递给 my_strlen 函数,这就是传址调用。通过传址调用,函数可以直接访问和操作原始字符串,而不是创建字符串的副本。

你可能感兴趣的:(c语言,算法,开发语言)