当前位置: 首页 > article >正文

【C++修炼】初识C++:命名空间、缺省参数、函数重载、引用、内联函数、指针空值


目录

一、命名空间

1.1 命名空间的定义

1.2 命名空间的使用

二、缺省参数

2.1 缺省参数的概念

2.2 缺省参数的分类

三、重载函数

四、引用

4.1 引用的概念

4.2 引用特性

4.3 常引用/const引用

4.4 使用场景

4.5 传值与传引用的效率对比

4.6 引用和指针的区别

五、内联函数

5.1 概念

5.2 特性

六、指针空值nullptr


一、命名空间

1.1 命名空间的定义

什么是命名空间?

命名空间其实是一个用于解决变量、函数、类等标识符名称冲突问题的概念。

// C语言代码示例
#include<stdio.h>
int x = 10;
int Add(int num1, int num2)
{
    return x + num1 + num2;
}
int main()
{
    int a = x + 1;
    int b = x - 1;
    int ret = Add(a, b);
    printf("%d\n", ret);
    return 0;
}

在C语言中,我们曾了解到作用域这一概念,如代码示例中的Add函数,全局变量x的作用域就是全局作用域,而局部变量a,b,ret的作用域则是局部作用域,仅限于主函数内使用。同一作用域内出现相同的标识符名称就会导致名称冲突的问题

例如,在实际工作中,一个项目通常由一个团队协作完成。每个人可能负责不同的文件,完成后最终合并到一起,那么在合并时就可能导致这种情况:一个团队成员写了一个Add函数解决某个问题,另一个团队成员也写了一个Add函数解决其他问题,两个函数的作用域都是全局作用域,那最终文件合并时就会发生冲突。在C中或许只能请这两名程序员一一校对修改函数名了,不过这样也太麻烦了。于是,C++在C的基础之上增加了命名空间的概念。

使用命名空间可以对标识符名称进行本地化,避免命名冲突或名称污染。

命名空间的定义需要用到关键字namespace,语法如下:

// example是命名空间的名称,一般在开发中是用项目名来命名空间名。
// 这里作为示例,随便选取的一个单词example作为空间名。
// 大家在编写自己的程序时,也可以用自己名字的缩写作为空间名,例如张三:zs
// 1. 正常的空间命名
namespace example
{
    // 自己的函数、变量、类型等
    int a = 10;
    int Add(int left, int right)
    {
        return left + right;
    }
    struct Node
    {
        struct Node* next;
        int val;
    };
}
// 2. 命名空间也可以嵌套
namespace example1
{
    int a;
    int b;
    int Add(int left, int right)
    {
        return left + right;
    }
    namespace exampleA
    {
        int c;
        int d;
        int Sub(int left, int right)
        {
            return left - right;
        }
    }
    namespace exampleB
    {
        int c;
        int d;
        int Mul(int left, int right)
        {
            return left * right;
        }
    }
}

一个命名空间定义一个新的作用域,命名空间中的所有内容都局限于该命名空间中

我们可以把命名空间看成一个人的脸或身份证,可能有很多人都叫张伟,但他们的脸和身份证号一定不同,因为每个张伟都有独属于他们的脸和身份证,而每张脸或身份证都来自于一个特定的人。命名空间的意义就在于此。

 那在同一个项目中,不同文件内定义的相同名称的命名空间是同一个命名空间么?

// 头文件test.h
namespace example
{
    int a = 10;
    int b = 20;
    int Add(int num1, int num2)
    {
        return num1 + num2;
    }
}

// 源文件test.cpp
namespace example
{
    int c = 30;
    int Sub(int num1, int num2)
    {
        return num1 - num2;
    }
}

同一个工程中是允许存在多个相同名称的命名空间的,编译器最后会合成为同一个命名空间。

例如上面代码中,test.h文件中有一个命名空间example,它与test.cpp中的example是同一个命名空间,编译时会合并到一起。

1.2 命名空间的使用

如何使用命名空间中的成员?

