C语言进阶(二)—— 指针强化

news2025/7/12 15:11:13

1. 指针是一种数据类型

1.1 指针变量

指针是一种数据类型,占用内存空间,用来保存内存地址

void test01(){
    
    int* p1 = 0x1234;
    int*** p2 = 0x1111;

    printf("p1 size:%d\n",sizeof(p1));
    printf("p2 size:%d\n",sizeof(p2));


    //指针是变量,指针本身也占内存空间,指针也可以被赋值
    int a = 10;
    p1 = &a;

    printf("p1 address:%p\n", &p1);
    printf("p1 address:%p\n", p1);
    printf("a address:%p\n", &a);

}

1.2 野指针和空指针

1.2.1 空指针

标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要使一个指针为NULL,可以给它赋值一个零值。为了测试一个指针百年来那个是否为NULL,你可以将它与零值进行比较。

对指针解引用操作可以获得它所指向的值。但从定义上看,NULL指针并未指向任何东西,因为对一个NULL指针因引用是一个非法的操作,在解引用之前,必须确保它不是一个NULL指针

如果对一个NULL指针间接访问会发生什么呢?结果因编译器而异。

不允许向NULL和非法地址拷贝内存:

void test(){
    char *p = NULL;
    //给p指向的内存区域拷贝内容
    strcpy(p, "1111"); //err

    char *q = 0x1122;  //非法地址
    //给q指向的内存区域拷贝内容
    strcpy(q, "2222"); //err        
}

1.2.2 野指针

在使用指针时,要避免野指针的出现:

野指针指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为 NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。

什么情况下回导致野指针?

  1. 指针变量未初始化

任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

  1. 指针释放后未置空

有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”

  1. 指针操作超越变量作用域

不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

操作野指针是非常危险的操作,应该规避野指针的出现:
1. 初始化时置 NULL
指针变量 一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。
2. 释放时置 NULL
当指针p指向的内存空间释放时,没有设置指针p的值为NULL。delete和free只是把内存空间释放了,但是并没有将指针p的值赋为NULL。 通常判断一个指针是否合法,都是使用if语句测试该指针是否为NULL

1.3 间接访问操作符

通过一个指针访问它所指向的地址的过程叫做间接访问,或者叫解引用指针,这个用于执行间接访问的操作符是*

注意:对一个int*类型指针解引用会产生一个整型值,类似地,对一个float*指针解引用会产生了一个float类型的值。

在指针 声明时,* 号表示所声明的变量为指针变量
在指针 使用时,* 号表示 操作指针所指向的内存空间
1)* 相当通过地址(指针变量的值)找到指针指向的内存,再操作内存
2)* 放在等号的 左边赋值(给内存赋值,写内存)
3)* 放在等号的 右边取值(从内存中取值,读内存)

1.4 指针的步长

指针是一种数据类型,是指它指向的内存空间的数据类型。指针所指向的内存空间决定了指针的步长。指针的步长指的是,当指针+1时候,移动多少字节单位

对自定义数据类型:
头文件 #include<stddef.h>
如果获取自定义数据类型中属性的偏移
offsetof( 结构体,属性 )

2. 指针的意义_间接赋值

2.1 间接赋值的三大条件

