用某个用户的权限来执行

1
run-as $package_name

例如:查看sp文件

1
2
3
4
adb shell
run-as $package_name
cd shared_prefs
cat $file_name
阅读全文 »

一、异或运算符 基础定义(二进制本质)

异或的符号是 ^ ,是二进制位运算,运算规则非常简单:相同为 0,不同为 1

  • 0 ^ 0 = 0
  • 0 ^ 1 = 1
  • 1 ^ 0 = 1
  • 1 ^ 1 = 0

补充:参与运算的整数,会先被转为二进制补码形式,再按位做异或运算,最终结果转回十进制。

例:3 ^ 5 → 二进制 011 ^ 101 = 110 → 十进制结果 6


二、异或的 6 个核心基础性质(必记,最常用)

这 6 个是异或最根本的性质,所有推导规律都来自于此,优先级从上到下,重要程度递减,全部要牢记:

✅ 性质 1:自反性 a ^ a = 0

任何整数和自身做异或运算,结果一定是 0

  • 原理:二进制每一位都相同,按位异或必然全为 0
  • 例:5^5=0100^100=00^0=0

✅ 性质 2:归零性 / 恒等律 a ^ 0 = a

任何整数和0做异或运算,结果一定是这个数本身

  • 原理:0 的二进制全是 0,任何位和 0 异或都「不同为 1、相同为 0」,等价于不变
  • 例:5^0=5-3^0=-30^0=0

✅ 性质 3:交换律 a ^ b = b ^ a

异或运算的顺序可以交换,结果不变

  • 例:3^5 = 5^3 = 62^7^9 = 7^2^9

✅ 性质 4:结合律 (a ^ b) ^ c = a ^ (b ^ c)

异或运算的组合顺序可以调换,结果不变

  • 例:(1^2)^3 = 1^(2^3) = 0

✅ 性质 5:异或的逆运算还是异或 a ^ b = c → a = c ^ b 、 b = c ^ a

这是异或最核心的解题性质,非常关键!

  • 含义:如果两个数的异或结果是第三个值,那么其中任意一个数,等于「结果值」异或「另一个数」
  • 推导:a^b = c 两边同时异或 ba^b^b = c^b → 由自反性 b^b=0a^0 = c^b → 由归零性得 a = c^b
  • 例:3^5=6 → 可以推出 3=6^55=6^3

✅ 性质 6:与加减运算的混合性质 a ^ b = (a + b) - 2*(a & b)

异或可以和「加法、按位与」做换算,了解即可,部分算法题会用到

  • 含义:两个数的异或 = 两数之和 - 两倍的「两数按位与」
  • 例:3+5=83&5=18 - 2*1 =6,和 3^5 的结果一致

三、异或的 3 个常用推导规律 / 特殊结论

由上面的基础性质,能推导出 3 个高频使用的结论,做题时可以直接套用:

结论 1

连续异或运算中,相同数字出现偶数次,会全部抵消为 0;出现奇数次,最终结果等于「该数字本身」

推导:a^a^a = (a^a)^a = 0^a = aa^a^a^a = (a^a)^(a^a) = 0^0 =0

例:1^2^1^2 = 01^2^1^2^3 = 3

结论 2

多个数连续异或,结果和数字的顺序无关(交换律 + 结合律推导)

例:a^b^c^d = b^d^a^c = d^c^b^a,结果完全相同

结论 3

a ^ b ^ a = b (最常用的「去重保留」)

推导:a^a^b = 0^b = b

含义:一个数异或另一个数,再异或自身,会得到「另一个数」,相当于 “剔除自身,保留目标数”


四、异或的 3 个经典高频应用场景(必考!)

异或的性质看似简单,但能解决很多「常规方法很麻烦」的问题,且**时间复杂度 O (n)、空间复杂度 O (1)**,是最优解,这三个场景是笔试 / 面试的常客,必须掌握:

✅ 场景 1:不用临时变量,交换两个整数的值

这是异或最经典的应用,也是最能体现「逆运算性质」的场景,无任何中间变量,效率极高

java

1
2
3
4
5
6
// 交换 a 和 b 的值,无临时变量
int a = 3, b = 5;
a = a ^ b; // 第一步:a 变成 3^5=6
b = a ^ b; // 第二步:b = 6^5 = 3 (此时b已经是原来的a)
a = a ^ b; // 第三步:a =6^3 =5 (此时a已经是原来的b)
// 最终 a=5,b=3,交换完成