// 命名空间的使用有三种方法,分别是:
// 1.命名空间的名称 + 作用域限定符::
// 2.使用using引入空间成员
// 3.使用using引入命名空间

// 方法1:命名空间名称 + 作用域限定符::
namespace example
{
    int x = 10;
    int y = 100;
    int Add(int num1, int num2)
    {
        return num1 + num2;
    }
}
int main()
{
    // 输出命名空间example中的变量x的值
    cout << example::x << endl;
    // 调用example中的Add函数,并用example中的x,y作为形参
    int ret = example::Add(example::x, example::y);
    // 输出Add函数的返回值
    cout << ret << endl;
    return 0;
}

// 方法2:使用using引入命名空间的成员
namespace example
{
    int x = 10;
    int y = 100;
    int Add(int num1, int num2)
    {
        return num1 + num2;
    }
}
using example::x;
using example::y;
using example::Add;
int main()
{
    int ret = Add(x ,y);
    cout << ret << endl;
    return 0;
}

// 方法3:使用using引入命名空间
namespace example
{
    int x = 10;
    int y = 100;
    int Add(int num1, int num2)
    {
        return num1 + num2;
    }
}
using namespace example;
int main()
{
    int ret = Add(x ,y);
    cout << ret << endl;
    return 0;
}

三种方法各有优劣。对于我们日常的练习与小程序编写,使用第三种方法using namespace更便捷,但在项目开发时,代码较多,规模较大,就很容易产生冲突。这时,通常考虑前两种方法结合使用,对于频繁使用的变量或函数等就用第二种方式引入,而较少使用的变量或函数等就用第一种方式。

小拓展:

不知道大家见到的第一份C++代码是什么呢?是否曾为 using namespace std; 是什么而困惑过呢?

看了前面命名空间的介绍相信大家应该隐约有答案了吧。 std 其实是C++标准库中命名空间的名称,C++将标准库的定义实现都放到了这个命名空间中。所以当我们引用一些C++的头文件如iostream,想使用如cout,cin之类的库函数时,必须通过上面三种方式之一才能使用。

二、缺省参数

2.1 缺省参数的概念

缺省参数是声明或定义函数时为函数的参数指定缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

void Func(int a = 0)
{
    cout << a << endl;
}
int main()
{
    Func();// 没有传参,使用缺省值,即形参a = 0
    Func(10);// 传参10,则a = 10
    return 0;
}

2.2 缺省参数的分类

  • 全缺省参数

// 对函数的全部形参都分别指定一个缺省值即全缺省参数
void Func(int a = 10, int b = 20, int c = 30)
{
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;
}
  • 半缺省参数

// 对函数的部分形参分别指定一个缺省值即半缺省参数
void Func(int a, int b = 10, int c = 20)
{
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;
}

注意:

  1. 半缺省参数必须从右往左依次给缺省值,也不能间隔着给;
  2. 缺省参数不能在函数的声明和定义中同时出现;
  3. 缺省值必须是常量或者全局变量(通常是常量);
  4. C语言不支持(编译器不支持),缺省参数是C++在C的基础上新增的。

错误示例如下: 

// 1. 从左往右给缺省值,或间隔着给缺省值
void Func(int a = 0, int b = 10, int c)// 从左往右给
{
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;
}
void Func(int a = 0, int b, int c = 20)// 间隔着给
{
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;
}

// 2. 在函数的声明和定义中同时出现(只在声明中给是正确的,只在定义中给也是正确的)
// 两个文件,其中一个文件中不赋值即可
// 头文件test.h中
void Func(int a = 0, int b = 10, int c = 20);
// 源文件test.cpp中
void Func(int a = 0, int b = 10, int c = 20)
{
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
}

三、重载函数

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

例如:如虎添翼。

意思之一:比喻本领很大的人又增加了新的助力,作褒义词;

意思之二:比喻凶恶的人得到援助更加凶恶嚣张,作贬义词。

同样的字,作前者用可以是褒义,作后者却也可以变成贬义。

