罗剑锋的C++实战笔记学习(一):const、智能指针、lambda表达式

news2024/11/18 4:32:15

1、const

1)、常量

const一般的用法就是修饰变量、引用、指针,修饰之后它们就变成了常量,需要注意的是const并未区分出编译期常量和运行期常量,并且const只保证了运行时不直接被修改

一般的情况,const放在左边,表示常量:

const int x = 100; // 常量
const int& rx = x; // 常量引用
const int* px = &x; // 常量指针

给变量加上const之后变量就成了常量,只能读、禁止写,编译器会帮你检查出所有对它的写操作,发出警告,在编译阶段防止有意或者无意的修改。这样一来,const常量用起来就相对安全一点。所以在设计函数的时候,将参数用const修饰的话,一个是可以保证效率,另一个是保证安全

2)、修饰成员函数

除此之外,const还能声明在成员函数上,const被放在了函数的后面,表示这个函数是一个常量,函数的执行过程是const的,不会修改对象的状态(即成员变量),比如:

class DemoClass final {
private:
    const long MAX_SIZE = 256; // const成员变量
    int m_value; // 成员变量
public:
    int get_value() const // const成员函数
    {
        // error: Cannot assign to non-static data member within const member function 'get_value'
        m_value = 100;
        return m_value;
    }
};
3)、指针常量

const放在*的右边,表示指针是常量(const pointer to int),指针不能被修改,而指向的变量可以被修改:

    int x = 100;
    int b = 150;
    int *const px = &x;
    *px = 102; // success
    px = &b; // error: Cannot assign to variable 'px' with const-qualified type 'int *const'

int *const pxconst int* px的区别:

  • int *const px定义了一个指针常量,指针本身的地址不可改变,但可以改变指针所指向的数据
  • const int* px定义了一个指向常量的指针,可以改变指针的地址使其指向其他int,但不能通过此指针修改所指向的int值
4)、小结
const非const
对象(实例)const T:对象只读,只能调用const成员函数可以修改对象,调用任意成员函数
引用const T&:引用的对象只读,只能调用const成员函数
指针*const:指针指向的对象只读,只能调用const成员函数
成员函数func() const:不允许修改成员变量可以修改成员变量
指针常量const T*:表示指针是常量,指针不能被修改,而其指向的变量可以被修改指针和其指向的变量都可以被修改

2、智能指针

1)、unique_ptr

unique_ptr是一种独占资源所有权的指针,它会自动管理初始化时的指针,在离开作用域时析构释放内存

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> ptr1(new int(10)); // int智能指针
    assert(*ptr1 == 10); // 使用*取内容
    assert(ptr1 != nullptr); // 判断是否为空指针

    std::unique_ptr<std::string> ptr2(new std::string("hello")); // string智能指针
    assert(*ptr2 == "hello"); // 使用*取内容
    assert(ptr2->size() == 5); // 使用->调用成员函数
    return 0;
}

在C++14的时候新加入了make_unique()函数,可以利用它构造一个unique_ptr对象:

#include <iostream>
#include <memory>

int main() {
    auto ptr1 = std::make_unique<int>(42); // 工厂函数创建智能指针
    assert(ptr1 && *ptr1 == 42);

    auto ptr2 = std::make_unique<std::string>("god of war"); // 工厂函数创建智能指针
    assert(!ptr2->empty());
    return 0;
}

unique_ptr的所有权:

unique_ptr表示指针的所有权是唯一的,不允许共享,任何时候只能有一个人持有它

为了实现这个目的,unique_ptr应用了C++的转移(move)语义,同时禁止了拷贝赋值,所以,在向另一个unique_ptr赋值的时候,要特别留意,必须用std::move()函数显式地声明所有权转移

赋值操作之后,指针的所有权就被转走了,原来的unique_ptr变成了空指针,新的unique_ptr接替了管理权,保证所有权的唯一性:

#include <iostream>
#include <memory>

int main() {
    auto ptr1 = std::make_unique<int>(42); // 工厂函数创建智能指针
    assert(ptr1 && *ptr1 == 42); // 此时智能指针有效

    auto ptr2 = std::move(ptr1); // 使用move()转移所有权
    assert(!ptr1 && ptr2); // ptr1变成了空指针
    return 0;
}

