C++初阶教程——C++入门

一、本章主要内容

        C++在C的基础之上,加入了面向对象编程的思想,并增加了许多有用的库以及编程范式。可以说,C是C++的子集。在这章的内容中,笔者将会为诸位读者讲C++如何补充C语言的一些不足。比如:作用域、IO、函数、指针等。

二、C++关键字

        C++中总计有63个关键字,这里只是给读者看看C++中有哪些关键字,不进行具体讲解。以后再细讲。

asmdoifreturntrycontinue
autodoubleinlineshorttypedeffor
booldynamic_castintsignedtypiedpublic
breakelselongsizeoftypenamethrow
caseenummutablestaticunionwchar_t
catchexplicitnamesapcestatic_castunsigneddefault
charexportnewstructusingfriend
classexternoperatorswitchvirtualregister
constfalseprivatetemplatevoidtrue
const_castfloatprotectedthisvolatilewhile
deletegotoreinterpret_cast

三、命名空间

        在C/C++中,变量、函数以及以后要学的类都是大量存在的,如果将它们的名称都存放于全局作用域中,难免导致名称的冲突。为了解决这个问题,C++引入了命名空间将标识符的名称进行本地化,避免命名冲突和名字污染。

#include<stdio.h>
#include<stdlib.h>

int rand = 10;

int main()
{
    printf("%d",&rand);
    return 0;
}

       在上面这段代码中,如果不引入stdlib库,那么这段代码是可以运行的。但是stdlib库中存在一个rand函数,与这里用户定义的rand变量重名,发生了冲突。C语言是没办法解决这类命名冲突问题的,所以就有了namespace。

3.1命名空间定义

namespace namespace_name
{
    declarations
}

        其中,namespace_name是可自定义的命名空间名,declarations是声明的变量、函数和类的实体。

        正常的命名空间定义:

//正常的namespace定义
namespace my_namespace
{
	int rand = 10;
	int Mul(int a, int b)
	{
		return a - b;
	}
	struct Node
	{
		int val;
		struct Node* Next;
	};
}

        命名空间可以嵌套:

//命名空间的嵌套
namespace my_namespace
{
	int rand = 10;
	int Mul(int a, int b)
	{
		return a - b;
	}
	struct Node
	{
		int val;
		struct Node* Next;
	};
	namespace my_namespace2
	{
		int k = 1;
		int Add(int a, int b)
		{
			return a + b;
		}
	}
}

         同一个工程中,允许存在多个同名的命名空间,编译器最后会将它他们合并成一个命名空间。

3.2命名空间的使用

        命名空间定义了一个新的作用域,这个区域有自己的一套规则和实体(比如变量、函数、类等)。在这个命名空间中定义的所有内容,比如函数、类、变量等,都默认被限定在这个命名空间内,它们不会直接干扰到命名空间外的其他代码。

        在命名空间中定义的实体默认是私有的,这意味着它们只能在该命名空间内被访问。如果你想要让外部代码访问这些实体,有以下三种方式:

不开放的使用方式:加命名空间名称以及作用于限定符

int main()
{
    cout << my_namespace::rand;
    return 0;
}

半开放的使用方式:使用using将命名空间中的某个成员引入

using my_namespace::rand;
int main()
{
    cout << rand;
    return 0;
}

全开放的使用方式:使用using namespace将整个命名空间引入 

using namespace my_namespace;
int main()
{
    cout << rand;
    return 0;
}

         这三种方法,不开放和半开放方式使用起来可能会增加代码的冗余度,但从安全角度来看是值得的。将整个命名空间引入,在大型项目中是很有可能导致命名冲突的。

四、C++的输入与输出

        输入和输出(I/O)是通过流来处理的。C++标准库提供了两个主要的流对象:std::cin用于输入,std::cout用于输出。这些流对象与std::istream和std::ostream类相关联,分别用于从标准输入(通常是键盘)读取数据和向标准输出(通常是屏幕)发送数据。

