2024_06_20

2024-06-20

C++总结

生成随机数

  • rand()和srand()

    • rand()会返回一随机数值, 范围在0至RAND_MAX 间。
    • srand()可用来设置rand()产生随机数时的随机数种子。
    • RAND_MAX定义在stdlib.h, 其值为2147483647。
    • 使用时需要引入头文件<stdlib.h>
    • rand函数不是线程安全的,多个线程同时调用rand可能获得同样的结果。
    • 代码:
      #include <cstdlib>
      #include <iostream>
      
      int main(int argc, char**argv)
      {
        srand(time(nullptr));
        //如果在应用程序中需要一个可重复的随机过程
        //使用固定的seed作为srand参数
        std::cout << "randmax: " << RAND_MAX << std::endl;
        for(int i = 0; i < 10; i++)
        {
          std::cout << " " << rand();
        }
        std::cout << std::endl;
        return 0;
      }
      
  • random(C++11)

    • random库主要由随机数引擎(Random number engines)和随机数分布(Random number distributions)组成,

    • 使用时需要引入头文件 #include

    • 使用随机数引擎生成一定范围内随机数,使用随机分布可以得到满足一定规律的分布(均匀分布,伯努利分布、泊松分布、正太分布、抽样分布)的随机数

    • 随机数模板类

      模板 作用
      linear_congruential_engine 实现线性同余算法
      速度适中
      mersenne_twister_engine 实现 Mersenne Twister 算法
      速度较慢, 具有最长非重复序列
      subtract_with_carry_engine 实现滞后斐波那契算法
      速度最快
    • std::random_device 是一个非确定性随机数生成器。通常被用作生成伪随机数器的种子。

    • 使用Mersenne twister算法生成随机数,随机效果最好:

      #include <iostream>
      #include <random>
      //std::mt19937
      /*定义
      std::mersenne_twister_engine<std::uint_fast32_t、32、624、397、31、
      0x9908b0df,11,
      0xffffffff,7,
      0x9d2c5680,15,
      0xefc60000,18,1812433253>
      */
      int main(int argc, char**argv)
      {
        std::random_device rd;
        unsigned int seed = rd();
        std::mt19937 mt_r(seed);
        for(int i = 0; i < 10; i++)
        {
          std::cout << " " << mt_r();
        }
        return 0;
      }
      
    • 随机数分布和更详细使用请参考: https://www.cnblogs.com/chen-pi/p/17866160.html

无符号整型 unsgined int

  • 一般不推荐使用无符号整型:
    • 将两个无符号整型相减,例如3-5等于-2,但是-2不能被无符号整型表示,结果为 4294967294。
    • 对无符号整型使用递减运算符(--)。那么当变量循环递减时,最终可能会发生于上述例子中同样的问题。
    • void doSomething(unsigned int x){}; 如果传入参数-1:
      实参 -1 被隐式转换成了一个无符号形参。-1 不在无符号整型的表示范围,因此必然被反转成一个非常大的数(可能是 4294967295)。然后你的程序就会出现问题。 更糟糕的是,并没有一种方法能够避免上述问题的发生。C++ 可以自由地转换有符号和无符号数,但是它并不会在转换时进行任何的范围检查以确保不发生溢出。
  • 推荐使用的情况:
    • 首先,无符号数在进行位运算时是推荐使用的。在一些确实需要反转行为的场合,无符号数也是有用的(例如一些加密算法和随机数生成)。
    • 其次,在有些时候无符号数是无法避免的(很多与数组进行索引有关)。我们会在有关数组的课程中进行详细介绍。在这些情况下,无符号数可以被转换为有符号数。