unique_ptr作为参数和返回值:

unique_ptr作为参数传递不会发生拷贝,但是会将对象所有权会转移到函数里,如下ptr会在main()方法结束之前被销毁:

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }

    ~Resource() { std::cout << "Resource destroyed\n"; }

    friend std::ostream &operator<<(std::ostream &out, const Resource &res) {
        out << "I am a resource";
        return out;
    }
};

void takeOwnership(std::unique_ptr<Resource> res) {
    if (res) {
        std::cout << *res << '\n';
    }
} // Resource对象会在这里销毁

int main() {
    auto ptr{std::make_unique<Resource>()};
    // takeOwnership(ptr); // 不能这样写,unique_ptr禁止了拷贝赋值,需要使用std::move()函数显式地声明所有权转移
    takeOwnership(std::move(ptr));
    std::cout << "Ending program\n";
    return 0;
}

输出:

Resource acquired
I am a resource
Resource destroyed
Ending program

有时候不想对象的所有权转移到函数里,这时候可以通过get()方法获取对象,如下:

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }

    ~Resource() { std::cout << "Resource destroyed\n"; }

    friend std::ostream &operator<<(std::ostream &out, const Resource &res) {
        out << "I am a resource";
        return out;
    }
};

void takeOwnership(Resource *res) {
    if (res) {
        std::cout << *res << '\n';
    } else {
        std::cout << "No resource\n";
    }
}

int main() {
    auto ptr{std::make_unique<Resource>()};
    takeOwnership(ptr.get());
    std::cout << "Ending program\n";
    return 0;
} // Resource对象会在这里销毁

输出:

Resource acquired
I am a resource
Ending program
Resource destroyed

unique_ptr可以直接作为返回值返回:

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }

    ~Resource() { std::cout << "Resource destroyed\n"; }

    friend std::ostream &operator<<(std::ostream &out, const Resource &res) {
        out << "I am a resource";
        return out;
    }
};

std::unique_ptr<Resource> createResource() {
    return std::make_unique<Resource>();
}

int main() {
    auto ptr{createResource()};
    return 0;
}

在C++14及之前的版本中会使用std::move来返回Resource对象,在C++17及以后版本中进行了RVO优化(https://en.wikipedia.org/wiki/Copy_elision),尽管没有显式使用std::move,编译器依然能够识别并优化这个返回过程,直接将新创建的Resource对象的所有权从createResource函数内部转移到了main函数中的ptr变量,而无需实际执行移动构造函数

2)、shared_ptr

shared_ptr和unique_ptr不同是它的所有权是可以被安全共享的,也就是说支持拷贝赋值,允许被多个人同时持有,就像原始指针一样

在底层实现中,shared_ptr采用引用计数的方式实现。引用计数最开始的时候是1,表示只有一个持有者。如果发生拷贝赋值——也就是共享的时候,引用计数就增加(为了保证并发安全,引用计数器的加1,减1操作都是原子操作),而发生析构销毁的时候,引用计数就减少。只有当引用计数减少到0,也就是说,没有任何人使用这个指针的时候,它才会真正调用delete释放内存

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }

    ~Resource() { std::cout << "Resource destroyed\n"; }
};

int main() {
    auto ptr1 = std::make_shared<Resource>(); // 工厂函数创建智能指针
    assert(ptr1 && ptr1.unique()); // 此时智能指针有效且唯一

    {
        auto ptr2 = ptr1; // 直接拷贝赋值,不需要使用move()
        assert(ptr1 && ptr2); // 此时两个智能指针均有效

        assert(ptr1 == ptr2); // shared_ptr可以直接比较
        
        // 两个智能指针均不唯一,且引用计数为2
        assert(!ptr1.unique() && ptr1.use_count() == 2);
        assert(!ptr2.unique() && ptr2.use_count() == 2);

        std::cout << "Killing one shared pointer\n";
    } // ptr2离开了作用域,但是没有资源被销毁

    assert(ptr1 && ptr1.unique()); // 此时智能指针有效且唯一
    std::cout << "Killing another shared pointer\n";
    return 0;
} // ptr1离开了作用域,Resource对象会在这里销毁

输出:

Resource acquired
Killing one shared pointer
Killing another shared pointer
Resource destroyed

在上面的例子中, ptr2在自己的作用域中被创建,然后出了作用域后ptr2虽然被销毁,但是所管理的资源却在main方法结束后才被销毁

enable_shared_from_this:

如果不小心直接在类里面返回this对象想要获得该对象的shared_ptr,那么会让一个对象被delete两次,如下:

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }

    ~Resource() { std::cout << "Resource destroyed\n"; }

    std::shared_ptr<Resource> GetSPtr() {
        return std::shared_ptr<Resource>(this);
    }
};

int main() {
    auto sptr1 = std::make_shared<Resource>();
    auto sptr2 = sptr1->GetSPtr();
    return 0;
}

输出:

Resource acquired
Resource destroyed
studyProject(6959,0x7ff854f04700) malloc: *** error for object 0x6000035ed1f8: pointer being freed was not allocated
studyProject(6959,0x7ff854f04700) malloc: *** set a breakpoint in malloc_error_break to debug

上面的代码其实会生成两个独立的shared_ptr,他们的控制块是独立的,所以导致Resource被释放了两次

使用enable_shared_from_this可以避免上述情况:

#include <iostream>
#include <memory>

class Resource : public std::enable_shared_from_this<Resource> {
public:
    Resource() { std::cout << "Resource acquired\n"; }

    ~Resource() { std::cout << "Resource destroyed\n"; }

    std::shared_ptr<Resource> GetSPtr() {
        return shared_from_this();
    }
};

int main() {
    auto sptr1 = std::make_shared<Resource>();
    auto sptr2 = sptr1->GetSPtr();
    return 0;
}

shared_ptr的循环引用问题:

shared_ptr的引用计数也导致了一个新的问题,就是循环引用,这在把shared_ptr作为类成员的时候最容易出现,典型的例子就是链表节点

#include <iostream>
#include <memory>

class Node {
public:
    using this_type = Node;
    using shared_type = std::shared_ptr<this_type>;

    Node(const std::string &name) : name(name) {
        std::cout << name << " created\n";
    }

    ~Node() {
        std::cout << name << " destroyed\n";
    }

    std::string name;
    shared_type next; // 使用智能指针来指向下一个节点
};

int main() {
    auto n1 = std::make_shared<Node>("n1");
    auto n2 = std::make_shared<Node>("n2");

    assert(n1.use_count() == 1); // 引用计数为1
    assert(n2.use_count() == 1);

    n1->next = n2; // 两个节点互指,形成了循环引用
    n2->next = n1;

    assert(n1.use_count() == 2); // 引用计数为2
    assert(n2.use_count() == 2); // 无法减到0,无法销毁,导致内存泄漏
    return 0;
}

输出:

n1 created
n2 created

上面的代码中,两个节点指针刚创建时,引用计数是1,但指针互指(即拷贝赋值)之后,引用计数都变成了2

这个时候,shared_ptr意识不到这是一个循环引用,多算了一次计数,后果就是引用计数无法减到0,无法调用析构函数执行delete,最终导致内存泄漏

3)、weak_ptr

weak_ptr是专门为打破循环引用而设计,它实际上不会托管对象,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。在需要的时候,可以调用weak_ptr的成员函数lock(),获取shared_ptr(强引用)

#include <iostream>
#include <memory>

class Node {
public:
    using this_type = Node;
    using shared_type = std::weak_ptr<this_type>; // 注意这里,别名改用weak_ptr

    Node(const std::string &name) : name(name) {
        std::cout << name << " created\n";
    }

    ~Node() {
        std::cout << name << " destroyed\n";
    }

    std::string name;
    shared_type next;
};

int main() {
    auto n1 = std::make_shared<Node>("n1");
    auto n2 = std::make_shared<Node>("n2");

    n1->next = n2; // 两个节点互指,形成了循环引用
    n2->next = n1;

    assert(n1.use_count() == 1); // 因为使用了weak_ptr,引用计数为1
    assert(n2.use_count() == 1); // 打破循环引用,不会导致内存泄漏

    if (!n1->next.expired()) { // 检查指针是否有效
        auto ptr = n1->next.lock(); // lock()获取shared_ptr
        assert(ptr == n2);
    }
    return 0;
}