输入(使用std::cin)

#include <iostream>

int main() {
    int number;
    std::cout << "输入一个整数";
    std::cin >> number; // 读取一个整数
    std::cout << "你输入的数为: " << number << std::endl;
    return 0;
}

        上面的代码中,std::cin会等待用于输入一个整数,直到按下回车键,输入的整数会被保存在变量number中。

输出(使用std::cout)

#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

       注意:

  1. cout和cin都是全局中的流对象,endl是特殊的C++符号,表示换行输出,它们都包含在头文件iostream中。
  2. <<是流插入运算符,>>是流提取运算符,这里体现了C++的一个重要特性——重载。<<本来作为移位运算符而存在,在这里有不同的含义。
  3. C++的输入输出更加方便,不需要手动控制格式,并且可以自动识别变量类型。
#include <iostream>
using namespace std;
int main()
{
	int a;
	double b;
	char c;

	// 可以自动识别变量的类型
	cin >> a;
	cin >> b >> c;

	cout << a << endl;
	cout << b << " " << c << endl;
	return 0;
}

五、缺省参数

         在C++中,函数的缺省参数(也称为默认参数)允许你在函数定义时为参数指定一个默认值。如果在调用函数时没有提供该参数的值,编译器会自动使用这个默认值。这使得函数可以处理不同数量的参数,同时保持向后兼容性。

#include <iostream>

// 函数声明,可以在这里指定缺省参数
void printMessage(const std::string& message, int times = 1);

int main() {
    printMessage("Hello, World!"); // 使用缺省参数
    printMessage("Hello, World!", 3); // 指定参数值
    return 0;
}

// 函数定义,指定缺省参数
void printMessage(const std::string& message, int times) {
    for (int i = 0; i < times; ++i) {
        std::cout << message << std::endl;
    }
}

        注意:

  1.  所有带有缺省值的参数必须位于参数列表的末尾。这是因为在调用函数时,如果省略了一些参数,编译器需要根据位置来确定哪些参数被省略,并且不能间隔着出现,必须依次给出。
  2. 如果函数在头文件中声明,并且在源文件中定义,那么缺省参数应该在函数定义中指定,而不是在声明中。这是因为在C++中,函数的声明通常不包含函数体,而缺省参数的值是在函数定义中指定的。
  3. 缺省参数并不创建函数的重载版本,如果两个函数除了缺省参数的值不同之外完全相同,它们将被视为同一个函数。

六、函数重载

        自然语言中,一个词可以有多重含义,人们可以通过上下文来判断这个词的真实含义,在C++中,我们可以说这个词被重载了。

6.1函数重载的定义

        函数重载是C++中一个重要的特性,它允许在相同的作用域内定义多个同名函数,只要这些函数的参数列表不同即可。参数列表不同可以是参数的类型不同、参数的数量不同,或者两者都不同。

#include <iostream>

// 重载函数print,一个参数
void print(int value) {
    std::cout << "Integer: " << value << std::endl;
}

// 重载函数print,两个参数
void print(double value) {
    std::cout << "Double: " << value << std::endl;
}

// 重载函数print,一个参数,参数是const引用
void print(const std::string& value) {
    std::cout << "String: " << value << std::endl;
}

int main() {
    print(10);        // 调用第一个print函数
    print(20.5);      // 调用第二个print函数
    print("Hello");   // 调用第三个print函数
    return 0;
}

        在这段代码中,print被重载了三次,每次都接受了不同的参数,当调用print函数时,编译器会根据传递参数的类型来决定调用对应版本的函数。

6.2为什么C语言不能实现重载

        重载是C++独立于C语言的一个特性,要讲为什么C++能够实现重载而C语言不能,笔者想要从一个可执行程序的诞生开始谈起。

        在C/C++中,一个程序想要运行起来,需要经过以下几个阶段:预处理、编译、汇编、链接。