重载函数的意义与其类似,同一个函数名可以代表不同的函数,实现类似的功能

函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数类型类型顺序)不同,常用来处理函数实现类似但数据类型不同的问题(这也是函数重载的主要目的之一)

#include<iostream>
using namespace std;

// 1、参数类型不同
int Add(int left, int right)
{
    cout << "int Add(int left, int right)" << endl;
    return left + right;
}
double Add(double left, double right)
{
    cout << "double Add(double left, double right)" << endl;
    return left + right;
}

// 2、参数个数不同
void f()
{
    cout << "f()" << endl;
}
void f(int a)
{
    cout << "f(int a)" << endl;
}

// 3、参数类型顺序不同
void f(int a, char b)
{
    cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
    cout << "f(char b, int a)" << endl;
}

// 使用时编译器可以自动识别参数类型从而调用对应函数
int main()
{
    Add(10, 20);
    Add(10.1, 20.2);
    f();
    f(10);
    f(10, 'a');
    f('a', 10);
    return 0;
}

需要特别注意的是,重载函数只看形参列表,返回值及其类型无关紧要,例如:

// 这两个不是重载函数,同一作用域内会报错
int Add(int left, int right)
{
    cout << "int Add(int left, int right)" << endl;
    return left + right;
}
double Add(int left, int right)
{
    cout << "double Add(int left, int right)" << endl;
    return left + right;
}

// return不同也不是重载函数,同一作用域内会报错
int Add(int left, int right)
{
    left = left + right;
    cout << "int Add(int left, int right)" << endl;
    return left;
}
int* Add(int left, int right)
{
    left = left + right;
    cout << "int* Add(int left, int right)" << endl;
    return &left;// 这里返回的是形参left的地址,left变量出函数即销毁,但这里主要关注返回值,不必纠结使用错误
}

四、引用

4.1 引用的概念

引用:给已存在的变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间,而不是额外申请空间存储引用变量。

比如:李华在学校被老师叫作“小华”,被同学叫作“班长”,到了家里被父母叫作“华华”,这些名称都是李华的别名,且指向的实体都只有一个。

// 引用的使用:类型 + & + 引用变量名(对象名) = 引用实体;
void TestReference()
{
    // 定义整型变量a
    int a = 10;
    // 类型:int 引用变量名(对象名):ra 引用实体:a
    int& ra = a;
    cout << a << endl;// 打印结果:10
    cout << ra << endl;// 打印结果:10
    // 打印整型变量a与引用变量ra的地址,结果:地址相同
    cout << &a << endl;
    cout << &ra << endl;
}

注意:引用变量的类型必须和引用实体的类型一致,例如:int类型的变量a,它的引用变量ra也为int类型。

4.2 引用特性

  1. 引用在定义时必须初始化;
  2. 一个变量可以有多个引用;
  3. 引用一旦引用一个实体,就不能引用其他实体。
// 错误示例
// 1. 引用时不初始化
int main()
{
    int a = 10;
    int& ra;// 不初始化这一条语句就会报错
    ra = a;// 先定义再初始化是不行的
    return 0;
}
// 2. 引用其他实体
int main()
{
    int a = 10;
    int b = 20;
    int& ra = a;
    ra = b;// 引用变量不可以再改变引用实体,这一条语句的结果是将b的值也就是20赋值给a,而非引用b
    return 0;
}

// 正确示例——一个变量的多个引用
int main()
{
    int a = 10;
    int& ra = a;
    int& rra = a;
    int& rrra = a;// 可以无限引用,但没有意义
    return 0;
}

4.3 常引用/const引用

在C语言中,我们了解过一个叫作const的关键字。它的主要作用是用来修饰变量,经过const修饰的变量即为常变量。只要一个变量前用const来修饰,就意味着该变量里的数据只能被访问,而不能被修改,const会在编译期间,告诉编译器这个对象是不能被修改的,也就是意味着“只读”(readonly)。