✅ 原理:全程用到「异或的逆运算」,没有任何数值溢出风险,比 a=a+b;b=a-b 更安全。

✅ 场景 2:找出数组中「唯一出现一次」的数字

题目:一个非空整数数组,除了一个数字只出现 1 次,其余所有数字都出现偶数次,请找出这个唯一的数字。

要求:空间复杂度 O (1),不能用哈希表 / 集合

✅ 最优解:数组中所有数字连续异或,最终结果就是目标数字

java

1
2
3
4
5
6
7
// 例:数组 [1,2,3,2,1],唯一出现一次的是3
int[] arr = {1,2,3,2,1};
int res = 0;
for(int num : arr) {
res ^= num; // 0^1^2^3^2^1 = 3
}
System.out.println(res); // 输出 3

✅ 原理:出现偶数次的数字,异或后相互抵消为 0;最终 0 ^ 唯一数字 = 唯一数字

✅ 场景 3:找出数组中「唯一出现奇数次」的数字

题目变形:数组中只有一个数字出现奇数次,其余都出现偶数次,求这个数字。

本质和场景 2 完全一致,解法相同!

扩展:如果数组中有两个数字出现奇数次,其余偶数次,解法是「分组异或」,也是面试高频题,思路:

  1. 全部异或得到 a^b(两个目标数的异或);
  2. a^b 二进制中任意一个为 1 的位,按这个位将数组分成两组;
  3. 两组分别异或,得到两个目标数 ab

五、补充:异或的小细节(避坑)

  1. 异或运算的优先级:比「加减」低,比「赋值」高,所以 a ^ b + c 等价于 a ^ (b+c),建议加括号避免出错;
  2. 异或支持负数运算:负数在计算机中是「补码」形式,异或规则不变,例:-1 ^ -2 = 3
  3. 异或运算无进位:二进制运算时,相同位相加不进位,这也是异或被称为「半加器」的原因。

✅ 总结(核心速记,建议收藏)

  1. 异或本质:二进制按位运算,相同为 0,不同为 1
  2. 核心三性质:a^a=0a^0=a逆运算还是异或
  3. 核心规律:偶数次抵消为 0,奇数次保留自身;
  4. 三大应用:无临时变量交换数字找唯一出现 1 次的数字找唯一奇数次数字

cmake

include_directories是CMake中的一个命令,其作用是向项目中添加包含目录(即头文件搜索路径)。当编译器编译项目中的源代码文件时,它会在这些指定的目录中查找#include指令引用的头文件。

使用 include_directories 命令可以使编译器搜索项目内外的附加目录,从而找到所需的头文件。这对于跨目录的项目结构或者使用第三方库非常有用。

下面是一个include_directories的使用示例:

1
include_directories(includes /path/to/external/includes)

在这个例子中,includes是项目内的一个目录,指的是相对于当前CMakeLists.txt文件所在目录的相对路径。/path/to/external/includes是系统中某个外部库头文件的绝对路径。这条命令将这两个目录添加到编译器的头文件搜索路径中。

一旦使用了include_directories,它会影响所有随后定义的目标(例如使用add_executable或者add_library定义的可执行文件或库)。如果你希望对特定目标设置包含目录而不影响其他的,应使用target_include_directories命令,它提供了更细粒度的控制。

例如:

1
2
add_executable(MyExecutable main.cpp)
target_include_directories(MyExecutable PRIVATE includes)

在这个例子中,MyExecutable仅会包含includes目录作为私有依赖,这意味着这个包含目录仅对MyExecutable可见,不会影响到其他目标。

通常建议使用target_include_directories而非include_directories,因为前者更符合现代CMake的目标属性设置方式,允许更好的封装性和可重用性

cmake

在 Java 中,”submodule” 这个术语并不是官方的编程概念,而是通常用于描述较大项目中的组织结构,尤其是在使用 Maven 或 Gradle 这样的构建工具时。这些构建工具允许你创建多模块(multi-module)项目,其中每个模块可以有自己的构建生命周期和依赖管理。

如果将 CMake 中的 add_subdirectory 命令与 Java 多模块项目的概念进行比较,那么可以说它们在概念上是相似的。add_subdirectory 允许你将一个目录(可以认为是一个模块或子项目)添加到主项目的构建过程中。这个目录通常包含它自己的 CMakeLists.txt 文件,定义了如何构建该目录中的代码。