预处理阶段(*.i)编译(*.s)汇编(生成可重定位文件*.o)链接
预处理指令

语法分析

词法分析

语义分析

符号汇总

形成符号表

汇编指令->二进制指令

合并段表

符号表的合并和符号表的重定位

  1. 实际项目由多个头文件和多个源文件构成,假设a.cpp调用了b.cpp中定义的函数时,在编译后链接前,a.o中并不知道这个函数的地址,因为这个函数是在b.cpp中定义的,所以它的地址在b.o中。
  2. 链接阶段就是用来处理上面这个问题的,链接器看到a.o调用该函数,但是没有这个函数的地址,就会到b.o的符号表中去找它的地址,然后链接到一起。
  3. 实际上,在链接阶段,链接器会根据编译器生成的符号(可以说是函数签名)来解析函数调用。这是C++能够重载而C语言不行的主要原因。
    1. 在C语言中,函数的签名只包括函数名。函数的参数类型和数量必须完全匹配,否则编译器会报错。C语言不支持基于参数类型或数量的函数重载。
    2. 在C++中,函数的签名包括函数名和参数列表(参数的类型、数量和顺序),但不包括返回类型。函数签名是区分不同重载函数的关键。即使两个函数的返回类型相同,只要它们的参数列表不同,它们就可以被重载。
  4. 另外,不同的编译器有不同的修饰规则,相当一部分规则还比较繁琐,了解即可。
     

七、引用

7.1引用的概念               

        在C++中,引用是一种特殊的变量,它本身不存储数据,而是另一个变量的别名。而且,定义它的时候必须进行初始化,也就是将他绑定到另一个变量上,并且不能再改绑到其它变量上。它与它引用的变量共用一块内存空间,主要是提供对变量的间接访问

int a = 10;
int& ref = a; // ref是a的引用

7.2引用的特性 

  1. 初始化:引用在声明时必须被初始化,它必须绑定到一个已经存在的变量上。

  2. 不可更改:一旦引用被绑定到一个变量上,它就不能被重新绑定到另一个变量上。

  3. 与原始变量共享内存:引用并不占用额外的内存,它和原始变量共享同一块内存空间。

  4. 别名:引用是原始变量的一个别名,所以对引用的操作直接影响原始变量。

7.3常量引用

        常量引用是指向常量的引用,它不允许通过引用改变所绑定变量的值。声明常量引用的方式是在类型后面加上const关键字,以及在变量名后面加上&符号。

const int a = 10;
int& ra = a;//错误,a为常量
cosnt int& ra = a;
int& b = 10;//错误,b为常量
cosnt int& b = 10;

7.4引用做函数参数 

        引用经常用作函数参数,以避免复制大型数据结构,同时允许函数修改实际传递的参数。

void swap(int& x, int& y) {
    int temp = x;
    x = y;
    y = temp;
}

int main() {
    int a = 10, b = 20;
    swap(a, b);
    // a 和 b 的值将被交换
    return 0;
}

       swap函数接受两个整数引用作为参数,并交换它们的值。由于参数是引用,所以函数内部对参数的修改将反映到实际传递的变量上。

7.5引用做返回值

int& Add(int a, int b)
{
    int c = a + b;
    return c;
}
int main()
{
    int& ret = Add(1, 2);
    Add(3, 4);
    cout << "Add(1, 2) is :"<< ret <<endl;
    return 0;
}

        这段代码的输出会是什么?这里的Add函数返回了一个变量c的引用,但是c是Add函数的局部变量,Add函数运行结束过后,该函数对应的栈空间就被回收了。在main函数中使用ret引用Add函数的返回值,实际应用的是一块已经被释放的空间。

        但是,只是Add函数的栈空间被回收了,并不是这块空间不能再使用,空间本身还在,这时,ret的值是不可控的。

        也就是说,如果函数返回时,离开作用域后,返回对象没有被回收,那就可以使用引用返回。反之,如果已经还给了系统,就必须使用传值返回。

