android直接方法和虚方法,Android NDK入门:C++ 基础知识

2023-11-15

ea1be023d41c

为什么写这篇文章

本文算作是 《Android 音视频开发打怪升级》系列文章的“番外”篇,原本打算将本文的内容写在 《Android FFmpeg视频解码播放》 这篇文章中,因为要想学习 FFmpeg 相关知识,C++ 的基础知识是必不可少的。

但是写着写着发现,篇幅还是太长了,加上有部分小伙伴对 C++ 可能也比较熟悉,所以把此节独立成篇,更有利于不熟悉 C++ 的小伙伴学习查看,熟悉的小伙伴也可以直接跳过。

C++ 相对于 Java 还是有许多的不同之处,对于没有使用过 C++ 的人来说,如果要学习 NDK 开发,C++ 是第一道坎,必须要掌握。

本文通过对比的方式,把 C++ 和 Java 之间最基础,也是最常使用知识的异同标记出来,方便大家学习。

当然了,本文只是重点对 C++ 中最常用的,也是重点的知识进行讲解,如有时间,最好还是系统地学一下相关的基础知识。

本文你可以了解到

本文使用对比的方式,将 C++ 与我们非常熟悉的 Java 进行对比学习,介绍 C++ 与 Java 使用的异同,帮助大家快速入门 C++ 。

一、 C++ 基本数据类型

C++ 提供了一下几种基础数据类型

类型

关键字

布尔型

bool

字符型

char

整型

int

浮点型

float

双浮点型

double

无类型

void

同时,这些类型还可以被类型修饰符修饰,拓展出更多的数据类型:

类型修饰符

关键字

有符号类型

signed

无符号类型

unsigned

短类型

short

长类型

long

其中 signed 和 unsigned 指定了数据是否有正负; short 和 long 主要指定了数据的内存大小。

由于不同的系统,同个数据类型所占用的内存大小也不一定是一样的,以下是典型值:

类型

内存大小

范围

char

1 个字节

-128到127 或 0到255

unsigned char

1 个字节

0 到 255

signed char

1 个字节

-128 到 127

int

4 个字节

-2147483648 到 2147483647

unsigned int

4 个字节

0 到 4294967295

signed int

4 个字节

-2147483648 到 2147483647

short int

2 个字节

-32768 到 32767

unsigned short int

2 个字节

0 到 65,535

signed short int

2 个字节

-32768 到 32767

long int

8 个字节

-xxx 到 xxx

signed long int

8 个字节

-xxx 到 xxx

unsigned long int

8 个字节

-xxx 到 xxx

float

4 个字节

-xxx 到 xxx

double

8 个字节

-xxx 到 xxx

long double

16 个字节

-xxx 到 xxx

可以看到,

short 修饰符将原类型内存大小减小一半;

long 修饰符将原数据类型内存大小扩大一倍。

二、C++ 类

C++ 是一门面向对象的语言,类是必不可少的。其类的定义与 Java 大同小异。

Java 类通常声明和定义通常都是在同一个文件 xxx.java 中。

而 C++ 类的声明和定义通常是分开在两个不同的文件中,分别是 .h 头文件 和 .cpp 文件

定义一个类

一个 类的头文件 通常如下:

// A.h

class A

{

private: //私有属性

int a;

void f1();

protected: //子类可见

int b;

void f2(int i);

public: //公开属性

int c = 2;

int f3(int j);

A(int a, int b); // 构造函数

~A(); //析构函数

};

对应的类实现文件 A.cpp如下:

// A.cpp

/**

* 实现构造函数

*/

A::A(int a, int b):

a(a),

b(b) {

}

// 等价于

/*

A::A(int a, int b) {

this.a = a;

this.b = b;

}

*/

/**

* 实现析构函数

*/

A::~A() {

}

/**

* 实现 f1 方法

*/

void A::f1() {

}

/**

* 实现 f2 方法

*/

void A::f2(int j) {

this.b = j

}

/**

* 实现 f3 方法

*/

int A::f3(int j) {

this.c = j

}

可以看到,.h 文件主要负责类成员变量和方法的声明; .cpp 文件主要负责成员变量和方法的定义。

但是,并非一定要按照这样的结构去实现类,你也可以在 .h 头文件中直接定义变量和方法。

比如:

// A.h

class A {

private:

int a = 1;

public:

void f1(int i) {

this.a = i;

}

}