在 Java 的多模块项目中,每个模块也会有自己的构建配置文件(例如 Maven 的 pom.xml),定义了模块的构建过程和依赖。

例如,在 Maven 中,你的项目结构可能如下:

1
2
3
4
5
6
7
8
project/

├── pom.xml # 父项目的 Maven POM 文件

├── submodule1/
│ └── pom.xml # 子模块的 Maven POM 文件

└── ...

父项目的 pom.xml 文件会包含对所有子模块的引用:

1
2
3
4
5
<modules>
<module>submodule1</module>
<module>submodule2</module>
<!-- ... -->
</modules>

将 Maven 的多模块项目和 CMake 中的 add_subdirectory 命令进行类比,它们都有以下相似之处:

  • 都是将一个较大的项目组织为更小、更易管理的组件或模块。
  • 每个模块或组件都有自己的构建配置和构建过程。
  • 主项目配置文件包含对这些子模块或组件的引用,并且管理整个构建过程。

总之,尽管 CMake 和 Java 构建工具(如 Maven 或 Gradle)在技术细节上不同,add_subdirectory 与 Java 多模块项目的概念在组织和管理大型代码基础方面确实有一定的相似性。

cmake

在 CMake 中,并没有一个默认机制来自动查找源码文件。CMake 需要你在 CMakeLists.txt 配置文件中显式地指定源码文件。这意味着 CMake 不会去猜测或自动探测你的源码文件所在;你必须明确告诉 CMake 需要编译哪些文件。

通常,你会在 CMakeLists.txt 文件中使用 add_executable 或者 add_library 命令来定义编译目标,并显式地列出构成该目标的所有源码文件。例如:

1
add_executable(MyExecutable main.cpp foo.cpp bar.cpp)

在这个例子中,MyExecutable 是目标的名字,main.cppfoo.cppbar.cpp 是明确指定的源码文件。

如果你有一组相对固定的源码文件,你可以简单地将它们全部列出。然而,对于较大的项目或者源码文件经常变动的情况,逐一列出所有文件可能会变得繁琐。为了简化这个过程,CMake 提供了一些命令来帮助你查找和指定源码文件,这些命令包括 aux_source_directoryfile(GLOB ...)。虽然这些命令可以帮助收集源码文件,但它们通常不是最佳实践,因为它们无法跟踪文件系统中的变化(例如,当你添加或删除文件时,CMake 不会自动更新)。

例如,使用 file(GLOB ...) 可以根据模式匹配查找文件:

1
2
file(GLOB MY_SOURCES "*.cpp") # 不推荐用于源文件
add_executable(MyExecutable ${MY_SOURCES})

在这个例子中,MY_SOURCES 变量会包含当前目录下所有后缀为 .cpp 的源码文件。

总结来说,CMake 需要你在 CMakeLists.txt 中显式指定源码文件,它不会默认查找源码文件。你可以使用 file(GLOB ...)aux_source_directory 这样的命令来收集源码文件,但最好的实践仍然是显式地列出所有源码文件,尤其是对于那些希望确保构建系统适应性和可维护性的项目来说。

在C++中,extern关键字用于指定变量或函数的定义在其他的文件中。这样可以在多个文件之间共享变量或函数。主要用途是用于提供对全局变量和函数的访问,尤其是在大型项目中多文件共用的场景。

extern用法主要有以下几种场景:

  1. 声明全局变量而不定义它extern用来在一个文件中声明一个全局变量,而不是定义它。真正的定义会在别的地方。这样做的目的是为了在多个文件中共享这个变量。

    1
    2
    3
    4
    5
    // file1.cpp
    int myGlobal; // 定义一个全局变量

    // file2.cpp
    extern int myGlobal; // 声明相同的全局变量,告诉编译器其定义在其他地方

    以上代码中,file1.cpp定义了一个全局变量myGlobal,而file2.cpp使用extern来声明这个全局变量,以便可以在file2.cpp中访问file1.cpp中定义的myGlobal

  2. 声明函数: 一般情况下,函数在C++中默认是extern的,即使没有显式声明。但是你也可以显式地声明它,特别是在你想要强调函数可以在其他文件中被访问时。

    1
    2
    3
    4
    5
    // file1.cpp
    void doSomething(); // 默认extern

    // file2.cpp
    extern void doSomething(); // 显式声明
  3. static关键字的对比externstatic在用于变量声明时有着对立的含义。static用于限制变量或函数的链接作用域到定义它的文件,而extern则表明变量或函数可以在多个文件间共享。

  4. 使用extern "C"防止C++的名称修饰: 当你想要在C++代码中调用C语言编写的函数时,你需要防止C++编译器对这些函数名进行名称修饰(name mangling),因为C编译器不会这么做。extern "C"可以用来告诉C++编译器不要修饰这些特定的函数名。

    1
    2
    3
    extern "C" {
    #include "header_of_c_code.h"
    }

    或者用于单个函数声明:

    1
    extern "C" void cFunction();