7.6传值和传引用效率比较

        以值作为参数或者返回类型时,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当这个参数或者返回值非常大时,效率就更低了。

        传参效率比较:

#include<iostream>
#include <time.h>
using namespace std;

struct A { int a[10000]; };

void TestFunc1(A a) {}

void TestFunc2(A& a) {}

void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();
	return 0;
}

        做返回值效率比较

#include<iostream>
#include <time.h>
using namespace std;

struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }

void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
	TestReturnByRefOrValue();
	return 0;
}

         通过运行上述代码,可以发现传值和传指针在作为传参以及返回值类型上效率相差很大。

7.7引用和指针的区别

        在语法概念上,引用是一个别名,没有独立空间,和其引用实体共用一块空间。但在底层空间上实际是有空间的,因为引用是按照指针方式来实现的。

        引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。

  2. 引用在定义时必须初始化,指针没有要求。

  3. 引用在初始化一个实体之后,不能再引用其它实体,而指针可以在任何时候指向同一个类型的实体。

  4. 没有NULL引用,但有NULL指针。

  5. 在sizeof中含义不同,引用为应用类型的大小,但指针始终是地址空间所占字节数,与类型无关。

  6. 引用自增即引用实体自增1,而指针则偏移一个类型的大小。

  7. 存在多级指针,不存在多级引用。

  8. 引用更加安全。

八、内联函数

8.1宏

        下面是一个C/C++中的宏函数:

#define ADD(x,y) (x+y)

        对吗?对的,但不完全对。在某些情况下这种写法是正确的,但是编程语言中的运算符是有优先级的。

#include<iostream>
using namespace std;

#define ADD(x,y) (x+y)

int main()
{
    int a = 1;
    int b = 0;
    cout << ADD(1,2) << endl;
    cout << ADD(a|b,a&b) << endl;//这个函数展开是什么样的?
}

        在C/C++中,"+"的优先级高于"|"和"&"。所以这里的宏函数展开实际上是:

ADD(a|b,a&b) = a|(b+a)&b

         与函数的实际作用不同,下面才是宏函数的正确写法:

#define ADD(x,y) ((x)+(y))

        总的来讲,宏的优点是能够增强代码的复用性,提高性能。它的缺点是不方便调试(因为预编译阶段进行了替换),代码可读性差并且没有类型安全的检查。

8.2inline函数

        在C/C++,引入了一个内联函数的概念,用来取代宏。以inline关键字修饰的函数被称为内联函数,在编译时C++编译器会在调用内联函数的地方展开,这时不需要为函数调用建立栈帧的开销,提升了程序运行的效率。

        内联函数是一种空间换时间的做法,如果编译器将函数当作内联函数处理,在编译阶段会用函数体替换函数调用,这会导致目标文件变大,但优势时可以直接运算而不需要进行函数调用,从而减少了开销。

        inline关键字对于编译器仅仅是一个建议。一般来讲,当函数规模较小,非递归且被频繁调用时采用inline修饰。否则会忽略inline特性。

        inline不建议声明和定义分离,分离会导致连接错误。因为inline会被展开,那就不存在函数地址,链接的时候就会找不到函数。

九、auto关键字(C++11)

        考虑到C++的发展,以及命名空间的引入,变量的类型越来越复杂,常常体现在类型难以拼写并且含义不明确容易出错。

#include<iostream>
#include<vector>

using namespace std;

int main()
{
    vector<int> v;
    cout << typeid(v.begin()).name();
}

         输出为:

        在C++11中,auto关键字允许编译器自动推断变量的类型,这样可以减少代码的冗余,提高代码的可读性和可维护性。

auto x = 10;       // x 的类型被推断为 int
auto y = 3.14;     // y 的类型被推断为 double
auto z = "hello";  // z 的类型被推断为 const char[]

        还可以与const一起用保持原类型的属性:

const int ci = 5;
auto a = ci;  // a 的类型被推断为 const int

std::vector<int> v = {1, 2, 3};
auto it = v.begin();  // it 的类型被推断为 std::vector<int>::iterator

        auto作为函数返回类型:

auto add(int x, int y) {
    return x + y;
}  // add 的返回类型被推断为 int

auto lambda = [](int x, int y) {
    return x + y;
};  // lambda 的类型被推断为 int(int, int) -> int

        注意:

  1. auto定义变量时,必须对其初始化,在编译阶段需要根据初始化表达式来推导auto的实际类型。auto并非是一种类型的声明,而是一个临时的占位符,在编译器会将其替换为实际的类型。
  2. auto可以与指针和引用结合起来使用,用auto声明指针类型时,用auto和auto*没有区别,但用auto声明时必须加&。 
    int main()
    {
    	int x = 10;
    	auto a = &x;
    	auto* b = &x;
    	auto& c = x;
    	cout << typeid(a).name() << endl;
    	cout << typeid(b).name() << endl;
    	cout << typeid(c).name() << endl;
    	*a = 20;
    	*b = 30;
    	c = 40;
    	return 0;
    }

  3. 同一行定义多个变量时,这些变量必须是相同的类型,因为编译器只对第一个类型进行推倒。
    void TestAuto()
    {
        auto a = 1, b = 2;
        auto c = 3, d = 4.0;//编译会失败,c和d的初始化表达式类型不同
    }
  4. auto不能作为函数的参数并且不能用来声明数组。

十、基于范围的for循环(C++11)

        在C++98中,要遍历一个数组可以按照以下方式进行:

void TestFor()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
		array[i] *= 2;
	for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
		cout << *p << endl;
}

        在C++11中引入了基于范围的for循环。 

for (declaration : range_expression) {
    // 循环体
}

        declaration声明了一个变量,用于存储从range_expression获取的数据。

        range_expression:一个表达式,它定义了要遍历的范围,比如数组、容器或任何提供迭代器的序列。 

        遍历数组:

int arr[] = {1, 2, 3, 4, 5};
for (int num : arr) {
    std::cout << num << " ";
}
// 输出:1 2 3 4 5

        遍历容器:

#include <vector>
std::vector<std::string> vec = {"apple", "banana", "cherry"};
for (const std::string& fruit : vec) {
    std::cout << fruit << " ";
}
// 输出:apple banana cherry

 十一、指针控制nullptr(C++11)

        在C/C++中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错位。通常在声明一个指针时,如果没有一个合法的指向,那么通过如下的方式进行初始化。

void Testptr()
{
    int *p = NULL;
}

         但是,这里的NULL实际上是一个宏,在C头文件(stddef.h)中,可以看到:

#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

        所以,NULL可能被定义为字面常量0,或者无类型指针的常量,可能会出现下面的麻烦。

#include<iostream>
using namespace std;
void f(int)
{
	cout << "f(int)" << endl;
}
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(0);
	f(NULL);
	f((int*)NULL);
	return 0;
}

         程序本意通过f(NULL)调用f(int*)这个函数,但是由于NULL被定义为0,因此与程序的想法相悖。但是由于向前兼容等问题,C++没有修改这个问题,而是引入了nullptr这个关键字来表示指针空值。注意:

  1. 在使用nullptr标志指针空值时,不需要包含头文件,因为nullptr时作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr)与sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的稳定性,在以后表示指针空值时,最好使用nullptr。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Swift Macro 在业务开发中的探索与实践

简介 Swift Macro 在 Swift 5.9 版本中正式引入&#xff0c;且需配合 Xcode 15 使用。Swift Macro 作为一种新的设计方法&#xff0c;致力于帮开发者降低编写重复代码的繁琐&#xff0c;以更为简洁优雅的方式去实现。 在 OC 中&#xff0c;有大家熟知的宏 #define&#xff0c;…