const修饰的变量在引用时只能使用常引用/const引用,例如:

void TestConstRef()
{
    // 1. 使用普通引用变量来引用常变量——错
    const int a = 10;
    int& ra = a;// 该语句编译时会出错,a为常变量
    
    // 2. 使用常引用来引用常变量——对
    const int& ra = a;
    
    // 3. 使用常引用来引用普通变量——对
    int b = 20;
    const int& rb = b;
}

 对于上述场景,我们需要了解对象的访问权限这个概念。

当我们使用const来修饰变量,那么这个变量的访问权限就被更改成了只读,现在我们使用普通引用来引用这个变量是行不通的,为什么呢?因为权限放大了。被引用的只可以读,结果一个引用别人的第三方却又可以读又可以写,这明显不合理。因此,我们需要牢记:权限可以缩小,但不能放大

于是,我们可以知道,使用普通引用来引用常变量行不通,但使用常引用来引用常变量可行,因为权限一致;使用常引用来引用普通变量也可行,因为权限可以缩小

还有两种情况需要注意常引用问题: 


int main()
{
    // 1. 引用表达式
    int a = 10;
    int b = 20;
    int& rc = a * b * 3;// 不行!会报错

    // 2. 引用时发生强制类型转换
    double d = 10.24;
    int& rd = d; // 不行!会报错
    return 0;
}

我们来分析一下错误原因。实质上,不管是引用表达式求值的结果,还是引用过程需要强制类型转换,都会涉及到临时对象的概念。所谓临时对象其实是编译器在编译过程中,在对表达式进行求值或者对变量进行强制类型转换时,临时创建的一个未命名对象,用于暂时存储表达式的求值结果或存储强制类型转换过程中的中间值。

C++规定临时对象具有常性。因此,在上面的过程中触发了权限放大的问题,所以对于上面两种情况,正确做法应该是使用常引用。

4.4 使用场景

引用的使用场景主要分为:做函数参数做函数返回值

// 1. 做函数参数
void Swap(int& left, int& right)
{
    int tmp = left;
    left = right;
    right = tmp;
}
// 与指针做参数的效果一致
void Swap(int* left, int* right)
{
    int tmp = *left;
    *left = *right;
    *right = tmp;
}

// 2. 做函数返回值
int& Count()
{
    static int n = 0;
    n = 2 * n + 10;
    return n;
}
// 与指针做返回值的效果一致
int* Count()
{
    static int n = 0;
    n = 2 * n + 10;
    return &n;
}

 错误使用场景:

int& Add(int a, int b)
{
    int c = a + b;
    return c;
}
int main()
{
    int& ret = Add(1, 2);// ret是多少?
    Add(3, 4);// 再次调用,返回值是多少?
    cout << "Add(1, 2) is :"<< ret <<endl;// 打印结果是什么?
    return 0;
}

 经过C语言的学习,我们知道,对于函数中定义的局部变量,当函数结束时即销毁。引用变量ret根据定义,接收的实质是局部变量c所在空间存储的值。其实,如果第一次调用Add函数结束时,局部变量C的那块空间还没有更新,ret的值就还是a+b的值。但当第二次调用Add函数时,函数的地址已经不是原来的地址,再次创建的c变量也已经与第一次调用Add函数时创建的c变量没有关系了(不再是同一个地址)。且由于ret所指向的空间大概率已经被更新过了,因此最后打印的结果其实大概率是个随机值。

因此,在函数返回时,如果出了函数作用域后,返回对象还在(没返回给系统),则可以使用引用返回,如果已经返还给系统,则必须使用传值返回。

4.5 传值与传引用的效率对比

为什么要使用引用做函数参数或返回值呢?有什么好处呢?

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

我们来分别对比一下,值作参数和引用作参数的效率以及值作返回值和引用作返回值的效率: 

#include<iostream>
#include <time.h> // 引入clock函数,clock函数可以返回当前程序的运行时间
using namespace std;

struct A { int a[100000]; };// 结构体大小为40万字节