C++ 类中几个特别的地方

1) 可见性 private、protected、public

这几个关键字和 Java 是一样的,只不过在 C++ 中,通常不会对每个成员变量和方法进行可见性声明,而是将不同的可见性的变量和方法集中在一起,统一声明,具体见上面定义的类A。

2) 构造函数和析构函数

C++ 中类的构造函数和 Java 基本一致,只不过,在实现构造函数时,对成员变量的初始化方式比较特别。如下:

A::A(int a, int b):

a(a),

b(b) {

}

// 等价于

A::A(int a, int b) {

this.a = a;

this.b = b;

}

以上两种方式都可以,通常使用第一种方式。

析构函数 则是 Java 中没有的。通过波浪符号 ~ 进行标记。

它和构造函数一样,都是由系统自动调用,只不过,构造函数 在类创建的时候调用,析构函数 在类被删除的时候调用,主要用于释放内部变量和内存。

析构函数的声明形式为 ~类名();

实现的形式为 类名::~类名() { }

具体见上面类 A 的写法。

3) :: 双冒号

看了上面类的定义,肯定会对 :: 这个符号感到很神奇。这是 C++ 中的 域作用符,用于标示变量和方法是属于哪个域的,比如上面的

void A::a() { }

说明 方法a 是属于 类A 的。

也可以用于调用类的静态成员变量,如

//A.h

class A {

private:

static int a = 1;

int b;

void a();

}

//A.cpp

void A::a() {

b = A::a;

}

类的继承

C++ 类的继承和 Java 也是大同小异,其格式如下:

class B: access-specifier A,其中 access-specifier 是访问修饰符, 是 public、protected 或 private 其中的一个。

访问修饰符的作用如下:

公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。

保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。

私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。

通常情况下,我们都是使用 公有继承(public),也就是和 Java 是一样的。

类可以多继承

Java 中,子类只能继承一个父类,但是 C++ 可以继承自多个父类,使用逗号 , 隔开:

class :,,…

{

};

三、 C++ 指针

Java 中的 “指针”

Java 中,是没有指针的概念的,但是其实 Java 中除了基本数据类,大部分情况下使用都是 指针。

比如下面这段 Java 代码:

People p1 = new People("David","0001");

People p2 = p1;

p2.setName("Denny");

System.out.println(p1.getName());

// 输出结果为:Denny

原因就是 p1 和 p2 都是对对象的引用,在完成赋值语句 People p2 = p1; 后, p2 和 p1 指向同一个存储空间,所以对于p2的修改也影响到了p1。

那么,为什么在 Java 中很少去关注指针呢?

因为 Java 已经将指针封装了,也不允许显式地去操作指针,并且 Java 中的内存都由虚拟机进行管理,无需我们去释放申请的内存。

C++ 中的指针

1) 指针的声明和定义

与 Java 不同的是,C++ 中的指针概念非常重要,并且无处不在。

指针:是一个变量,这个变量的值是另一个变量的内存地址。也就是说,指针是一个指向内存地址的变量。

指针的声明和定义方法如下:

int a = 1; // 实际变量的声明

int *p; // 指针变量的声明

p = &a; // 指针指向 a 的内存地址

printf("p 指向的地址: %d, p指向的地址存储的内容: %d\n", p, *p);

// 输出如下:

// p 指向的地址: -1730170860, p指向的地址存储的内容: 1

这个例子中有两个很重要的符号: * 、&。其中:

* :有两个作用:

i. 用于定义一个指针: type *var_name; ,var_name 是一个指针变量,如 int *p;

ii. 用于对一个指针取内容: *var_name, 如 *p 的值是 1。

& :是一个取址符号

其用于获取一个变量所在的内存地址。如 &a; 的值是 a 所在内存的位置,即 a 的地址。

通过上面的例子,可能无法很好的理解指针的用处,来看另一个例子。

class A {

public:

int i;

};

int main() {

//-----1-------

A a = A(); // 定变量 a

a.i = 1; // 修改 a 中的变量

A b = a; // 定义变量 b ,赋值为 a

A *c = &a; // 定义指针 c,指向 a

printf("%d, %d, %d\n", a.i, b.i, c->i);

// 输出:1, 1, 1

//-----2-------

b.i = 2; //修改 b 中的变量

printf("%d, %d, %d\n", a.i, b.i, c->i);

// 输出:1, 2, 1

//-----3-------

c->i = 3; //修改 c 中的变量

printf("%d, %d, %d\n", a.i, b.i, c->i);

// 输出:3, 2, 3

//-----4-------

// 打印地址

printf("%d, %d, %d\n", &a, &b, c);

// 输出:-1861360224, -1861360208, -1861360224

return 0;

}