在实际开发过程中,使用extern关键字可以帮助组织跨多个文件的代码,方便代码管理和模块化设计。但是,过度使用全局变量可能会引起程序设计上的问题,如数据封装不足、线程安全问题等,因此应当谨慎使用。

在C++中,特殊成员函数是那些可能由编译器隐式声明和定义的类成员函数。它们是类的生命周期和对象管理的关键部分。以下是C++中的特殊成员函数:

  1. 默认构造函数 (Default Constructor): 这是一个不接受任何参数,或者每个参数都有默认值的构造函数。如果你没有定义任何构造函数,编译器会为你提供一个默认的无参构造函数。
  2. 拷贝构造函数 (Copy Constructor): 它用于创建一个新对象作为另一个现有对象的副本。如果不定义,编译器会定义一个默认的拷贝构造函数,进行成员到成员的复制。
  3. 拷贝赋值操作符 (Copy Assignment Operator): 当通过赋值操作将一个对象的内容复制到另一个现有对象中时,会调用这个特殊成员函数。默认的拷贝赋值操作符也执行成员到成员的复制。
  4. 移动构造函数 (Move Constructor): 在C++11中引入,允许资源的转移而非复制。如果一个对象拥有资源,如动态内存,移动构造函数可以将资源从一个对象转移到另一个对象,通常是为了提高性能。
  5. 移动赋值操作符 (Move Assignment Operator): 类似于移动构造函数,它允许你通过移动而非复制一个对象的资源来给另一个对象赋值。这通常发生在具有右值引用参数的赋值操作中。
  6. 析构函数 (Destructor): 当对象生命周期结束时,例如对象离开作用域或被delete调用时,析构函数被调用来进行清理工作,比如释放资源等。
阅读全文 »

在 C++ 中,explicit 关键字用于防止类构造函数的隐式自动类型转换。默认情况下,如果一个构造函数只接受一个参数(或所有参数都有默认值),它可以被用作隐式类型转换操作符,这意味着它可以在不经过显式转换的情况下将一种类型的值转换为类类型的对象。

使用 explicit 关键字可以阻止编译器执行这种隐式转换,这有助于避免意外的类型转换可能导致的错误和混淆。当你希望类构造函数只在明确使用构造语法时被调用时,你应该将它声明为 explicit

例如,考虑以下类:

1
2
3
4
5
6
class MyString {
public:
MyString(const char* s) {
// 构造函数,将const char*类型的字符串转换为MyString对象
}
};
阅读全文 »

复制语义(Copy Semantics)涉及创建对象的深层次副本,包含对其所有成员(包括指向的动态资源)的复制。移动语义(Move Semantics)则是C++11引入的一种优化,它允许“窃取”临时对象的资源,避免不必要的复制。

以下是一个简单的Vector类的例子,该类具有动态分配的数组,用以展示复制语义和移动语义的区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <algorithm> // For std::copy
#include <utility> // For std::move

class Vector {
public:
// 构造函数
Vector(size_t size) : size_(size), data_(new int[size]) {}

// 析构函数
~Vector() { delete[] data_; }

// 拷贝构造函数(复制语义)
Vector(const Vector& other) : size_(other.size_), data_(new int[other.size_]) {
std::copy(other.data_, other.data_ + size_, data_);
}

// 移动构造函数(移动语义)
Vector(Vector&& other) noexcept: size_(other.size_), data_(other.data_) {
other.size_ = 0; // 将other对象置于有效且确定的状态
other.data_ = nullptr;
}

// 更多成员函数...

private:
size_t size_;
int* data_;
};
阅读全文 »
0%