void TestFunc1(A a) {}// 传值
void TestFunc2(A& a) {}// 传引用
void TestRefAndValue()// 传值与传引用的效率对比
{
	struct A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 1000000; ++i)// 传值过程中拷贝一百万次a
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 1000000; ++i)// 传引用过程中使用同一个空间——a的空间
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "传值:" << end1 - begin1 << "ms" << endl;
	cout << "传引用:" << end2 - begin2 << "ms" <<endl;
}

// 创建全局变量struct A b
struct A b;
A TestFunc1() { return b; }// 值返回
A& TestFunc2() { return b; }// 引用返回
void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 1000000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 1000000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "值作返回值:" << end1 - begin1 << "ms" << endl;
	cout << "引用作返回值:" << end2 - begin2 << "ms" << endl;
}

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

运行结果: 

可以看到差距是十分明显的,尽管这个测试并不是很严谨,差距具体有多少我们并不能准确估量,但大致的量级我们还是有数的。因此,我们可以下结论:引用作为函数参数或返回值效率上优于值作为函数参数或返回值

4.6 引用和指针的区别

学习到这里,我们肯定有一个疑问,那就是引用和指针到底有什么区别?引用难道只是C++把C语言中的指针搬运过来然后随便改巴改巴,再起了个新名字么?

并不是。其实引用和指针的关系很像两个性格迥异的同胞兄弟,指针“年长”一些,引用“年幼”一些。但在实际生活中(实践中),无关长幼,他们互相帮助,相辅相成,各有特点且不可替代。

下面列出一些不同点,以示区分:

  1. 引用在概念上是对一个变量起的别名,与该变量共用一块空间,不额外开辟空间;指针在概念上是存储了一个变量的地址,需要额外开辟一块空间用于存储指针变量。
  2. 引用在定义时必须初始化;指针在定义时建议初始化,但在语法上并不是必须的。
  3. 引用在初始化时引用一个实体后,不能再引用其他实体;指针在定义后,可以多次更改指向的对象。
  4. 引用可以直接访问指向对象;指针需要解引用才能访问指向对象。
  5. 引用变量加1相当于引用实体加1;指针变量加1相当于指针的指向向后偏移一个类型的大小。
  6. 有多级指针,但没有多级引用。
  7. 没有空引用的概念,但有空指针的概念。
  8. sizeof中的含义不同:sizeof(引用变量)的计算结果是引用实体的类型大小;sizeof(指针变量)计算结果是地址所占字节数(例如:32位平台下地址长度为0x0000,需要占4个字节;64位平台下地址长度为0x00000000,需要占8个字节)。
  9. 指针容易出现使用混乱与野指针的问题,因此引用相比指针的使用更安全一些,出现问题的可能更少一些。

五、内联函数

5.1 概念

inline修饰的函数叫做内联函数,编译时C++编译器会在内联函数的调用地点展开,没有函数调用时建立栈帧空间的开销,可以提升程序运行的效率。

在调用地点展开是什么意思呢?和预编译过程有所类似。

在C语言的学习中,我们应该接触过宏的概念,例如宏:#define Add(x, y) ( (x) + (y) )

通常在预编译阶段,编译器会将#include#define等代码展开。使用#include包含的头文件中写了许多有关变量或函数声明之类的代码,预编译过程中相当于将头文件的代码原封不动的复制粘贴到预编译的源文件中,并替换掉#include<...>这句代码。同理,当你#define了一个常量,例如:#define a 10,那么编译器就会识别这个标识符a并用10进行代替。

而上面的宏其实是利用文本替换实现了函数的效果。例如,使用宏时语法和函数一致:Add(1, 2),但实际上却是:( (1) + (2) ),最终等于3,结果与使用函数可以得出的结果一致。使用宏相比函数的优势是不用为宏建立空间,也不需要传参,只是文本替换,且效率更高。缺点是设计复杂,不安全,无法调试。