上面的例子,定义了一个变量 a ,然后将 a 分别赋值给普通变量 b 和指针变量 c。

第一次,打印三个变量中的成员变量的 i 的值都为 1;

第二次,修改了 b 中的 i,结果只修改了 b 的值,对 a 和 c 都没有影响;

第三次,修改了 c 中的 i,结果修改了 a 和 c 的值,对 b 都没有影响;

最后,打印了三个变量的地址,可以发现 a 和 c 的值是一样的,b 的地址不一样。

从这个例子就可以看出端倪了:

通过 普通变量 赋值的时候,系统创建了一个新的独立的内存块,如 b,对 b 的修改,只影响其本身;

通过 指针变量 赋值时,系统没有创建新的内存块,而是将指针指向了已存在的内存块,如 c , 任何对 c 的修改,都将影响原来的变量,如 a。

还有一点需要注意的是,指针变量 对成员变量的引用,使用的是箭头符号 ->,如 c->i ;普通变量对成员变量的引用,使用的是点符号 . ,如 b.i 。

2) new 和 delete

在上面的例子中,是通过创建了一个变量 a ,然后将 指针变量 c 指向了 a 的方式定义了 c。还有另外一种方法,可以声明和定义一个指针变量,那就是通过 new 动态创建。

class A {

public:

int i;

}

int main() {

A *a = new A();

a->i = 0;

printf("%d\n", a->i);

// 输出: 0

// 删除指针变量,回收内存

delete a;

return 0;

}

这就是动态创建指针变量的方式,这是 C++ 常用的方式。

重要提醒:

要注意的是,通过 new 的方式创建的指针变量和不通过 new 创建的变量最大的区别在于:通过 new 创建的指针需要我们自己手动回收内存,否则将会导致内存泄漏。回收内存则是通过 delete 关键字进行的。

也就是说,new 和 delete 必须要成对调用。

int main() {

A a = A(); // 无new,main 函数结束后,系统会自动回收内存

A *b = new A(); // new 方式创建,系统不会自动回收内存,要手动 delete

delete b; // 手动删除,回收内存

return 0;

}

可以看到,C++ 的指针变量其实更接近与 Java 中普通变量的使用方式。

四、C++ 引用

引用 是除了指针外,另一个非常重要的概念。在 C++ 也是经常使用的。

引用指的是:为一个变量起一个别名,也就是说,它是某个已存在变量的另一个名字。

引用和指针非常的相似,初学者非常容易把这两者混淆了。

引用的声明和定义

首先来看下如何声明一个引用变量。

// 声明一个普通变量 i

int i = 0;

// 声明定义一个引用 j

int &j = i;

j = 1;

printf("%d, %d\n", i, j)

// 输出:1, 1

是不是有点熟悉,又是与符号 & ,但是这里并非表示取址,这里只是作为一个标示符号。

请记住,千万不要和取址符号混淆,取址表示方式是:A *p = &a;

在上面的例子中,修改了 j 的值,i 的值也发生了变化。这和指针是不是非常像?

那么,引用和指针有什么不一样呢?

i. 不存在空引用。引用必须连接到一块合法的内存。

ii. 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。

iii. 引用必须在创建时被初始化。指针可以在任何时间被初始化。

i 和 iii 都很好理解,就是声明引用的时候,必须要初始化好,并且不能初始化为空 NULL 。

ii 是最让人不理解的,什么叫做 “不能被指向到另一个对象” ?

引用和指针的区别

看以下的例子:

int i = 0;

// 定义引用 j ,指向 i

int &j = i;

int k = 1;

// 这个操作是指向另外一个对象吗?

j = k;

printf("%d, %d, %d\n", i, j, k);

// 输出:1, 1, 1

// 打印地址

printf("%d, %d, %d\n", &i, &j, &k);

// 输出:-977299952, -977299952, -977299948

可以看到,i j k 三个的值都变成了 1,这看起来和指针是一样的效果,但却有质的区别。

看最后一个打印输出,i 和 j 的地址始终是一样的,和 k 是不一样的。也就是说, j 始终指向 i ,不可改变。 j = k 只是把 k 的值给到了 j,同时也改变了 i 。