输出:

n1 created
n2 created
n2 destroyed
n1 destroyed
4)、小结

unique_ptr是一种独占资源所有权的指针,它会在栈上分配,然后在离开作用域之后进行释放,删除里面持有的对象,它只能使用move语义转移对象。所以如果你想操作一个指针,在进入作用域的时候分配好内存,然后在离开作用域的时候安全释放对象,那么可以使用它

shared_ptr所管理的资源可以被多个对象持有,并且使用引用计数策略来释放对象,如果计数没有清零,那么它所管理的资源不会释放

weak_ptr不管理对象,只是shared_ptr对象管理的资源的观察者,所以它不影响共享资源的生命周期,它用于解决shared_ptr循环引用

3、lambda表达式

1)、lambda基本使用
#include <iostream>

int main() {
    auto func = [](int x) // 定义一个lambda表达式
    {
        std::cout << x * x << std::endl; // lambda表达式的具体内容
    };
    func(3); // 调用lambda表达式
    return 0;
}

C++里的lambda表达式除了可以像普通函数那样被调用,还有一个普通函数所不具备的特殊本领,就是可以捕获外部变量,在内部的代码里直接操作

#include <iostream>

int main() {
    int n = 10; // 一个外部变量
    auto func = [=](int x) // lambda表达式,用=捕获
    {
        std::cout << x * n << std::endl; // 直接操作外部变量
    };
    func(3); // 调用lambda表达式
    return 0;
}
2)、使用lambda的注意事项

1)lambda的形式

嵌套定义lambda表达式

#include <iostream>

int main() {
    auto f1 = []() // 定义一个lambda表达式
    {
        std::cout << "lambda f1" << std::endl;

        auto f2 = [](int x) // 嵌套定义lambda表达式
        {
            return x * x;
        }; // lambda f2

        std::cout << f2(10) << std::endl;
    }; // lambda f1
    
    f1();
    return 0;
}

匿名lambda表达式

#include <iostream>

int main() {
    std::vector<int> v = {3, 1, 8, 5, 0}; // 标准容器

    std::cout << *find_if(begin(v), end(v), // 标准库里的查找算法
                          [](int x) // 匿名lambda表达式,不需要auto赋值
                          {
                              return x >= 5; // 用做算法的谓词判断条件
                          }
    )
              << std::endl; // 语句执行完,lambda表达式就不存在了
    return 0;
}

2)lambda的变量捕获

lambda的变量捕获要点:

  • [=]:表示按值捕获所有外部变量,表达式内部是值的拷贝,并且不能修改
  • [&]:按引用捕获所有外部变量,内部以引用的方式使用,可以修改
  • 可以在[]里明确写出外部变量名,指定按值或者按引用捕获
int main() {
    int x = 33; // 一个外部变量
    auto f1 = [=]() // lambda表达式,用=按值捕获
    {
        //x += 10; // x只读,不允许修改
    };
    auto f2 = [&]() // lambda表达式,用&按引用捕获
    {
        x += 10; // x是引用,可以修改
    };
    auto f3 = [=, &x]() // lambda表达式,用&按引用捕获x,其他的按值捕获
    {
        x += 20; // x是引用,可以修改
    };
    return 0;
}

3)泛型的lambda

在C++14里,lambda表达式可以实现泛型化,相当于简化了的模板函数

#include <iostream>

int main() {
    auto f = [](const auto &x) // 参数使用auto声明,泛型化
    {
        return x + x;
    };
    std::cout << f(3) << std::endl; // 参数类型是int
    std::cout << f(0.618) << std::endl; // 参数类型是double

    std::string str = "matrix";
    std::cout << f(str) << std::endl; // 参数类型是string
    return 0;
}
3)、小结
  1. lambda表达式是一个闭包,能够像函数一样被调用,像变量一样被传递
  2. 可以使用auto自动推导类型存储lambda表达式,但C++鼓励尽量就地匿名使用,缩小作用域
  3. lambda表达式使用[=]的方式按值捕获,使用[&]的方式按引用捕获,空的[]则是无捕获(也就相当于普通函数)
  4. 捕获引用时必须要注意外部变量的生命周期,防止变量失效
  5. C++14里可以使用泛型的lambda表达式,相当于简化的模板函数