Pseudo Multi-Camera Editing 数据集:通过常规视频生成的伪标记多摄像机推荐数据集,显著提升模型在未知领域的准确性。

2024-10-19&#xff0c;由伊利诺伊大学厄巴纳-香槟分校和香港城市大学的研究团队提出了一种创新方法&#xff0c;通过将常规视频转换成伪标记的多摄像机视角推荐数据集&#xff0c;有效解决了在未知领域中模型泛化能力差的问题。数据集的创建&#xff0c;为电影、电视和其他媒体…

练习LabVIEW第二十三题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第二十三题&#xff1a; 建立一个枚举控件&#xff0c;其内容为张三、李四、王五共三位先生&#xff0c;要求当枚举控件显…

Spring Boot 实现文件分片上传和下载

文章目录 一、原理分析1.1 文件分片1.2 断点续传和断点下载1.2 文件分片下载的 HTTP 参数 二、文件上传功能实现2.1 客户端(前端)2.2 服务端 三、文件下载功能实现3.1 客户端(前端)3.2 服务端 四、功能测试4.1 文件上传功能测试4.2 文件下载功能实现 参考资料 完整案例代码&…

分类预测|基于WOA鲸鱼优化K近邻KNN的数据分类预测Matlab程序 多特征输入多类别输出GWO-KNN

文章目录 一、基本原理原理流程总结 二、实验结果三、核心代码四、代码获取五、总结 一、基本原理 鲸鱼优化算法&#xff08;WOA&#xff0c;Whale Optimization Algorithm&#xff09;是一种模拟座头鲸捕猎行为的启发式优化算法&#xff0c;适用于解决各种优化问题。在K近邻&…

深度探索:超实用阿里云应用之低功耗模组AT开发示例

今天我们讲解一款低功耗4G全网通模组作为例子&#xff0c; 基于Air780EP模组AT开发的阿里云应用教程&#xff0c; 本文同样适用于以下型号&#xff1a; Air700ECQ/Air700EAQ/Air700EMQ Air780EQ/Air780EPA/Air780EPT/Air780EPS Air780E/Air780EX/Air724UG… 1、相关准备工作 …

大白话讲解分布式事务-SEATA事务四种模式(内含demo)

因为这里主要是讲解分布式事务&#xff0c;关于什么是事务&#xff0c;以及事务的特性&#xff0c;单个事务的使用方式&#xff0c;以及在Spring框架下&#xff0c;事务的传播方式&#xff0c;这里就不再赘述了。但是我这里要补充一点就是&#xff0c;一提到事务大家脑子里第一…

假如浙江与福建合并为“浙福省”

在中国&#xff0c;很多省份之间的关系颇有“渊源”&#xff0c;例如河南与河北、湖南与湖北、广东与广西等等&#xff0c;他们因一山或一湖之隔&#xff0c;地域相近、文化相通。 但有这么两个省份&#xff0c;省名没有共通之处&#xff0c;文化上也有诸多不同&#xff0c;但…

[简易版] 自动化脚本

前言 uniapp cli项目中没办法自动化打开微信开发者工具&#xff0c;需要手动打开比较繁琐&#xff0c;故此自动化脚本就诞生啦~ 实现 const spawn require("cross-spawn"); const chalk require("picocolors"); const dayjs require("dayjs&quo…

7.使用Redis进行秒杀优化

目录 1. 优化思路 总结之前实现的秒杀过程 下单流程 2. 使用Redis完成秒杀资格判断和库存 0. Redis中数据类型的选用 1.将优惠券信息保存到Redis中 2.基于Lua脚本&#xff0c;判断秒杀库存、一人一单&#xff0c;决定用户是否抢购成功 3. 开启新协程&#xff0c;处理数…

MongoDB-Plus