如果还不懂,再来看一下指针的例子,你就明白了。

int i = 0;

// 定义指针 j ,指向 i

int *j = &i;

int k = 1;

// 指向另一个对象

j = &k;

printf("%d, %d, %d\n", i, *j, k);

// 输出:0, 1, 1

// 打印地址

printf("%d, %d, %d\n", &i, j, &k);

// 输出:-1790365184, -1790365180, -1790365180

看到了吗? j 在赋值了 &k 以后,地址就变成和 k 一样了,也就是说,指针 j 可以指向不同的对象。这时候, j 和 i 就没有任何关系了,i 的值也不会随着 j 改变而改变。

如何使用引用

引用最常出现的地方是作为函数的参数使用。

void change(int &i, int &j) {

int temp = i;

i = j;

j = temp;

}

int main() {

int i = 0;

int j = 1;

// 打印地址

printf("[before: %d, %d]\n", &i, &j);

//输出:[before: -224237816, -224237812]

change(i, j);

printf("[i: %d, j: %d]\n", i, j);

// 输出:i: 1, j: 0

// 打印地址

printf("[after: %d, %d]\n", &i, &j);

// 输出:after: -224237816, -224237812

return 0;

}

在上面的例子中,change 方法的两个参数都是引用,和普通的参数有以下两个区别:

i. 引用参数不会创建新的内存块,参数只是对外部传进来的变量的一个引用。

ii. 引用参数可以改变外部变量的值。

这是普通变量的情况:

void change(int i, int j) {

int temp = i;

i = j;

j = temp;

// 打印地址

pritf("[change: %d, %d]\n", &i, &j);

// 输出[change: -1136723044, -1136723048]

}

int main() {

int i = 0;

int j = 1;

// 打印地址

printf("[before: %d, %d]\n", &i, &j);

//输出:[before: -224237816, -224237812]

change(i, j);

printf("[i: %d, j: %d]\n", i, j);

// 输出:i: 0, j: 1

// 打印地址

printf("[after: %d, %d]\n", &i, &j);

// 输出:after: -224237816, -224237812

return 0;

}

可以看到,i j 的值不会被改不变,原因是 change 方法创建了两个临时的局部变量,都有自己的内存块,这个变量的地址和外部传进来的变量是没有关系的,所以无法改变外部变量的值。

到这里,就可以看到参数引用的好处了:引用参数为我们节省了内存,执行效率也更快。

同样的,指针参数也有类似的效果,但是其仍然和引用有着本质的区别。引用为我们提供另一个种很好的传参选择。

有时候,我们并不想让函数内部改变外部变量的值,可以给参数加上常量的标志。

void change(const int &i, const int &j) {

int temp = i;

i = j; // 不允许修改i,编译出错

j = temp; // 不允许修改j,编译出错

}

五、C++ 多态和虚函数

多态 是面向对象的三大特点之一。

C++ 的多态和 Java 非常相似,但是也有着明显的不同。

静态绑定

看下面一个例子:

class A {

public:

void f() {

printf("a\n");

};

};

class B : public A {

public:

void f() {

printf("b\n");

};

};

int main() {

A *a = new B();

a->f();

// 输出:a

return 0;

}

这里 B 继承了 A,并重写了方法 f 。

在 main 函数中,定义了一个基类变量指针 a ,并指向子类 B 。接着调用了 a 的方法 f。

如果是 Java 中类似的操作的话,那么毫无疑问,此处会输出 b,可是这里却输出了 a 。也就是说,这里方法 f 实际上是基类 A 的 f 方法。

这就是 C++ 和 Java 其中一个很大的不同。

原因是,调用函数 f() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接。

函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 f() 函数在程序编译期间就已经设置好了。

那么如果想实现类似 Java 中的多态重载呢?

虚函数

virtual 是 C++ 中的一个关键字,用于声明函数,表示虚函数。用于告诉编译器不要静态链接到该函数,改为动态链接。

依然是上面的例子,在 A 的 f 函数上加上 virtual,将得到类似 Java 的效果:

class A {

public:

virtual void f() {

printf("a\n");

};

};

class B : public A {

public:

void f() {

printf("b\n");

};

};

int main() {

A *a = new B();

a->f();

// 输出:b

return 0;

}

纯虚函数