固定宽度整形和size_t

  • C99 定义了一组固定宽度整形(位于 stdint.h)来确保整型在不同的计算机体系结构下都具有相同的大小。

    名称 类型 范围 备注
    std::int8_t 1 byte signed -128 到 127 在大多数系统上被看做有符号字符处理,见下面备注。
    std::uint8_t 1 byte unsigned 0 到 255 在大多数系统上被看做无符号字符处理,见下面备注
    std::int16_t 2 byte signed -32,768 到 32,767
    std::uint16_t 2 byte unsigned 0 到 65,535
    std::int32_t 4 byte signed -2,147,483,648 到 2,147,483,647
    std::uint32_t 4 byte unsigned 0 到 4,294,967,295
    std::int64_t 8 byte signed -9,223,372,036,854,775,808 到 9,223,372,036,854,775, 807
    std::uint64_t 8 byte unsigned 0 到 18,446,744,073,709,551,615
  • C++ 在 C++11 中官方吸纳了这些固定宽度整型。通过包含 头文件就可以使用它们,它们被定义在 std 命名空间中。

    #include <cstdint> // for fixed-width integers
    #include <iostream>
    
    int main()
    {
        std::int16_t i{5};
        std::cout << i;
        return 0;
    }
    
  • 固定宽度整型通常来讲有两个缺陷:
    首先,固定宽度整型不能保证在所有的体系结构中都被定义了。它只存在于, They only exist on systems where there are fundamental types matching their widths and following a certain binary representation.如果体系结构不支持固定宽度整型,则你的程序是无法编译的。不过,大多数的现代体系结构都以 8/16/32/64-位的变量为标准,因此除非你需要将程序移植到某个极其特殊的大型机或嵌入式系统上,否则一般没有问题。
    其次,如果你使用了固定宽度整型,它的性能相较于使用更宽的类型可能或稍差(在同样的体系结构下)。例如,如果你需要一个大小确定为 32 位的整型,你可能会使用 std:: int32_t,但是你的 CPU 可能在处理 64 位整形时更快。不过,即便 CPU 能够更快的处理某种给定的类型,也不一定意味着程序的运行速度更快——现代程序更多受限于内存使用而不是 CPU 处理速度。使用更大的内存足迹(memory footprint)带来的性能损失可能会超过 CPU 对其进行的加速。不经实际测试是很难进行对比的。

  • 最佳实践:

    • 如果整型的大小不重要(例如存放的数总是在2字节有符号整型范围内),推荐使用 int 。例如,如果你要求用户输入年龄,或者进行10以内的计数(此时2字节范围总是够用的)。那么使用 int 就足够应对绝大多数场景了;
    • 如果需要存储数量值,且范围必须明确时,推荐使用std::int#_t;
    • 如果需要进行位运算或想要利用无符号数的翻转特性时,使用std::uint#_t。
  • 尽量避免:

    • 使用无符号类型保存数量值;
    • 使用 8 为固定宽度整型;
    • 使用速度或大小优先的固定宽度类型;
    • 任何与特定编译器相关的固定整型类型——例如 Visual Studio 定义的 __int8,__int16等。
  • size_t

    • sizeof()返回类型为 std::size_t
    • 和整型的大小可以改变一样, std::size_t 同样可能会改变。
    • std::size_t 只保证它为无符号,并且最小为16位,但是在大多数系统中,它的实际大小通常等于应用程序的地址宽度。即对于32位应用程序,std::size_t 通常为 32 位无符号整型,而对于 64 位程序,size_t通常为64位无符号整型。
    • size_t 被定义为要足够大能以便能表示对应系统能够创建的最大的对象(以字节计)的尺寸。例如,如果 std::size_t 是4个字节宽,则该系统能够创建的最大的对象不可能超过 4,294,967,295 字节,因为这是4字节无符号整型能够存放的最大值。不过,这只是对象尺寸的理论上限,实际上限取决于你使用的编译器。
    • 从定义上看,任何尺寸大于size_t能够表示的值的对象,都被看做是错误的(会导致编译错),因为此时size_t已经不能够正确表示该对象的大小(会发生无符号数翻转)。

类型转换和 static_cast

  • 隐式类型转换

  • int 类型被转换为 double 类型为安全转换,编译器不会警告。

  • double 类型被转换为 int 类型为可能丢失精度,编译器会警告。

  • 推荐使用括号初始化,括号初始化能够避免初始化值因为隐式类型转换而丢失信息。

  • 使用 static_cast 运算符进行显式类型转换
    显式类型转换允许我们明确地告诉编译器将一种类型的值转换为另外一种类型,程序员会为此操作负责(即如果转换导致了信息的丢失,算我们的错)。

    #include <iostream>
    
    int main()
    {
        char ch{ 97 }; // 97 是 ASCII 码中的 'a'
        std::cout << ch << " has value " << static_cast<int>(ch) << '\n'; // 将 ch 打印为 int
    
        return 0;
    }
    

