C语言----深入理解指针(1)(二)

简介: C语言----深入理解指针(1)

C语言----深入理解指针(1)(一)https://developer.aliyun.com/article/1544335

5.指针运算

指针的基本运算有三种,分别是:

指针+-指数

指针-指针

指针的关系运算

//循环打印数组的内容
//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    int sz = sizeof(arr) / sizeof(arr[0]);
//    for (int i = 0; i < sz; i++)
//    {
//        printf("%d", arr[i]);
//    }
//
//
//    return 0;
//
//}
 
//采用指针来获取数组元素的地址
//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    int sz = sizeof(arr) / sizeof(arr[0]);
//    int *p = &arr[0];//将arr[0]的地址存在*p中
//    for (int i = 0; i < sz; i++)
//    {
//        printf("%d ", *p);//解引用来打印arr[0]
//        p++;//打印完p++往后走一步,整型指针加一就是向后挪了一个整型
//        //循环十次就能把这个数组的内容打印出来
//    }
//    return 0;
//}
//获取数组第一个数字的地址赋值给p,再利用*p解引用,打印*p所指的数
//p+1就是*(p+1),打印数组下一个数字
 
//1.指针类型决定了指针+1的步长,决定了指针解引用的权限
//2.数组在内存中是连续存放的
 
 
//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    int sz = sizeof(arr) / sizeof(arr[0]);
//    int* p = &arr[0];//将arr[0]的地址存在*p中
//    for (int i = 0; i < sz; i++)
//    {
//        printf("%d ", *(p + i));//直接解引用*(p+i),当i=0时,就是*p,打印的就是数组第一个数
//        
//    }
//    return 0;
//}
p+i  是跳过i*sizeof(int)个字节
//指针-整数,从10开始打印
//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    int sz = sizeof(arr) / sizeof(arr[0]);
//    int* p = &arr[sz-1];//数组中最后一位的下标是sz-1
//    for (int i = 0; i < sz; i++)
//    {
//        printf("%d ", *p );
//        p--;
//    }
//    return 0;
//}
//指针-指针的绝对值是指针和指针之间元素的个数
//指针-指针,计算的前提条件是两个指针指向的是同一块空间
 
//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    printf("%d\n", &arr[9] - &arr[0]);//输出结果是9
//    printf("%d\n",  &arr[0]-&arr[9] );//输出结果是-9
//    return 0;
//}
//size_t my_strlen(char* p)//传过来的是数组名,用字符串指针来接收,*p指向的就是数组第一个元素
//{//size_t是无符号返回值
//    size_t count = 0;
//    while (*p != '\0')
//    {
//        count++;
//        p++;//往后走一位
//    }
//    return count;
//}
//int main()
//{
//    char arr[] = "abcdef";
//    size_t len = my_strlen(arr);//数组名其实是数组首元素的地址,arr==&arr[0]
//    //传过去数组名,就是传过去首元素的地址
//    printf("%zd\n", len);//打印结果是6
//
//    return 0;
//}
 
//另一种写法
size_t my_strlen(char* p)//传过来的是数组名,用字符串指针来接收,*p指向的就是数组第一个元素
{//size_t是无符号返回值
    char* star = p;//指向的是数组第一个数字
    char* end = p;
    while (*end!= '\0')//如果end不等于\0,就让end++
    {//这个while的循环条件可以是while(*end),因为到了\0的时候,\0的ASCLL值就是0,不满足循环条件就停下来了
 
        end++;//直到enf走到\0不满足条件就不进行循环了,此时的*end指向的就是\0
    }
    return end-star;//两个指针相减得到的就是指针之间元素的个数
}//数组中最后一个元素的指针减去第一个指针的元素得到的 就是这个数组的数量
int main()
{
    char arr[] = "abcdef";
    size_t len = my_strlen(arr);//数组名其实是数组首元素的地址,arr==&arr[0]
    //传过去数组名,就是传过去首元素的地址
    printf("%zd\n", len);//打印结果是6
 
    return 0;
}
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//数组中随着下标的增长,地址由低到高变化的
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = arr[0];//*p指向数组首个元素
    while (p < &arr[sz])//数组下标为10的数不在数组之内,所以在arr[sz]前面的就是数组所有的元素
    {//p的地址大小小于arr[sz]的地址,所以只要地址一直小于arr[sz]的地址就一直可以循环打印
        printf("%d ", *p);
        p++;
    }
    return 0;
}
//这里就运用到指针关系大大小p < &arr[sz]

6.野指针

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

//1.未初始化会造成野指针
/*int main()
{
    //一个局部变量不初始化的话,它的值是随机的
    int* p;//p是局部变量,没有初始化,其值是随机值,如果将p中的值当做地址,
    //解引用操作就会形成非法访问
    *p = 20;//p就是野指针
    return 0;
}*/
 
 
//2.指针越界访问也会造成野指针的问题
/*int main()
{
    int arr[10] = { 0 };//数组初始化
    int i = 0;
    int* p = &arr[0];//将数组第一个元素的地址给p
    for (i = 0; i <= 10; i++)//循环11次
    {
        *p = i;//当第11次循环的时候,访问到了不属于这个数组的空间,访问到数组之外的空间了
        p++;//此时的p就是野指针了
    }
 
    return 0;
}*/
 
 
//3.指针指向的空间释放也会造成野指针
int test()
{
    int n = 100;//n是局部变量,进入函数创建,出函数销毁,也就是说返回的&n的地址并不是原先存储100的地址
    return &n;//地址被还给操作系统了
}
 