在 Java 中,我们经常会使用 interface 或 abstract 来定义一些接口,方便代码规范和拓展,但是在 C++ 没有这样的方法,但是可以有类似的实现,那就是:纯虚函数。

class A {

public:

// 声明一个纯虚函数

virtual void f() = 0;

}

class B : public A {

public:

// 子类必须实现 f ,否则编译不通过

void f() {

printf("b\n");

};

};

int main() {

A *a = new B();

a->f();

// 输出:b

return 0;

}

A 中的 virtual void f() = 0; 就是一个纯虚函数。如果继承 A,子类必须实现 f 这个接口,否则编译不通过。

A 则是一个抽象类。不能被直接定义使用。

六、C++ 预处理

在 C++ 中有一个方法,可以让我们在程序编译前,对代码做一些处理,称为预处理。这是 Java 中没有的,在 C++ 中却经常使用到。

预处理是一些指令,但是这些指令并不是 C++ 语句,所以不需要以分号 ; 结束。

所有的预处理语句都是以井号 # 开始的。

比如 #include 就是一个预处理,用于将其他文件导入到一个另一个文件中,类似 Java 的 import 。

例如导入头文件:

// A.h

class A{

public:

A();

~A();

}

#include "A.h"

A::A() {

}

A::~A() {

}

在 C++ 中常用的预处理有以下几个 #include、 #define 、#if、#else 、 #ifdef 、 #endif 等。

宏定义

最常用的一个预处理语句 #define ,通常称为宏定义。

其形式为:

#define name replacement-text

#define PI 3.14159

printf("PI = %f", PI);

// 在编译之前,上面的语句被展开为:

// printf("PI = %f", 3.14159);

带参数宏定义

#define SUM(a,b) (a + b)

printf("a + b = %d", SUM(1, 2));

// 在编译之前,上面的语句被展开为:

// printf("a + b = %d", 1 + 2);

// 输出:a + b = 3

# 和 ## 运算符

在宏定义中,# 用于将参数 字符串化。

#define MKSTR( x ) #x

printf(MKSTR(Hello C++));

// 在编译之前,上面的语句被展开为:

// printf("Hello C++");

// 输出: Hello C++

在宏定义中,## 用于将参数 连接起来。

#define CONCAT(a, b) a ## b

int xy = 100;

printf("xy = %d", CONCAT(x, y));

// 在编译之前,上面的语句被展开为:

// printf("xy = %d", xy);

// 输出:xy = 100

注意:# 、 ## 在多个宏定义嵌套使用的时候,会导致不展开的问题

例如:

#define CONCAT(x, y) x ## y

#define A a

#define B b

void mian() {

char *ab = "ab";

char *AB = "AB";

printf("AB = %s", CONCAT(A, B));

// 在编译之前,上面的语句被展开为:

// printf("AB = %s", AB);

}

虽然定义了 A B 两个宏定义,但是在 CONCAT 中遇到 ## 的时候,A B 这两个宏定义是不会开展的,而是直接当作两个参数被连接起来了。

那么要如何解决这个问题呢?那就是再转接一层。

#define _CONCAT(x, y) x ## y

#define CONCAT(x, y) _CONCAT(x, y)

#define A a

#define B b

void mian() {

char *ab = "ab";

char *AB = "AB";

printf("AB = %s", CONCAT(A, B));

// 在编译之前,上面的语句被展开为:

// printf("AB = %s", _CONCAT(a, b));

// printf("AB = %s", ab);

// 输出:AB = ab

}

条件编译

#if、#else、 #ifdef、 #endif 这几个的组合主要用条件编译。

在 C++ 中条件编译也是经常使用到的,可以用来控制哪些代码参与编译,哪些不参与编译。

#define DEBUG

int main() {

#ifdef DEBUG

// 参与编译

printf("I am DEBUG\n");

#else

// 不参与编译

printf("No DEBUG\n");

#endif

return 0;

}

// 输出:I am DEBUG

以上代码,由于先前已经定义了 #define DEBUG 所以 #ifdef DEBUG 为 true ,编译 printf("I am DEBUG\n"); 。

如果去掉 #define DEBUG ,则编译 printf("No DEBUG\n"); 。

int main() {

#if 0

// 这里面的代码都被注释掉,不参与编译

printf("I am not compiled\n");

#endif

return 0;

}

七、总结

以上,基本就是在 C++ 经常使用到的,与 Java 相似,又存在差异的一些基础知识,由于面向对象语言都存在一定的相似性,相信有了以上的基础之后,你就可以比较通畅地阅读一些 C++ 代码了。