const 和 constexpr 和 consteval

  • const
    编译时常量与运行时常量:

    int main()
    {
        const int x{ 3};           // x is a compile time constant
        const int z{ x + 1 };      // z is a compile time constant
    
        std::cout << "Enter a number: ";
        const int y{ getNumber() }; // y is a runtime constant
    
        std::cout << x + y;         // x + y is a runtime expression
    
        return 0;
    }
    
  • constexpr
    constexpr(“常量表达式”的缩写)变量只能是编译时常量。如果 constexpr 变量的初始化值不是常量表达式,编译器将出错。
    任何在初始化后值就不能被改变,且初始化值可以在编译时确定的变量,都必须声明为 constexpr。 任何在初始化后值就不能被改变,但是初始化值不能在编译时确定的变量,都应该声明为 const。

  • consteval (C++20)
    C++20 引入了 consteval 关键字,它可以指定函数必须在编译时求值,否则将会产生编译错误。这种函数被称为即时函数(immediate functions)。

    #include <iostream>
    
    consteval int greater(int x, int y) // 函数现在是 consteval 类型
    {
        return (x > y ? x : y);
    }
    
    int main()
    {
        constexpr int g { greater(5, 6) };            // ok: will evaluate at compile-time
        std::cout << greater(5, 6) << " is greater!"; // ok: will evaluate at compile-time
    
        int x{ 5 }; // not constexpr
        std::cout << greater(x, 6) << " is greater!"; // 错误: consteval 函数必须在编译时求值
    
        return 0;
    }
    

静态属性和静态方法

静态成员是类级别的,也就是它和类的地位等同,而普通成员是对象(实例)级别的.类级别的成员,应先于该类任何对象的存在而存在,所以类的静态成员会被该类所有的对象共享,因此不能在静态方法里面访问非静态元素但非静态方法可以访问类的静态成员及非静态成员

当我们需要在每次创建对象时进行实例化对象数量的计数,使用普通变量的话,由于普通变量为类中的局部变量,也就是说,每建立一次对象,对象中的局部变量都会被初始化并重新写入,而有了类中的静态变量,由于静态变量是类中的全局变量且不可被类外的对象访问,全局变量值在每次对象构建时不会被初始化,而是在原值的基础上进行累计,使用静态属性和静态方法,保证了重要参数的安全性

所以静态数据成员的用途之一是统计有多少个对象实际存在

举例:
比如现在需要统计一下有多少只活的动物,那么我们需要一个计数器数量:每诞生一只宠物,就给宠物计数器加上1,每死掉一只,就减去1,此时我们首先想到的是创建一个全局变量来充当这个计数器,但这么做的后果是程序中的任何代码都可以修改这个计数器,稍不小心就会在程序里留下一个难以查堵的漏洞

所以坚决不建议在非必要的时候声明全局变量,我们真正需要的是一个只有在创建或删除对象时候才允许访问的计数器

这个问题必须使用C++的静态属性和静态函数才能完美地得到解决,C++允许我们把一个或多个成员声明为属于某个类,而不是仅属于该类实例化出的某个对象这么做的好处在于程序员可以在没有创建任何对象的情况下调用有关的方法 而且 有关数据仍能在该类的所有对象(静态和非静态皆可)之间共享

  • 静态数据成员
    静态数据成员实际上是类域中的全局变量,且类的静态数据成员在类内只能声明,定义和初始化必须在类外
    因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的,所以静态成员不能在类内初始化

    • 在C++中,类的静态成员(static member)必须在类内声明,在类外初始化,像下面这样 :
    class A
    {  
    private:
        static int count ; // 类内声明一个static类的静态数据成员
    };
    int A::count = 0 ; // 类外初始化静态数据,注意,类外定义时不必再加static关键字说明其的静态属性!
    
    

    注意: 在C++中,类的静态常量成员(static const member)可以在类内声明并初始化,像下面这样 :

    class A
    {  
    private:
        static const int count = 0; // 静态常量成员可以在类内初始化
    };
    
    • const数据成员不能在类内定义时初始化,在类外初始化也不行,这是因为const数据成员的值必须在对象创建时就已经确定,而类内初始化通常发生在编译时,无法保证在对象创建时值已经确定,其通常通过构造函数初始化。
    • 静态数据成员被类的所有对象共享,包括该类的派生类对象,所以静态数据成员可以想象为类内的全局变量。
    • 所有的静态数据成员(const,static,static const)都必须在.cpp文件中定义 否则就会出现无法识别外部符号的错误.
  • 静态成员函数
    类的静态成员函数可以在没有定义任何对象前使用,即无须创建任何对象实例就可以使用此成员函数,举例如下:

    class A
    {  
    public:  
        static void B(); //建立一个静态成员函数
    }  
    A::B();//正确访问静态成员函数的方式 格式为classname::funcname();
    

参考:https://blog.csdn.net/Chroniccandy/article/details/108621102

posted @ 2024-06-20 17:27  nostalg1a  阅读(44)  评论(0)    收藏  举报