. ->运算符
在C和C++两种语言中,.
和->
运算符都存在,并且它们的用法和含义是相同的。
.
运算符:在C和C++中,当你有一个结构体或类的实例(也就是一个对象)时,你可以使用.
运算符来访问其成员。->
运算符:在C和C++中,当你有一个指向结构体或类的指针时,你可以使用->
运算符来访问其成员。
JNIEnv *env;
c中你需要写(*env)->member
,而在C++中,你可以简单地写env->member
是因为JNIEnv在c和cpp是不一样的,
对于(env) c中是JNINativeInterface,cpp是_JNIEnv。并不是由于. ->运算符在c和cpp中的功能不同
方法签名
JNIEnv *env, jobject thiz
参数签名时要省略 例如:
cpp
jint native_op(JNIEnv *env, jobject thiz,int p) {
return p;
}
对应的签名是:(I)I
函数的类型是什么
还是上面那个例子:
cpp
jint native_op(JNIEnv *env, jobject thiz,int p) {
return p;
}
如果只写一个native_op
他的类型应该怎么写?
c
int addFunction(int a, int b) {
return a + b;
}
void test(){
int (*funcPtr)(int, int) = addFunction;
int p = funcPtr(1,2);
funcPtr(p,3);
}
对于addFunction
,他的类型是int (*funcPtr)(int, int)
,变量名字叫funcPtr
所以例子中的函数类型是:jint (*funcPtr)(JNIEnv *, jobject, int)
当然在c++中除了上面那样写,还可以使用auto
关键字,c语言中的auto没有这个特性
cpp
void test() {
auto funcPtr = addFunction;
int p = funcPtr(1, 2);
funcPtr(p, 3);
}
c++特有的类型自动推断
在C++中,auto
关键字用于自动类型推断,让编译器根据初始化表达式来推断变量的类型。这在处理复杂类型或者想要避免类型名称冗长的情况下非常有用。以下是一个简单的例子:
cpp
auto i = 5; // i is int
auto d = 3.14; // d is double
auto s = "hello"; // s is const char*
在这个例子中,编译器会根据等号右边的值来推断i
、d
和s
的类型。这样可以使代码更简洁,更易于阅读和维护。
注: 在C语言中,
auto
关键字主要用于声明一个变量为自动存储类型,这意味着这个变量的生命周期仅限于它的声明块(通常是一个函数)。一旦控制流离开这个块,变量就会被销毁。然而,实际上
auto
关键字在C语言中的使用非常少,因为如果没有明确指定存储类型,局部变量默认就是自动存储类型。也就是说,以下两个声明是等价的:
cppint x; // 默认为自动存储类型 auto int y; // 显式声明为自动存储类型
因此,
auto
关键字在C语言中的应用场景非常有限,通常不需要显式使用。在现代C++中,auto
关键字的含义已经改变,用于自动类型推断,这是一个完全不同的概念。
c++中的lambda
除了和c语言一样去定义函数
还可以使用lambda
cpp
int main() {
// 定义一个lambda表达式,接受两个int参数,返回它们的和
auto add = [](int a, int b) {
return a + b;
};
// 使用lambda表达式
int result = add(3, 4);
// result is 7
std::cout << result << std::endl;
return 0;
}
c++ 面向对象
在C++中,对象是类的实例。首先,你需要定义一个类,然后你可以创建该类的对象。以下是一个简单的例子:
cpp
#include <iostream>
// 定义一个类
class MyClass {
public:
int myNumber;
std::string myString;
private:
int privateNumber;
public:
void print() {
std::cout << "Number: " << myNumber << ", String: " << myString << std::endl;
}
};
int main() {
// 创建一个MyClass的对象
MyClass obj;
// 设置对象的属性
obj.myNumber = 5;
obj.myString = "Hello";
// 调用对象的成员函数
obj.print();
return 0;
}
在这个例子中,MyClass
是一个类,它有两个成员变量(myNumber
和myString
)和一个成员函数(print
)。然后,我们在main
函数中创建了一个MyClass
的对象obj
,设置了它的属性,并调用了它的成员函数。
注: 在C++中,创建对象的方式有两种:一种是在栈上创建,另一种是在堆上创建。
在上述例子中,我们在栈上创建了一个对象。这种情况下,不需要使用
new
关键字。对象在声明时就已经创建,并且在离开作用域时会自动销毁。
cppMyClass obj; // 在栈上创建对象
如果你想在堆上创建对象,那么就需要使用
new
关键字。这种情况下,对象在使用new
创建后存在,直到你使用delete
显式销毁它。
cppMyClass* obj = new MyClass(); // 在堆上创建对象 // 使用对象... delete obj; // 销毁对象
在堆上创建对象可以让对象的生命周期超过创建它的作用域,但需要手动管理内存,否则可能会导致内存泄漏。
构造方法
在C++中,构造函数是一种特殊的成员函数,它在创建类的对象时自动调用。构造函数的名称与类的名称相同,没有返回类型。以下是一些构造函数的写法:
- 默认构造函数:没有参数或所有参数都有默认值。
cpp
class MyClass {
public:
MyClass() {
// 默认构造函数
}
};
- 参数化构造函数:带有参数的构造函数。
cpp
class MyClass {
public:
int x;
MyClass(int val) : x(val) {
// 参数化构造函数
}
};
- 拷贝构造函数:用于初始化一个对象为另一个对象的副本。
cpp
class MyClass {
public:
int x;
MyClass(const MyClass &obj) : x(obj.x) {
// 拷贝构造函数
}
};
- 移动构造函数:用于初始化一个对象为另一个对象的"移动"副本。
cpp
class MyClass {
public:
int *x;
MyClass(MyClass &&obj) : x(obj.x) {
obj.x = nullptr; // 移动构造函数
}
};
- 委托构造函数:一个构造函数调用同类的其他构造函数。
cpp
class MyClass {
public:
int x, y;
MyClass(int val) : x(val), y(val) {}
MyClass() : MyClass(0) {} // 委托构造函数
};
在C++中,x(val)
和y(val)
是成员初始化列表的一部分,用于初始化类的成员变量。在这个例子中,x(val)
和y(val)
表示将val
的值分别赋给x
和y
。
成员初始化列表位于构造函数参数列表和函数体之间,由冒号开始,每个初始化器由一个成员名和括号内的初始值组成,初始化器之间用逗号分隔。
这种初始化方式比在构造函数体内部进行赋值更有效,因为它可以直接初始化成员,而不是先创建然后再赋值。对于某些类型的成员(如const或引用成员),必须在成员初始化列表中进行初始化,因为它们不能在构造函数体内部赋值。
如果你既要初始化成员变量又要调用基类的构造函数,你可以在派生类的构造函数中使用成员初始化列表。成员初始化列表可以同时完成这两个任务。以下是一个示例:
cpp
#include <iostream>
class Base {
public:
Base(int x) {
std::cout << "Base constructor called with value: " << x << std::endl;
}
};
class Derived : public Base {
public:
int y;
Derived(int x, int y_value) : Base(x), y(y_value) { // 调用基类的构造函数并初始化成员变量
std::cout << "Derived constructor called with value: " << y << std::endl;
}
};
int main() {
Derived obj(10, 20);
}
在这个例子中,Derived
类的构造函数通过成员初始化列表调用了Base
类的构造函数,并初始化了成员变量y
。当创建Derived
类的对象时,将首先调用Base
类的构造函数,然后初始化y
,最后执行Derived
类的构造函数体。
派生
C++中,派生是通过继承来创建新类的过程。新类(派生类)继承了一个或多个现有类(基类)的特性。以下是一个简单的例子:
cpp
// 基类
class BaseClass {
public:
int baseNumber;
void basePrint() {
std::cout << "Base Number: " << baseNumber << std::endl;
}
};
// 派生类
class DerivedClass : public BaseClass {//还有private、protected
public:
int derivedNumber;
void derivedPrint() {
std::cout << "Derived Number: " << derivedNumber << std::endl;
}
};
int main() {
DerivedClass obj;
// 访问基类的成员
obj.baseNumber = 10;
obj.basePrint();
// 访问派生类的成员
obj.derivedNumber = 20;
obj.derivedPrint();
return 0;
}
在这个例子中,DerivedClass
是通过公有继承从BaseClass
派生的。这意味着BaseClass
的所有公有和保护成员都成为了DerivedClass
的成员,并且保持了它们在BaseClass
中的访问级别。因此,我们可以在DerivedClass
的对象中访问baseNumber
和basePrint
。如果选择私有继承,那么 BaseClass对外是不可见的,只可以在DerivedClass类中调用。
C++ 模板 也就是java和kotlin里面的泛型
在C++中,模板是一种特性,允许你编写通用的代码,可以处理多种数据类型。模板可以应用于函数(称为函数模板)和类(称为类模板)。
以下是一个函数模板的例子:
cpp
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << max<int>(3, 7) << std::endl; // 输出7
std::cout << max<double>(3.14, 2.72) << std::endl; // 输出3.14
return 0;
}
这里的max<int>(3, 7)
等价于max(3, 7)
,c++可以自动推断出。
在这个例子中,max
函数是一个模板函数,可以接受任何类型的参数,只要该类型支持>
运算符。
以下是一个类模板的例子:
cpp
template <typename T>
class MyPair {
public:
T first, second;
MyPair(T a, T b) {
first = a;
second = b;
}
T getMax() {
return (first > second) ? first : second;
}
};
int main() {
MyPair<int> intPair(3, 7);
std::cout << intPair.getMax() << std::endl; // 输出7
MyPair<double> doublePair(3.14, 2.72);
std::cout << doublePair.getMax() << std::endl; // 输出3.14
return 0;
}
在这个例子中,MyPair
类是一个模板类,可以接受任何类型的参数,只要该类型支持>
运算符。
c++ 异常处理
在C++中,异常处理主要涉及到以下几个关键字:try
,catch
,throw
。以下是一些基本的使用方式:
- 抛出异常:使用
throw
关键字抛出异常。
cpp
throw "An error occurred";
- 捕获异常:使用
try
/catch
块捕获并处理异常。
cpp
try {
// 可能抛出异常的代码
throw "An error occurred";
}
catch (const char* msg) {
std::cerr << msg << std::endl;
}
- 捕获所有异常:使用
catch(...)
捕获所有类型的异常。
cpp
try {
// 可能抛出异常的代码
throw "An error occurred";
}
catch (...) {
std::cerr << "An unknown error occurred" << std::endl;
}
- 标准库异常:C++标准库提供了一系列的异常类,如
std::exception
,std::runtime_error
,std::out_of_range
等。
cpp
try {
// 可能抛出异常的代码
throw std::runtime_error("A runtime error occurred");
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
请注意,异常处理应该用于处理那些无法预防或无法通过其他方式处理的错误情况。过度使用异常处理可能会导致代码难以理解和维护。
c++ 可变参数
在C++中,...
被称为省略号或者可变参数。它有两个主要的用途:
- 在函数参数列表中表示该函数可以接受任意数量和类型的参数。这在C++的旧版本中常见,但在现代C++中,我们更倾向于使用模板和容器来处理可变数量的参数。
cpp
#include <cstdarg>
#include <iostream>
void printNumbers(int count, ...) {
va_list list;
va_start(list, count);
for (int i = 0; i < count; ++i) {
std::cout << va_arg(list, int) << '\n';
}
va_end(list);
}
int main() {
printNumbers(3, 10, 20, 30);
}
- 在
catch
语句中,...
表示捕获所有类型的异常。
cpp
try {
// 可能抛出异常的代码
throw "An error occurred";
}
catch (...) {
std::cerr << "An unknown error occurred" << std::endl;
}
在C++11及其后续版本中,...
还用于模板编程中的参数包,用于表示任意数量和类型的模板参数。
例如: 在C++11及其后续版本中,...
被用于模板编程中的参数包,表示任意数量和类型的模板参数。这是一种强大的功能,允许你创建可以处理任意类型和数量参数的模板函数或模板类。
以下是一个简单的例子,展示了如何使用参数包创建一个可以接受任意数量参数的函数模板:
cpp
#include <iostream>
// 基本情况,没有参数
void print() {}
// 递归模板函数
template<typename T, typename... Args>
void print(T head, Args... tail) {
std::cout << head << " ";
print(tail...); // 递归调用,参数包中的下一个参数成为新的head
}
int main() {
print(1, 2.0, "three", '4');//省略泛型的声明<>
}
在这个例子中,print
函数模板可以接受任意数量和类型的参数。T
表示第一个参数的类型,Args...
表示剩余参数的类型。在函数体中,print(tail...)
递归地调用print
函数,每次调用都将参数包中的下一个参数作为head
。
va_start
这里的va_start
是C和C++中处理可变参数的宏。它被用于初始化va_list
类型的对象,这个对象用于访问函数参数列表中的可变参数。
在使用va_start
宏之前,你需要定义一个va_list
类型的变量,这个变量将被初始化为指向第一个可变参数的指针。va_start
接受两个参数:第一个是刚刚提到的va_list
变量,第二个是可变参数列表前的最后一个非可变参数。
以下是一个例子:
cpp
#include <cstdarg>
#include <iostream>
void printNumbers(int count, ...) {
va_list list;
va_start(list, count); // 初始化va_list
for (int i = 0; i < count; ++i) {
int num = va_arg(list, int); // 获取下一个参数的值
std::cout << num << '\n';
}
va_end(list); // 清理为va_list分配的内存
}
int main() {
printNumbers(3, 10, 20, 30);
}
在这个例子中,va_start(list, count)
将list
初始化为指向第一个可变参数的指针。然后,va_arg
宏被用于获取可变参数的值。最后,va_end
宏被用于清理为va_list
分配的内存。
c++ 智能指针
在C++中,智能指针是一种对象,它可以像常规指针一样处理,但当它不再需要时,它会自动删除它所指向的对象。这种自动删除可以防止内存泄漏,使内存管理变得更加简单。
C++标准库提供了几种类型的智能指针,包括std::unique_ptr
,std::shared_ptr
和std::weak_ptr
。
std::unique_ptr
是一种独占所有权的智能指针,它不允许多个指针指向同一个对象。std::shared_ptr
允许多个指针共享同一个对象,当最后一个shared_ptr
不再指向对象时,对象会被删除。std::weak_ptr
是一种不控制对象生命周期的智能指针,它被设计为与std::shared_ptr
一起使用,防止智能指针的循环引用。
使用智能指针可以帮助你更有效地管理内存,避免常规指针可能导致的内存泄漏和悬挂指针问题。
当然可以。以下是一个使用std::unique_ptr
的例子:
cpp
#include <memory>
struct MyClass {
int value;
MyClass(int v) : value(v) {}
};
int main() {
std::unique_ptr<MyClass> ptr(new MyClass(10));
std::cout << ptr->value << std::endl; // 输出:10
return 0;
}
在这个例子中,当ptr
离开其作用域(即main
函数结束)时,它会自动删除它所指向的MyClass
对象,释放内存。
以下是一个使用std::shared_ptr
的例子:
cpp
#include <memory>
struct MyClass {
int value;
MyClass(int v) : value(v) {}
};
int main() {
std::shared_ptr<MyClass> ptr1(new MyClass(10));
{
std::shared_ptr<MyClass> ptr2 = ptr1;
std::cout << ptr1->value << std::endl; // 输出:10
std::cout << ptr2->value << std::endl; // 输出:10
} // ptr2离开作用域,但ptr1仍然指向对象
std::cout << ptr1->value << std::endl; // 输出:10
return 0;
}
在这个例子中,ptr1
和ptr2
共享同一个对象。当ptr2
离开其作用域时,对象不会被删除,因为ptr1
仍然指向它。只有当ptr1
也离开其作用域时,对象才会被删除。
验证 :std::unique_ptr
指向的指针只能存在一个引用,编译期间就会报错
c++ 并发
以下是一些C++并发编程的例子:
- 线程(Threads) :
cpp
#include <iostream>
#include <thread>
void hello() {
std::cout << "Hello, World!" << std::endl;
}
int main() {
std::thread t(hello);
t.join();
return 0;
}
- 互斥量(Mutexes) :
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_block(int n, char c) {
mtx.lock();
for (int i=0; i<n; ++i) { std::cout << c; }
std::cout << '\n';
mtx.unlock();
}
int main() {
std::thread th1(print_block,50,'*');
std::thread th2(print_block,50,'$');
th1.join();
th2.join();
return 0;
}
- 条件变量(Condition Variables) :
cpp
#include <iostream>
#include <thread>
#include <condition_variable>
std::condition_variable cv;
std::mutex cv_m;
int i = 0;
void waits() {
std::unique_lock<std::mutex> lk(cv_m);
cv.wait(lk, []{return i == 1;});
std::cout << "Finished waiting. i == 1\n";
}
void signals() {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Notifying...\n";
i = 1;
cv.notify_one();
}
int main() {
std::thread t1(waits), t2(signals);
t1.join();
t2.join();
return 0;
}
std::this_thread
是C++标准库中的一个命名空间,它提供了一些函数,这些函数对于操作当前线程非常有用。这些函数包括:
std::this_thread::get_id()
:返回当前线程的线程ID。std::this_thread::sleep_for()
:使当前线程休眠指定的时间段。std::this_thread::sleep_until()
:使当前线程休眠直到指定的时间点。std::this_thread::yield()
:建议操作系统切换到其他线程。这是一种对操作系统的提示,表示当前线程没有紧急的任务要执行,可以切换到其他线程。
例如,以下代码使当前线程休眠1秒:
cpp
#include <iostream>
#include <thread>
#include <chrono>
int main() {
std::cout << "Sleeping for 1 second...\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Awake!\n";
return 0;
}
这些函数可以帮助你更好地控制和管理线程的行为。
命名空间
命名空间(Namespace)是C++中的一个特性,用于组织代码并防止命名冲突。命名空间可以包含变量、函数、类等。
以下是一个简单的命名空间示例:
cpp
namespace MyNamespace {
int myVar = 10;
void myFunction() {
// 函数实现
}
}
你可以使用::
运算符来访问命名空间中的元素,如MyNamespace::myVar
。
命名空间的使用方式和应用场景包括:
- 防止命名冲突:当两个库都定义了同名的函数或类时,可以通过命名空间来区分它们。
- 代码组织:可以使用命名空间来将相关的函数、类和变量组织在一起。
- 使用
using
声明 :可以使用using
声明来引入命名空间中的特定元素,或者使用using namespace
来引入整个命名空间。
例如:
cpp
using MyNamespace::myVar; // 引入特定元素
using namespace MyNamespace; // 引入整个命名空间
使用using namespace
后就不需要带MyNamespace::
前缀了
请注意,过度使用using namespace
可能会导致命名冲突,因此在编写大型项目时应谨慎使用。