如果你是一个 Java 程序员,可能对其中的一些知识还是会感到迷惑,这时候需要你抛弃 Java 中的一些惯有思维,重新细细品尝一下 C++ 的味道,可以实际的去敲一下代码来消化这些知识,只有实践才能出真知。

推荐两个网站:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android直接方法和虚方法,Android NDK入门:C++ 基础知识 的相关文章

  • ESP8266开发板+mysql数据库+DHT11

    wemos D1 wifi ESP8266开发板连接mysql wemos D1 wifi ESP8266开发板是一款基于esp8266的开发板 使用这个开发板可以很方便的连接wifi 同时这个开发板有很多IO口供我们使用 这个开发板的可以
  • WPS Office 2019政府定制版本 2019.1.15版

    1 广东省 广东省政府机关单位 http wpspro support wps cn gov guangdong WPS Office 2016 专用版 10 8 0 6423 潮州市党政机关单位 http wpspro support w
  • 基于FPGA的频率计设计

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 频率计是什么 二 使用步骤 1 测量方法 2 测周方法 3 系统框图 总结 前言 所谓 频率 就是周期性信号在单位时间 秒 内变化的次数 一 频率计是什么
  • 线性代数的几何意义(一)——线性代数的意义

    线性代数的几何意义 一 一 线性 代数 的意义 何为 代数 代数 一词的英文是Algebra 源于阿拉伯语 其本意是 结合在一起 就是说代数的功能就是把许多看似不相关的事物 结合在一起 也就是进行抽象 抽象的目的不是故弄玄虚 而是为了更好的
  • zabbix配置钉钉告警、和故障自愈、监控java

    文章目录 1 配置钉钉告警 server 配置 web界面创建媒介 给用户添加媒介 测试告警 实现故障自愈功能 监控Java zabbix server 安装java gateway 配置 Zabbix Server 支持 Java gat
  • 【深度学习】 Python 和 NumPy 系列教程(十五):Matplotlib详解:2、3d绘图类型(1):线框图(Wireframe Plot)

    目录 一 前言 二 实验环境 三 Matplotlib详解 1 2d绘图类型 2 3d绘图类型 0 设置中文字体 1 线框图 Wireframe Plot 一 前言 Python是一种高级编程语言 由Guido van Rossum于199
  • 信号覆盖 蓝桥杯模拟

    信号覆盖 暴力模拟 问题描述 小蓝负责一块区域的信号塔安装 整块区域是一个长方形区域 建立坐标轴后 西南角坐标为 0 0 东南角坐标为 W 0 西北角坐标为 0 H 东北角坐标为 W H 其中 W H 都是整数 他在 n 个位置设置了信号塔
  • postgresql之pgbackrest备份恢复

    1 安装pgbackrest yum install y https download postgresql org pub repos yum reporpms EL 7 x86 64 pgdg redhat repo latest no
  • 重塑未来:AI对教育行业的深远影响与挑战

    自从AI人工智能的发展进入 iPhone时刻 以来 我们已身处一个日新月异的时代 在众多领域 AI已经大放异彩 而教育作为培养下一代的关键领域 自然也受到了这场科技革命的影响 AI对教育行业重大影响 最近可汗学院 Khan Academy
  • Python金融系列第四篇:置信区间和假设检验

    作者 chen h 微信号 QQ 862251340 微信公众号 coderpai 第一篇 计算股票回报率 均值和方差 第二篇 简单线性回归 第三篇 随机变量和分布 第四篇 置信区间和假设检验 第五篇 多元线性回归和残差分析 第六篇 现代投
  • 用chatgpt写论文可行吗,查重率会达到多少

    AI工具国内体验 关注 码视野 回复关键字 1002 选题 题目 物联网技术在智能家居系统中的应用研究 概要生成 问 请以 物联网技术在智能家居系统中的应用研究 为课题 写一篇物联网专业本科毕业论文的摘要 不少于400字 答 随着人们生活水
  • 单内核与微内核

    单内核是个很大的进程 它的内部又能够被分为若干模块 或是层次或其他 但是在运行的时候 他是个单独的二进制大映象 其模块间的通讯是通过直接调用其他模块中的函数实现的 而不是消息传递 在运行效率上 单内核会具有一定的好处 单内核结构是非常有吸引
  • 前端将后端返回的文件流转为excel并下载

    1 目的 将文件流转为excel并进行下载 下面图片是发请求之后 后端返回的文件流 想要实现的效果是将文件流转为excel并进行下载 2 实现步骤 2 1 utils exportFile js export function export
  • npm 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。

    npm 无法将 npm 项识别为 cmdlet 函数 脚本文件或可运行程序的名称 请检查名称的拼写 如果包括路径 请确保路径正确 然后再试一次 目录 一 报错 二 解决 1 安装node js node js安装过程中的报错问题 解决nod
  • ThinkPHP5多语言切换项目实战

    ThinkPHP5多语言切换实战 1 在配置文件中开启多语言配置 2 然后添加多语言目录 这里创建你需要的语言包 在语言包里定义需要翻译的文本 中英文数组的键名写成一致 然后在html文件里输入 lang 键名 对应的键名 就是下图的写法
  • unity shader加载序列帧图片

    先设置序列帧图WarpMode 为Repeat Shader部分 Shader My Sequence Properties Opacity 透明度 range 0 1 0 5 Sequence 序列帧 2d gray RowCount 行
  • global clk 的 skew & jitter

    ku040 的 skew 同一个 clk 下的不同寄存器 clk 到达时间可能会差 300ps 跟 clk 走线的长度相关 一般同一个 bank内 clk 在 30ps 之内 但是不同的 clk 即使从同一个 mmcm pll 的不同管脚发
  • 【C语言的栈溢出问题以及部分解决】

    C语言的栈溢出问题 例如 针对学习过程中遇到的栈溢出问题 C语言的栈溢出问题 前言 栈溢出 Stack overflow 导致栈溢出的原因 函数递归层次太深 1 修改栈区空间大小 2 尾部递归优化 附一 设置优化选项 O1 O2 附二 解决
  • idea中 mybatis 的 mapper.xml 新建没有 头文件

    idea中 mybatis 的 mapper xml 新建没有 头文件 解决步骤 1 直接 settings 2 直接 选择 MybatisMapper 添加