MongoDB-Plus是一款功能强大的数据库工具&#xff0c;它基于MongoDB&#xff0c;提供了更丰富的功能和更便捷的操作方式。以下是一篇关于MongoDB-Plus轻松上手的详细指南&#xff0c;旨在帮助初学者快速掌握其安装、配置和基础操作。 一、MongoDB-Plus概述 MongoDB是一款由C编…

鸿蒙next之导航组件跳转携带参数

官方文档推荐使用导航组件的形式进行页面管理&#xff0c;官方文档看了半天也没搞明白&#xff0c;查了各种文档才弄清楚。以下是具体实现方法&#xff1a; 在src/main/resources/base/profile下新建router_map.json文件 里边存放的是导航组件 {"routerMap" : [{&q…

鸿蒙API12 端云一体化开发——云函数篇

大家好&#xff0c;我是学徒小z&#xff0c;我们接着上次的端云一体化继续讲解&#xff0c;今天来说说云函数怎么创建和调用 文章目录 云函数1. 入口方法2. 编写云函数3. 进行云端测试4. 在本地端侧调用云函数5. 云函数传参6. 环境变量 云函数 1. 入口方法 在CloudProgram中…

软硬件开发面试问题大汇总篇——针对非常规八股问题的提问与应答(代码规范与生态管理)

软硬件开发&#xff0c;对于编码规范、生态管理等等综合问题的考察尤为重要。 阐述下环形缓冲区的用途 环形缓冲区&#xff08;Ring Buffer&#xff09;是一种固定大小的数据结构&#xff0c;常用于实现数据的流式传输或临时存储。在环形缓冲区中&#xff0c;当到达缓冲区的末尾…

Java Lock CyclicBarrier 总结

前言 相关系列 《Java & Lock & 目录》&#xff08;持续更新&#xff09;《Java & Lock & CyclicBarrier & 源码》&#xff08;学习过程/多有漏误/仅作参考/不再更新&#xff09;《Java & Lock & CyclicBarrier & 总结》&#xff08;学习总结…

什么是排列树?

一、排列树的定义 排列树就是一个能表示全排列的树形结构。全排列咱们都学过&#xff0c;就是所有可能的排列。 当问题的解是n个元素的某个排列时&#xff0c;其解空间&#xff08;全部可能解构成的集合&#xff09;就是n个元素的全排列&#xff0c;称为排列树。 以3个元素{…

1 环境配置、创建功能包、编译、Cmake文件及package文件学习笔记

1 基本结构 放张 赵虚左老师的pdf截图 2 环境配置 //每次都需配置 . install/setup.bash//或者一次配置echo "source /path/to/your/workspace_name/install/setup.bash" >> ~/.bashrcsource ~/.bashrc3 创建功能包 ros2 pkg create 包名--build-type 构建类…

ClickHouse 5节点集群安装

ClickHouse 5节点集群安装 在此架构中&#xff0c;配置了五台服务器。其中两个用于托管数据副本。其他三台服务器用于协调数据的复制。在此示例中&#xff0c;我们将创建一个数据库和表&#xff0c;将使用 ReplicatedMergeTree 表引擎在两个数据节点之间复制该数据库和表。 官…

简单易用的Android主线程耗时检测类 MainThreadMonitor

适用场景 debug 本地测试 文章目录 代码类 MainThreadMonitor.java使用方式 Application的attachBaseContextlog输出示例 代码类 MainThreadMonitor.java public class MainThreadMonitor {private static final String TAG "MainThreadMonitor";private static Sc…

uniapp的IOS证书申请(测试和正式环境)及UDID配置流程

1.说明 本教程只提供uniapp在ios端的证书文件申请&#xff08;包含正式环境和开发环境&#xff09;、UDID配置说明&#xff0c;请勿用文档中的账号和其他隐私数据进行测试&#xff0c;请勿侵权&#xff01; 2.申请前准备 证书生成网站&#xff1a;苹果应用上传、解析&#x…