因此,“在调用地点展开”我们可以看作类似预编译的过程,但它实际发生在编译过程。内联函数实际上也是C++对C语言的宏的一种改良。

一般函数及内联函数调用过程对比如下:

可以看到,左边在函数前加了关键字inline,并没有出现call指令,这意味着函数并没有建立栈帧空间,其本质只是函数调用语句被函数体替换,和宏类似。 

5.2 特性

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。缺陷:可能会使目标文件变大,优势:没有调用开销,提高程序运行效率。
  2. inline修饰的函数的展开申请对于编译器而言只是一个建议,不同编译器关于inline的实现机制可能不同。使用建议:将规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、流程直接不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
  3. inline不建议声明和定义分离。因为inline被展开后就没有函数地址了,链接时会发生链接错误。

六、指针空值nullptr

一个良好的C/C++编程习惯,通常会在声明一个变量时给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

void TestPtr()
{
    // 0既可以代表整型数字,也可以代表空指针
    int x = 0;
    int* p1 = NULL;
    int* p2 = 0;
    // ……
}

NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

// #ifndef #endif #ifdef #else都是条件编译指令
#ifndef NULL            // 如果遇到标识符NULL
#ifdef __cplusplus      // 判断该文件是否是C++文件
#define NULL 0          // 如果是C++文件,则NULL替换为0
#else                   // 如果不是
#define NULL ((void*)0) // 则NULL替换为无类型(void*)指针的常量,相当于地址为0
#endif
#endif

不论是替换为0,还是替换为指针,都不可避免会遇到一些麻烦,例如: 

// 重载函数
void f(int)                // 函数A
{
    cout<<"f(int)"<<endl;
}
void f(int*)               // 函数B
{
    cout<<"f(int*)"<<endl;
}
int main()
{
    // 编译器如何识别并调用相应重载函数呢?
    f(0);           // 成功调用函数A
    f(NULL);        // NULL调用谁?
    f((int*)NULL);  // 成功调用函数B
    return 0;
}

运行结果:

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。

我们知道,C++到现在经历过许多次版本更新,而在早期版本C++98中,字符常量0既可以是一个整型数字,也可以是无类型(void*)的指针常量,但是编译器默认情况下将其看成是一个整型常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0

因此在C++11中,为了解决0同时代表两个含义所造成的指针空值问题,引入了新关键字nullptr

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是关键字;
  2. sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同;
  3. 为了提高代码的健壮性,在后续表示指针空值时建议舍弃NULL的用法,使用nullptr。


http://www.kler.cn/news/364875.html

相关文章:

  • qt QSaveFile详解
  • 大家都在用的HR招聘管理工具:国内Top5排名
  • 【Javaee】网络原理—TCP协议的核心机制
  • 【32】C++流
  • php如何对海量数据进行基数统计
  • Android GPU Inspector分析帧数据快速入门
  • java编译[WARNING]告警处理
  • OpenCV KeyPoint与描述子编解码
  • 搭建你的第一个Spring Cloud Alibaba微服务
  • Java | Leetcode Java题解之第492题构造矩形
  • 论文引用收录证明有什么用?
  • vue2之混入(mixin)
  • python的接口自动化的测试与实现java监测jsp源代码Mysql
  • 【React系列二】—React学习历程的分享
  • 2024 JavaScript 入门教程:语法、算法与重要知识点详解
  • 查看linux的版本
  • 电影评论网站开发:Spring Boot技术指南
  • K最近邻算法
  • TinTin Web3 动态精选:Vitalik 探讨以太坊协议,Solana ETN 开启质押功能
  • 数造科技荣获2024DAMA中国“数据治理创新奖”
  • Lua repeat-until循环
  • UEFI BIOSAPP编程开发查询手册.pdf
  • 最后一周征稿!第四届计算机、物联网与控制工程国际学术会议(CITCE 2024)
  • 中国游戏产业趋势是什么?附2024趋势以及潜力分析报告
  • 如何使用JMeter进行性能测试的保姆级教程
  • Mac安装 TIDB并启动集群