通过指针间接赋值成立的三大条件:

  1. 2个变量(一个普通变量一个指针变量、或者一个实参一个形参

  1. 建立关系

  1. 通过 * 操作指针指向的内存

2.2 间接赋值的推论

  • 用1级指针形参,去间接修改了0级指针(实参)的值。

  • 用2级指针形参,去间接修改了1级指针(实参)的值。

  • 用3级指针形参,去间接修改了2级指针(实参)的值。

  • n级指针形参,去间接修改了n-1级指针(实参)的值。

3. 指针做函数参数

指针做函数参数,具备输入输出特性:

  • 输入:主调函数分配内存

  • 输出:被调用函数分配内存

3.1 输入特性

void fun(char *p /* in */)
{
    //给p指向的内存区域拷贝内容
    strcpy(p, "abcddsgsd");
}

void test(void)
{
    //输入,主调函数分配内存
    char buf[100] = { 0 };
    fun(buf);
    printf("buf  = %s\n", buf);
}

3.2 输出特性

void fun(char **p /* out */, int *len)
{
    char *tmp = (char *)malloc(100);
    if (tmp == NULL)
    {
        return;
    }
    strcpy(tmp, "adlsgjldsk");

    //间接赋值
    *p = tmp;
    *len = strlen(tmp);
}

void test(void)
{
    //输出,被调用函数分配内存,地址传递
    char *p = NULL;
    int len = 0;
    fun(&p, &len);
    if (p != NULL)
    {
        printf("p = %s, len = %d\n", p, len);
    }
}

4. 字符串指针强化

4.1 字符串指针做函数参数

4.1.1 字符串基本操作

//字符串基本操作
//字符串是以0或者'\0'结尾的字符数组,(数字0和字符'\0'等价)
void test01(){

    //字符数组只能初始化5个字符,当输出的时候,从开始位置直到找到0结束
    char str1[] = { 'h', 'e', 'l', 'l', 'o' };
    printf("%s\n",str1);

    //字符数组部分初始化,剩余填0
    char str2[100] = { 'h', 'e', 'l', 'l', 'o' };
    printf("%s\n", str2);

    //如果以字符串初始化,那么编译器默认会在字符串尾部添加'\0'
    char str3[] = "hello";
    printf("%s\n",str3);
    printf("sizeof str:%d\n",sizeof(str3));
    printf("strlen str:%d\n",strlen(str3));

    //sizeof计算数组大小,数组包含'\0'字符
    //strlen计算字符串的长度,到'\0'结束

    //那么如果我这么写,结果是多少呢?
    char str4[100] = "hello";
    printf("sizeof str:%d\n", sizeof(str4));
    printf("strlen str:%d\n", strlen(str4));

    //请问下面输入结果是多少?sizeof结果是多少?strlen结果是多少?
    char str5[] = "hello\0world"; 
    printf("%s\n",str5);
    printf("sizeof str5:%d\n",sizeof(str5));
    printf("strlen str5:%d\n",strlen(str5));

    //再请问下面输入结果是多少?sizeof结果是多少?strlen结果是多少?
    char str6[] = "hello\012world";
    printf("%s\n", str6);
    printf("sizeof str6:%d\n", sizeof(str6));
    printf("strlen str6:%d\n", strlen(str6));
}

4.1.2 字符串拷贝功能实现

//拷贝方法1 利用下标方式
void copy_string01(char* dest, char* source ){

    for (int i = 0; source[i] != '\0';i++){
        dest[i] = source[i];
    }

}

//拷贝方法2 利用字符串指针
void copy_string02(char* dest, char* source){
    while (*source != '\0' /* *source != 0 */){
        *dest = *source;
        source++;
        dest++;
    }
}

//拷贝方法3
void copy_string03(char* dest, char* source){
    //判断*dest是否为0,0则退出循环
    while (*dest++ = *source++){}
}

4.1.3 字符串反转模型

void reverse_string(char* str){

    if (str == NULL){
        return;
    }

    int begin = 0;
    int end = strlen(str) - 1;
    
    while (begin < end){
        //下标法:交换两个字符元素
        char temp = str[begin];
        str[begin] = str[end];
        str[end] = temp;

        begin++;
        end--;
    }

}

void reverse_string1(char* str){
    char* start = str; //指向第一个字符的指针
    char* end = str + strlen(str) -1; //指向最后一个字符的指针
    while (start < end) {
        //利用字符指针交互元素
        char temp = *start;
        *start = *end;
        *end = temp;
    }
}

void test(){
    char str[] = "abcdefghijklmn";
    printf("str:%s\n", str);
    reverse_string(str);
    printf("str:%s\n", str);
    char str1[] = "abcdefghijklmnjkl";
    printf("str1:%s\n", str1);
    reverse_string1(str1);
    printf("str1:%s\n", str1);
}

4.2 字符串的格式化

4.2.1 sprintf

#include <stdio.h>
int sprintf(char *str, const char *format, ...);
功能:
根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到 出现字符串结束符 '\0' 为止。
参数:
str:字符串首地址
format:字符串格式,用法和printf()一样
返回值:
成功:实际格式化的字符个数
失败: - 1
void test(){
    
    //1. 格式化字符串
    char buf[1024] = { 0 };
    sprintf(buf, "你好,%s,欢迎加入我们!", "John");
    printf("buf:%s\n",buf);

    memset(buf, 0, 1024);
    sprintf(buf, "我今年%d岁了!", 20);
    printf("buf:%s\n", buf);

    //2. 拼接字符串
    memset(buf, 0, 1024);
    char str1[] = "hello";
    char str2[] = "world";
    int len = sprintf(buf,"%s %s",str1,str2);
    printf("buf:%s len:%d\n", buf,len);

    //3. 数字转字符串
    memset(buf, 0, 1024);
    int num = 100;
    sprintf(buf, "%d", num);
    printf("buf:%s\n", buf);
    //设置宽度 右对齐
    memset(buf, 0, 1024);
    sprintf(buf, "%8d", num);
    printf("buf:%s\n", buf);
    //设置宽度 左对齐
    memset(buf, 0, 1024);
    sprintf(buf, "%-8d", num);
    printf("buf:%s\n", buf);
    //转成16进制字符串 小写
    memset(buf, 0, 1024);
    sprintf(buf, "0x%x", num);
    printf("buf:%s\n", buf);

    //转成8进制字符串
    memset(buf, 0, 1024);
    sprintf(buf, "0%o", num);
    printf("buf:%s\n", buf);
}

4.2.2 sscanf

#include <stdio.h>
int sscanf(const char *str, const char *format, ...);
功能:
从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
参数:
str:指定的字符串首地址
format:字符串格式,用法和scanf()一样
返回值:
成功:成功则返回参数数目,失败则返回-1
失败: - 1

格式

作用

%*s或%*d

跳过数据

%[width]s

读指定宽度的数据

%[a-z]

匹配a到z中任意字符(尽可能多的匹配)

%[aBc]

匹配a、B、c中一员,贪婪性

%[^a]

匹配非a的任意字符,贪婪性

%[^a-z]

表示读取除a-z以外的所有字符

//1. 跳过数据
void test01(){
    char buf[1024] = { 0 };
    //跳过前面的数字
    //匹配第一个字符是否是数字,如果是,则跳过
    //如果不是则停止匹配
    sscanf("123456aaaa", "%*d%s", buf); 
    printf("buf:%s\n",buf);
}

//2. 读取指定宽度数据
void test02(){
    char buf[1024] = { 0 };
    //跳过前面的数字
    sscanf("123456aaaa", "%7s", buf);
    printf("buf:%s\n", buf);
}

//3. 匹配a-z中任意字符
void test03(){
    char buf[1024] = { 0 };
    //跳过前面的数字
    //先匹配第一个字符,判断字符是否是a-z中的字符,如果是匹配
    //如果不是停止匹配
    sscanf("abcdefg123456", "%[a-z]", buf);
    printf("buf:%s\n", buf);
}

//4. 匹配aBc中的任何一个
void test04(){
    char buf[1024] = { 0 };
    //跳过前面的数字
    //先匹配第一个字符是否是aBc中的一个,如果是,则匹配,如果不是则停止匹配
    sscanf("abcdefg123456", "%[aBc]", buf);
    printf("buf:%s\n", buf);
}

//5. 匹配非a的任意字符
void test05(){
    char buf[1024] = { 0 };
    sscanf("bcdefag123456", "%[^a]", buf);
    printf("buf:%s\n", buf);
}

//6. 匹配非a-z中的任意字符
void test06(){
    char buf[1024] = { 0 };
    sscanf("123456ABCDbcdefag", "%[^a-z]", buf);
    printf("buf:%s\n", buf);
}
/*
小练习:
1.已给定字符串为: helloworld@itcast.cn,请编码实现helloworld输出和itcast.cn输出。
2.已给定字符串为:123abcd$myname@000qwe.请编码实现匹配出myname字符串,并输出.
*/

5. 一级指针易错点

5.1 越界

void test(){
    char buf[3] = "abc";
    printf("buf:%s\n",buf);
}

5.2 指针叠加会不断改变指针指向

void test(){
    char *p = (char *)malloc(50);
    char buf[] = "abcdef";
    int n = strlen(buf);
    int i = 0;

    for (i = 0; i < n; i++)
    {
        *p = buf[i];
        p++; //修改原指针指向
    }

    free(p); //err
}

5.3 返回局部变量地址

char *get_str()
{
    char str[] = "abcdedsgads"; //栈区,
    printf("[get_str]str = %s\n", str);
    return str;
}

5.4 同一块内存释放多次(不可以释放野指针)

void test(){    
    char *p = NULL;

    p = (char *)malloc(50);
    strcpy(p, "abcdef");

    if (p != NULL)
    {
        //free()函数的功能只是告诉系统 p 指向的内存可以回收了
        // 就是说,p 指向的内存使用权交还给系统
        //但是,p的值还是原来的值(野指针),p还是指向原来的内存
        free(p); 
    }

    if (p != NULL)
    {
        free(p);
    }
}

6. const使用场景

用来修饰函数中的形参,防止误操作

//const修饰变量
void test01(){
    //1. const基本概念
    const int i = 0;
    //i = 100; //错误,只读变量初始化之后不能修改

    //2. 定义const变量最好初始化
    const int j;
    //j = 100; //错误,不能再次赋值

    //3. c语言的const是一个只读变量,并不是一个常量,可通过指针间接修改
    const int k = 10;
    //k = 100; //错误,不可直接修改,我们可通过指针间接修改
    printf("k:%d\n", k);
    int* p = &k;
    *p = 100;
    printf("k:%d\n", k);
}

//const 修饰指针
void test02(){

    int a = 10;
    int b = 20;
    //const放在*号左侧 修饰p_a指针指向的内存空间不能修改,但可修改指针的指向
    const int* p_a = &a;
    //*p_a = 100; //不可修改指针指向的内存空间
    p_a = &b; //可修改指针的指向

    //const放在*号的右侧, 修饰指针的指向不能修改,但是可修改指针指向的内存空间
    int* const p_b = &a;
    //p_b = &b; //不可修改指针的指向
    *p_b = 100; //可修改指针指向的内存空间

    //指针的指向和指针指向的内存空间都不能修改
    const int* const p_c = &a;
}
//const指针用法
struct Person{
    char name[64];
    int id;
    int age;
    int score;
};

//每次都对对象进行拷贝,效率低,应该用指针
void printPersonByValue(struct Person person){
    printf("Name:%s\n", person.name);
    printf("Name:%d\n", person.id);
    printf("Name:%d\n", person.age);
    printf("Name:%d\n", person.score);
}

//但是用指针会有副作用,可能会不小心修改原数据
void printPersonByPointer(const struct Person *person){
    printf("Name:%s\n", person->name);
    printf("Name:%d\n", person->id);
    printf("Name:%d\n", person->age);
    printf("Name:%d\n", person->score);
}
void test03(){
    struct Person p = { "Obama", 1101, 23, 87 };
    //printPersonByValue(p);
    printPersonByPointer(&p);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/362521.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python实现贝叶斯优化器(Bayes_opt)优化卷积神经网络分类模型(CNN分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。1.项目背景贝叶斯优化器(BayesianOptimization) 是一种黑盒子优化器&#xff0c;用来寻找最优参数。贝叶斯优化器是基…

Redis+Caffeine多级(二级)缓存,让访问速度纵享丝滑

目录多级缓存的引入多级缓存的优势CaffeineRedis实现多级缓存V1.0版本V2.0版本V3.0版本多级缓存的引入 在高性能的服务架构设计中&#xff0c;缓存是一个不可或缺的环节。在实际的项目中&#xff0c;我们通常会将一些热点数据存储到Redis或MemCache这类缓存中间件中&#xff0…

【100个 Unity实用技能】☀️ | Unity 通过自定义菜单将资源一键导出

Unity 小科普 老规矩&#xff0c;先介绍一下 Unity 的科普小知识&#xff1a; Unity是 实时3D互动内容创作和运营平台 。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者&#xff0c;借助 Unity 将创意变成现实。Unity 平台提供一整套完善的软件解决方案&#xff…

ESP32设备驱动-内置电容触摸传感器

内置电容触摸传感器 文章目录 内置电容触摸传感器1、电容触摸传感器介绍2、软件准备3、硬件准备4、代码实现本文将详细介绍如何使用ESP32的内置电容式传感器。 1、电容触摸传感器介绍 ESP32 具有可用作触摸按钮的电容式传感器。 这些是引脚排列上著名的TOUCH引脚。 在开发板的…

vs2019+opencv450+opencv contrib450+cmake3.25.2安装流程

目的 为了研究利用sift、orb、surf等算法进行视觉特征检测&#xff0c;第一次配置折腾了四五天。 电脑环境 win10 opencv4.5.0 opencv contrib 4.5.0 cmake3.25.2 报错 问题1 OpenCV(3.4.3) Error: The function/feature is not implemented (This algorithm is patented…

基于QUIC 协议的HTTP/3

HTTP/2 存在一些比较严重的与 TCP 协议相关的缺陷&#xff0c;但由于 TCP 协议僵化&#xff0c;我们几乎不可能通过修改 TCP 协议自身来解决这些问题&#xff0c;那么解决问题的思路是绕过 TCP 协议&#xff0c;发明一个 TCP 和 UDP 之外的新的传输协议。但是这也面临着和修改 …

Dart 表达式以及语法糖汇总

前言 Dart语言中有许多语法糖或者说lambda表达式&#xff0c;语法和代码量是简洁了许多&#xff0c;但给想要入门的我添加了许多困扰&#xff0c;我经常看官方API或者第三方文档API的时候&#xff0c;在示例中大量的使用了类似的语法糖&#xff0c;让代码的可读性大大下降&…

内部知识管理应该怎么做?

许多公司都知道需要有一个面向客户的知识库&#xff0c;以加强客户服务&#xff0c;提供更好的客户体验。 但是很多企业没有意识到的是&#xff0c;拥有一个内部知识库软件对于员工改善沟通和促进知识共享的重要性。 协作是组织成功的关键部分&#xff0c;通过明确的远景和使命…

微服务之Ribbon负载均衡

&#x1f3e0;个人主页&#xff1a;阿杰的博客 &#x1f4aa;个人简介&#xff1a;大家好&#xff0c;我是阿杰&#xff0c;一个正在努力让自己变得更好的男人&#x1f468; 目前状况&#x1f389;&#xff1a;24届毕业生&#xff0c;奋斗在找实习的路上&#x1f31f; &#x1…

论文笔记:DropMessage: Unifying Random Dropping for Graph Neural Networks

&#xff08;AAAI 23 优秀论文&#xff09; 1 intro GNN的一个普遍思路是&#xff0c;每一层卷积层中&#xff0c;从邻居处聚合信息 尽管GNN有显著的进步&#xff0c;但是在大规模图中训练GNN会遇到各种问题&#xff1a; 过拟合 过拟合之后&#xff0c;GNN的泛化能力就被限制…

Matplotlib精品学习笔记001-图形绘制常见的组分有哪些?

简介 从头学习&#xff0c;逐步精美 学习蓝本 学习资料是Quick start 内容 所有绘图的起始步骤 import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np通过一个简单的例子认识Matplotlib绘图的过程&#xff0c;见代码注释 import matplotlib.py…

重温Python基础知识点,又来重新巩固一遍

前言 最近有很多朋友刚接触python学的还是有点模糊 还有的朋友就是想重温一下基础内容&#xff0c;毕竟基础不牢地动山摇 行吧&#xff0c;就总结了以下的一些知识点&#xff0c;可以都看看哈 一、开发环境搭建 更多学习资料.点击领取即可 1.1 Python解释器的安装 Python解…

ctfshow 代码审计专题

文章目录web 301web 302web 303web 304web 305web 306web 307web 308web 309web 310web 301 简单看一下&#xff0c;在checklogin.php中发现了sql语句&#xff0c;且没过滤&#xff0c;直接sql注入。 –form测试,–batch绕过waf.–dump列出所有库和表。 得到账号密码&#xf…

MySQL数据库————MVCC

MySQL的脏读、幻读、不可重复读 脏读 现在有两个事务在操作table表&#xff0c;事务B修改了id2的name字段为李老四&#xff0c;但是没有提交&#xff0c;事务A查询id2的数据&#xff0c;得到name为李老四&#xff1b;事务B发生回滚&#xff0c;id2的数据的name又变回李四&…

Linux 防火墙配置(iptables和firewalld)

目录 防火墙基本概念 Iptables讲解 Iptables表 Iptables规则链 Iptables控制类型 Iptables命令配置 firewalld讲解 Firewalld区域概念 Firewalld两种配置方法 firewall-cmd命令行基础配置 firewall-config图形化配置 防火墙基本概念 防火墙就是根据系统管理员设定的…

用 tensorflow.js 做了一个动漫分类的功能(二)

前言&#xff1a;前面已经通过采集拿到了图片&#xff0c;并且也手动对图片做了标注。接下来就要通过 Tensorflow.js 基于 mobileNet 训练模型&#xff0c;最后就可以实现在采集中对图片进行自动分类了。这种功能在应用场景里就比较多了&#xff0c;比如图标素材站点&#xff0…

java集成redis简单存储

这里主要将数据存redis并设置过期时间、通过key删除redis、通过key更新redis&#xff08;续期&#xff09; 将数据存redis并设置过期时间 引入redis依赖 import org.springframework.data.redis.core.StringRedisTemplate;AutowiredStringRedisTemplate stringRedisTemplate…

【基础教程】Appium自动化测试,太详细了!

Appium简介Appium是一款开源的Appium自动化工具, 基于Webdriver协议, 主要有以下3个特点:全能: 支持iOS/Andorid/H5/混合App/WinApp通用: 支持Win/Linux/Mac, 支持Java/Python/Ruby/Js/PHP等各种语言开源: 免费App自动化测试工具对比iOS官方:Uiautomation/XCUITest: 白盒, UI测…

(二十)、完成个人中心页面的数据统计+设置详情页点赞用户的头像组【uniapp+uinicloud多用户社区博客实战项目(完整开发文档-从零到完整项目)】

1&#xff0c;个人中心页面的数据统计 数据统计包括两项内容&#xff1a;1.当前登录用户的点赞总数量。2.当前登录用户发布文章的总数量 1.1&#xff0c;在self页面data中定义对象 data() {return {totalObj:{artNum:0,likeNum:0}};},1.2&#xff0c;获取总数量的方法&#x…

多线程(初识线程)

线程的诞生 了解进程存在的意义 实现了并发编程的效果&#xff08;并发编程&#xff1a;有可能是并发执行&#xff0c;也有可能是并行执行&#xff09; 并发编程的目的&#xff1a;充分利用上多核CPU资源&#xff0c;提升运行效率 了解进程创建和销毁的过程带来的问题 进程是…