参考:

07 | const/volatile/mutable:常量/变量究竟是怎么回事?

C++ 中让人头晕的const & constexpr

08 | smart_ptr:智能指针到底“智能”在哪里?

写给[C++ ]新人智能指针避坑指南

10 | lambda:函数式编程带来了什么?

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

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

相关文章

政安晨【零基础玩转各类开源AI项目】基于Ubuntu系统部署ComfyUI:功能最强大、模块化程度最高的Stable Diffusion图形用户界面和后台

目录 ComfyUI的特性介绍 开始安装 做点准备工作 在Conda虚拟环境中进行 依赖项的安装 运行 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 零基础玩转各类开源AI项目 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&…

2024年江苏省研究生数学建模竞赛B题人造革性能优化设计研究论文和代码

经过不懈的努力&#xff0c; 2024年江苏省研究生数学建模竞赛B题人造革性能优化设计研究论文和代码已完成&#xff0c;代码为C题全部问题的代码&#xff0c;论文包括摘要、问题重述、问题分析、模型假设、符号说明、模型的建立和求解&#xff08;问题1模型的建立和求解、问题2模…

适用于 Windows的 5 个最佳 PDF 转 Word 转换器

PDF 文件是共享文档的首选格式&#xff0c;但是&#xff0c;此类文件存在限制&#xff0c;使其难以修改或编辑。因此&#xff0c;您可能会发现自己正在寻找一种将 PDF 文件转换为 Word 或其他可编辑格式的方法。 有许多不同的 PDF 转换器&#xff0c;每个转换器的功能略有不同…

KDTree 简单原理与实现

介绍 K-D树是一种二叉树的数据结构&#xff0c;其中每个节点代表一个k维点&#xff0c;可用于组织K维空间中的点&#xff0c;其中K通常是一个非常大的数字。二叉树结构允许对多维空间中的点进行非常有效的搜索&#xff0c;包括最近邻搜索和范围搜索&#xff0c;树中的每个非叶…

嵌入式系统中状态机实现详解

嵌入式开发中系统经常处于某种状态,如何处理呢?接下来分析一下,状态机的实现无非就是 3 个要素:状态、事件、响应。转换成具体的行为就 3 句话。 发生了什么事? 现在系统处在什么状态? 在这样的状态下发生了这样的事,系统要干什么? 用 C 语言实现状态机主要有 3 种方法…

机器学习——岭回归

1、岭回归与线性回归的区别 岭回归&#xff08;Ridge Regression&#xff09;和线性回归&#xff08;Linear Regression&#xff09;都是用于回归分析的统计方法&#xff0c;但它们在处理方式和应用场景上有一些关键的区别&#xff1a; a)基本概念 线性回归&#xff1a;目标是…

帕金森病患者在选择运动疗法时应该注意哪些事项?

帕金森病患者在选择运动疗法时&#xff0c;应该遵循以下几点注意事项&#xff1a; 个性化运动处方&#xff1a;根据患者的病情、年龄、健康状况、以往运动能力等因素&#xff0c;制定个体化的运动处方。 避免运动负荷过大&#xff1a;运动时间不宜过长&#xff0c;注意控制心率…

【优化论】约束优化算法

约束优化算法是一类专门处理目标函数在存在约束条件下求解最优解的方法。为了更好地理解约束优化算法&#xff0c;我们需要了解一些核心概念和基本方法。 约束优化的核心概念 可行域&#xff08;Feasible Region&#xff09;&#xff1a; 比喻&#xff1a;想象你在一个园艺场…

量化机器人:金融市场的智能助手

引言 想象一下&#xff0c;在繁忙的金融市场中&#xff0c;有一位不知疲倦、冷静客观的“超级交易员”&#xff0c;它能够迅速分析海量数据&#xff0c;精准捕捉交易机会&#xff0c;并自动完成买卖操作。这位“超级交易员”不是人类&#xff0c;而是我们今天要聊的主角——量…

SSM家庭理财个人理财系统-JAVA【数据库设计、源码、开题报告】