随机推荐

  • C6678多核DSP开发——image_processing例程

    前言 这篇学习笔记记录了在DSP上实现简单图像处理算法的image processing例程 该例程在CCS安装时安装在目录下 主要实现了对图像的分割 灰度处理以及边缘检测 学会了调用和修改DSP例程以及图像处理基本程序框架 1 打开CCS
  • iOS 的 APP 在系统中如何适应 iPhone 5s/6/6 Plus 三种屏幕的尺寸?

    iOS 的 APP 在系统中如何适应 iPhone 5s 6 6 Plus 三种屏幕的尺寸 iOS开发如何标准化的适应新的iPhone 5s iPhone6 6 Plus 是否有一种一劳永逸的解决方法 而不需要设计师针对不同的尺寸单独进行设
  • 基础算法题——炎炎消防队(取巧、三分)

    炎炎夏日 题目描述 夏天的重庆格外地炎热 很容易起火 消防士们都全副武装 一旦发生险情就立马赶往救火 森罗是消防队中的一员 他在灭火的过程中突发奇想 如果能用退火的原理求解函数求最小值 那不就可以很容易计算了吗 翌日 森罗来到即将高考的弟弟
  • Android中如何重新启动应用APP或重启系统

    重新启动应 程序 有两种 法 分别是 1 通过ActivityManager来重新启动应 程序 java 代码 ActivityManager manager ActivityManager this getSystemService Co
  • pm grant 命令

    CustomLocale apk所需要的权限 android permission CHANGE CONFIGURATION 自Android 4 2 4 2 2起系统定义为android protectionLevel signature
  • java学习笔记——众筹项目练习——前台系统的实名认证功能、ajax发送跨域请求、后台manager系统的实名认证人工审核

    实名认证功能 前面的文章中我们实现了后台manager系统中的流程管理功能 并且将实名认证的流程上传到了服务器并完成部署 不过 仅仅是长传和部署当然不是我们的目的啦 我们上传这个实名认证流程时为了可以让前台的广大用户可以使用这个流程 怎么才
  • C++ Primer 第五章 Statements

    C Primer 第五章 Statements 5 3 Conditional Statements 5 3 2 The switch Statement 5 4 Iterative Statements 5 4 3 Range for S
  • VScode 运行java出现exited with code=1 in 0.695 seconds的问题解决

    在运行vs中Java代码时 配置过程中可能会出现一些问题 导致运行结果为上述所示 在vs中运行Java代码时 首先要确保Java环境配置无误 出现下面的则证明配置成功 之后需要安装几个插件 最后就可以在vs中编写Java代码了
  • 使用C++调用C#的DLL

    SwfDotNet是C 编写的 作者的C 水平 真是令我佩服 这是个特别好的读写Swf文件的库 但是 我要用在C 项目中 怎么让C 调用C 的DLL呢 今天一上午都在琢磨这个问题 耽误了很多时间 原因是编译是出现 warning C4819
  • LeetCode题目笔记——389. 找不同/Python/C++

    文章目录 题目描述 题目难度 简单 方法一 使用哈希表计数出现次数 代码 Python 方法二 利用字母的ASCII码 代码 C Python 方法三 位运算 总结 题目描述 给定两个字符串 s 和 t 它们只包含小写字母 字符串 t 由字
  • thttpd嵌入式www服务工具的使用

    thttpd是一个非常小巧的轻量级web server 它非常简单 仅仅提供了HTTP 1 1和简单的CGI支持 在其官方网站上有一个与其他web server 如Apache Zeus等 的对比图 Benchmark 可以参考 此外 th
  • 空间频率 MTF和 SFR

    什么叫空间频率 我们日常中一般的频率是指时间频率 其单位是Hz 其定义是单位时间内 s 运动次数 官方定义 1 单位时间内完成振动或振荡的次数或周数 2 某一时间内某事物发生的次数或完成某过程的次数 这里的被除数是单位所以是时间频率 但是如
  • [现代控制理论]11_现代控制理论串讲_完结_pdf获取

    DR CAN的现代控制理论的笔记就结束了 加上这篇一共11篇 现代控制理论 11 现代控制理论串讲 完结 pdf获取 现代控制理论 10 可观测性与分离原理 观测器与控制器 现代控制理论 9 状态观测器设计 龙伯格观测器 现代控制理论 8
  • 数据结构(五):前序遍历、中序遍历、后序遍历

    我们先看下二叉树的前序 后序和中序遍历 遍历下面这个二叉树 分别以前中后三种遍历方式 写出结点的顺序 前序遍历 顺序 根左右 或 中左右 遍历根节点 遍历根结点的左子结点 如果左结点不是叶节点 则以当前结点开始 重新从第一步开始循环 遍历根
  • keil错误 *** FATAL ERROR L250: CODE SIZE LIMIT IN RESTRICTED VERSION EXCEEDED 解决方法

    keil错误 FATAL ERROR L250 CODE SIZE LIMIT IN RESTRICTED VERSION EXCEEDED 解决方法 出现这个是你的keli没有破解 步骤如下 1 以管理员的身份运行你的keli 2 以管理
  • 【Java+MySQL】使用JDBC连接MySQL 8.0数据库

    一 Java MySQL 8 0连接驱动包 下载链接 https pan baidu com s 1YFOImz0dCHtzIajSFq9xgg pwd boul 提取码 boul IDEA 导入方式 1 在 External Librar
  • Mysql 学习

    文章目录 下载 安装 修改 修改登录密码 授权 下载 Mysql 数据库的下载链接 注意 CenterOS 请选择 Red Hat 那一项 安装 这里介绍的是 5 7 版本 安装包是 rpm bundle tar 结尾的 创建 mysql
  • ZC-CLS381RGB颜色识别——配置寄存器组(上)

    文章目录 前言 一 ZC CLS381RGB简介 二 配置寄存器组 1 主控寄存器 2 检测速率寄存器 2 增益寄存器 2 颜色数据寄存器 三 状态转移图和信号波形图绘制 总结 前言 在现代工业生产中 颜色识别技术已经成为了一个非常重要的技
  • 使用JsonConvert.DeserializeObject注意事项

    在使用JsonConvert DeserializeObject反序列化自定义对象的时候 我遇到了一个问题 定义了一个对象QueryModel QueryModel拥有两个构造方法 私有无参构造方法 private QueryModel 跟
  • android直接方法和虚方法,Android NDK入门:C++ 基础知识

    为什么写这篇文章 本文算作是 Android 音视频开发打怪升级 系列文章的 番外 篇 原本打算将本文的内容写在 Android FFmpeg视频解码播放 这篇文章中 因为要想学习 FFmpeg 相关知识 C 的基础知识是必不可少的 但是写