int main()
{
    int* p = test();//
    printf("%d\n", *p);//p一旦接受这个地址,p里面的就是野指针,造成内存非法访问,篡改内存
    return 0;
}
//规避野指针
//如果明确知道指针指向哪里就直接赋值地址,如果不知道指针应该指向哪里,
// 可以给指针赋值NULL
//NULL是c语言中定义的一个表示符常量,值是0,0也是地址,这个地址是无法正常使用的,读写地址会报错
/*int main()
{
    int a = 10;
    int* p = &a;//给出明确的地址,将a的地址赋值给p
 
    int* p2 = NULL;//把野狗拴在柱子上。p2没有指向的对象
 
 
    return 0;
}*/
 
//int main()
//{
//    int* p = NULL;
//    if (p != NULL)
//    { 
//        *P = 200;
//    }
//    return 0;
//
//}
//当指针变量指向一块区域时,我们可以通过指针访问该区域,
// 后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL
//只要是空指针我们就不去访问,类似把野狗用柱子拴起来,将野指针暂时管理起来

如何规避野指针:

1.对指针进行初始化

2.小心指针越界

3.指针变量不再使用,及时置NULL(空指针),指针使用之前检查有效性

4.避免返回局部变量的地址

7.assert断言

assert.h头文件定义了assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行,这个宏常常被称为“断言”

#define NDEBUG
 int main()
{
    /*int* p = NULL;
    assert(p != NULL);*///会报错,assert判断后面括号的条件,为假就报错 
     int a = 10;
     int* p = &a;
     assert(p != NULL);//这种情况就不会报错
    //assert可以判断指针的有效性
 
 
//#define NDEBUG,利用这句话就可以控制assert,是否产生效果
     //如果想产生效果就注释掉,不想产生效果就在#include <assert.h>上方添加
//只要添加了#define NDEBUG这个语句,代码中的assert就会被禁用
 
//assert()语句的缺点就是,因为引入了额外的检查,增加了程序的运行时间
    /*if (p != NULL)
    { 
        *P = 200;
    }
    return 0;
}*/
//在vs版本中,Debug中assert()语句是可以使用的,但是在Release版本中直接优化掉了assert()语句
 
//这样debug版本编写代码有利于程序员排查问题,在Release版本不影响用户使用时的效率
//在Release版本选择性的优化assert()

8.指针的使用和地址调用

//strlen是求字符长度的,统计的是字符串中\0之前的字符个数
//函数求字符串长度
//参数s指向的字符串不期望被修改
size_t my_strlen(const char*s)//把字符元素的地址传过来,用char*s接收
{//添加const不希望字符串被修改,直接将每次传来的实参固定死
    //不加const的话原先字符串的长度就被修改了
    size_t count = 0;
    assert(s != NULL);//防止传过来的实参为空指针,检测指针s是否有效
    while (*s)//当s遇到\0的时候,循环就停止
    {
        count++;
        s++;
    }
    return count;
}
 
int main()
{
    char arr[] = "abcdef";
    size_t len = my_strlen(arr);
    printf("%zd", len);
    return 0;
}
//写一个函数,交换两个整型变量的值
//void Swap1(int x, int y)
//{
//    int z = 0;
//    z = x;//先把x的值放到z里面,x空了
//    x = y;//把y的值放到x里面,y空了
//    y = z;//把z的值放到y里面去,在这之前放在z里面的值是x
//}
//
//
//int main()
//{
//    int a = 0;
//    int b = 0;
//    scanf("%d %d", &a, &b);
//    
//    //交换a和b的值
//    printf("交换前:a=%d b=%d\n", a, b);
//    Swap1(a, b);
//    printf("交换后:a=%d b=%d\n", a, b);
//    return 0;
//}
//改代码打印结果是:
//交换前:a=3 b=5
//交换后:a = 3 b = 5
//很明显,出问题了 
 
//当实参传递给形参的时候,形参是实参的一份临时拷贝,
//对形参的修改不会影响实参
//那么如何修改呢?
 
void Swap2(int *pa, int*pb)
{
    int z = 0;
    z = *pa;//z=a
    *pa = *pb;//a=b
    *pb = z;//b=z
}//脑海中把图画出来
 
 
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
 
    //交换a和b的值
    printf("交换前:a=%d b=%d\n", a, b);
    Swap2(&a, &b);//把a、b的地址传过去
    printf("交换后:a=%d b=%d\n", a, b);
    return 0;
}
 
//在这两个代码中,Swap1是传值调用
//Swap2是传址调用,直接将变量本身传递过去了
//当我们采用的是传值调用,形参和实参占用的是不同的空间,对形参的修改不会改变实参
 
//完成两个整数的相加
Add(int x,int y)
{
    int z = x + y;
    return z;
}
 
 
int main()
{
    int a = 10;
    int b = 20;
    int c=Add(a, b);
    printf("%d\n", c);
    //传值调用
    return 0;
}
//当使用传值调用时,实际上是将参数值复制到函数内部的一个局部变量中。
// 这意味着函数内部对参数值所做的任何修改都不会影响原始变量。
//原始数据不会被修改,传值调用通常被认为是安全的
//传址调用涉及将参数的内存地址传递给函数。这意味着函数可以直接访问和修改原始变量。

传值调用:实际上是将参数值复制到函数内部的一个局部变量中,这意味着函数内部对参数值所做的任何修改都不会影响原始变量,原始数据不会被修改

传址调用:涉及将参数的内存地址传递给函数,这意味着函数可以直接访问和修改原始变量。

相关文章
|
2月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
52 0
|
10天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
58 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
10天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
35 9
|
10天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
32 7
|
20天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
81 12
|
13天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
14天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
45 3
|
14天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
20天前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
42 10
|
14天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
29 1