第一章 绪论 1.1 课题背景、目的及意义 从 20 世纪末以来&#xff0c;在全球经济日趋一体化的背景之下&#xff0c;中国经济也得到了飞速的发展&#xff0c;家庭收入也快速增长。居民的消费结构发生了巨大变化&#xff0c;购置房产、旅游、汽车消费、教育等成为居民消费重点。…

SQL使用join查询方式找出没有分类的电影id以及名称

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 现有电影信息…

ABAP 生成word文档

1.创建模板 通过开发工具->空间->格式文本为word添加变量 选中要设为变量的文本&#xff0c;点击格式文本&#xff0c;然后在属性页签设置变量名 模板使用示例参考ZABAPDOCX包下的模板 2. 代码生成 参考ZABAPDOCX包下示例程序&#xff0c;可直接执行下载word文档 如果…

linux ifconfig未找到命令

linux ifconfig未找到命令 1、使用yum安装net-tools yum install net-toolsyum报未找到命令请查看文章vim未找到命令&#xff0c;且yum install vim安装vim失败 2、安装后使用ifconfig命令 ifconfig

【Kubernetes】Pod 资源调度之亲和性调度

Pod 资源调度之亲和性调度 1.Node 亲和性调度1.1 Node 硬亲和性1.2 Node 软亲和性 2.Pod 亲和性调度2.1 Pod 硬亲和2.2 Pod 软亲和2.3 Pod 反亲和 Kubernetes 的 默认调度器 以 预选、优选、选定机制 完成将每个新的 Pod 资源绑定至为其选出的目标节点上&#xff0c;不过&#…

解决数据库PGSQL,在Mybatis中创建临时表报错TODO IDENTIFIER,连接池用的Druid。更换最新版本Druid仍然报错解决

Druid版本1.1.9报错Caused by: java.sql.SQLException: sql injection violation, syntax error: TODO IDENTIFIER : CREATE TEMPORARY TABLE temp_ball_classify (id int8 NOT NULL,create_time TIMESTAMP,create_by VARCHAR,classify_name VARCHAR) 代码如下&#xff1a; 测…

基于java+springboot+vue实现的在线课程管理系统(文末源码+Lw)236

摘要 本文首先介绍了在线课程管理系统的现状及开发背景&#xff0c;然后论述了系统的设计目标、系统需求、总体设计方案以及系统的详细设计和实现&#xff0c;最后对在线课程管理系统进行了系统检测并提出了还需要改进的问题。本系统能够实现教师管理&#xff0c;科目管理&…

Android --- 新电脑安装Android Studio 使用 Android 内置模拟器电脑直接卡死,鼠标和键盘都操作不了

新电脑安装Android Studio 使用 Android 内置模拟器电脑直接卡死&#xff0c;鼠标和键盘都操作不了 大概原因就是,初始化默认Google的安卓模拟器占用的RAM内存是2048&#xff0c;如果电脑的性能和内存一般的话就可能卡死&#xff0c;解决方案是手动修改安卓模拟器的config文件&…

皮卡超级壁纸 | 幸运壁纸幸运壁纸app是一款涵盖了热门影视剧、动漫、风景等等资源的装饰工具,

软件下载链接&#xff1a;壁纸下载方式在链接中文章底部 皮卡超级壁纸 皮卡超级壁纸是一款专为手机用户设计的壁纸应用&#xff0c;它提供了丰富多样的高清壁纸资源&#xff0c;让用户的手机界面焕然一新。这款应用以其海量的壁纸库和用户友好的操作界面&#xff0c;在市场上…

模型加载gltf

3. 加载.gltf文件(模型加载全流程) | Three.js中文网 (webgl3d.cn) 1.引入GLFloader.js模型加载器 import {GLTFloader} from three/addons/loader/GLTFloader.js; 2.GLTF加载器new GLTFloader() 执行new GLTFloader()就可以实例化一个gltf加载器对象 const loader new …

Star CCM+界面显示字体大小调整

前言 打开界面字体显示大小是默认的&#xff0c;软件内设置调整默认字体的大小是无法实现&#xff0c;需要在图标属性中进行设置&#xff0c;操作方法与中英文切换很类似&#xff0c;具体方法如下&#xff1a; 操作流程 1. 右击Star-CCM快捷⽅式&#xff0c;选择“属性”&…