NDK学习之路之 c++ 知识归纳

1.c++ 引用

其实就是四驱模型的拷贝,引用其实是地址赋值,可以看成同一块内存的另外一个变量
以两个变量值交换为例

#include<stdio.h>
void swap(int &number1, int &number2) {
	// &number 此处相当于同一块内存 取了另一个别名
	int temp = number1;
	number1 = number2;
	number2 = temp;
}
void main() {
	int number1 = 10;
	int number2 = 20;
	swap(number1, number2);
	printf("%d  %d\n", number1, number2); // 结果 20 10
	getchar();
}
2.常量指针和指针常量

常量变量:被常量修饰的变量,不能再次被赋值 (Java)

常量指针(const int *p):const 在 * 之前,指针的地址是可以被再次赋值的(可以修改的),指针地址上面的值(变量)是不能被修改的,常量指针的常量是不能被改变的。指向常量的指针,表示不可以修改p的内容,但是可以修改 p 的地址.

指针常量(int *const p):const 在 * 之后,指针的地址是不可以被再次赋值的(不可以修改的),指针地址上面的值(变量)能被修改的,指针常量的指针地址是不能被改变的。指针的常量,表示不可以修改p的地址,但是可以修改 p 的内容.

    // 常量,不能去修改
	const int number = 100;
	// number = 200;   // error

	int number1 = 100;
	int number2 = 200;

	// 常量指针
	// int const * n_p = &number2;
	// n_p = &number1;
	// printf("n_p = %p",n_p); // 地址是可以重新被赋值的 
	// *n_p = 300; // 值是不能改的 error

	// 指针常量
	int * const n_p = &number2;
	// n_p = &number1; // 地址是不能被重新赋值
	*n_p = 300;
	printf("number2 = %d", number2);// 值可以被修改,结果:300
3.malloc,free,new,delete 区别

malloc/free 一对,new/delete 是一对

malloc/free 不会去调用构造函数和析构函数

new/delete 会去调用构造函数和析构函数
如果用了new,一定要记得 delete 释放内存

4.构造函数、析构函数和拷贝构造函数

直接上代码说明

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

using namespace std;

class Student
{
public:
	// 构造函数
	Student(){// 空参数构造函数
		cout << "空参数构造函数"<< endl;
	}

	// Student(char* name):age(0){// 一个参数构造函数, 相当于 this->age = 0
	//	cout << "一个参数构造函数" << endl;
	//	this->name = name;
	// }

	Student(char* name) :Student(name,0){
     // 调用两个参数的构造函数
     // 注意:先会调用两个参数的构造函数,然后才会执行当前构造函数
     cout << "一个参数构造函数" << endl;
	}

	Student(char* name, int age){// 两个参数构造函数
		cout << "两个参数构造函数" << endl;
		this->name = (char*)malloc(sizeof(char)*100);
		strcpy(this->name,name);
		this->age = age;
	}

	// 2. 析构函数,如果有在对象内部开辟堆内存,可以在析构函数中释放内存
	~Student(){
		cout << "析构函数" << endl;
		// 对象被回收的时候会被调用
		// 释放内存
		free(this->name);
		this->name = NULL;
	}

	/*
	4.拷贝构造函数,在 java 中如果 Student stu2 = stu1,那么我们一般会认为 stu2 对象和 stu1 对象是同一个对象,
	但是在 c++ 中这种认为就是错误的,我们可以分别打印 stu1 和 stu2 的地址发现并不相等,
	所以 stu1 和 stu2 并不是同一个对象,而是会调用拷贝构造函数。*/
	// 对象会有一个默认的拷贝构造函数,用来对象之间的赋值
	Student(const Student& stu){// 常量的引用
		cout << "拷贝构造函数" << endl;
		// this->name = stu.name;// 浅拷贝
		// 如果动态开辟内存,一定要采用深拷贝
		this->name = (char*)malloc(sizeof(char)* 100);
		strcpy(this->name, stu.name);
		this->age = stu.age;
	}
	
	Student getStudent(char* name){
    Student stu(name);// 栈 ,方法执行完,这个对象会被回收,但是发现调用了拷贝构造函数
    cout << &stu << endl;
    return stu;// 会返回一个新的 Student 对象,而栈内开辟的 stu 是会被回收
}


void printStudent(Student stu){// stu 是该方法栈中一个新的对象,拷贝构造函数赋值,方法执行完会调用析构函数
    cout << stu.getName() << " , " << stu.getAge() << endl;
}

void main(){
    // 1. = 会调用拷贝构造函数
    // Student stu1("Darren", 24);
    // Student stu2 = stu1; // = 是赋值,把里面所有定义的属性赋值,c/c++ 编译器帮我们做的,其实会调用对象的拷贝构造

    // Student stu2;// 声明变量,开辟变量内存
    // stu2 = stu1; // 这个不会去调用拷贝构造函数,但是会赋值 c 的类似

    // 2. 第二种场景 作为参数返回的时候会调用拷贝构造函数
    // Student stu = getStudent("Jack");
    // cout << &stu << endl;

    // 3. 第三种场景 作为参数传递的时候会调用拷贝构造函数
    // Student stu("Darren", 24);
    // printStudent(stu);

    // 知识的补充
    Student stu = getStudent("Jack");

    // cout << stu.getName() << " , " << stu.getAge() << endl;

    printStudent(stu);

    getchar();
}

private:
	int age;
	char* name;
 
public:
	int getAge(){
		return this->age;
	}

	char* getName(){
		return this->name;
	}

	void setAge(int age){
		this->age = age;
	}

	void setName(char* name){
		this->name = name;
	}
};
5.内存四驱模型

在 c/c++ 中我们将运行时数据,分为四个区域分别是:栈区,堆区,数据区,代码区。我们详细来介绍下:

  • 栈区:由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
  • 堆区:一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收。
  • 数据区:存放全局变量、静态变量和常量字符串等等。 程序结束后由系统释放。
  • 存放函数体的二进制代码。
6.可变参数
#include <iostream>
#include <stdarg.h>
using namespace std;

int sum(int count, ...) {  // 格式:count代表参数个数, ...代表n个参数
	va_list ap;  // 声明一个va_list变量
	va_start(ap, count);  // 第二个参数表示形参的个数

	int sum = 0;
	for (int i = 0; i < count; i++) {
		sum += va_arg(ap, int);   // 第二个参数表示形参类型
	}
	va_end(ap);  // 用于清理
	return sum;

}
void main(){
	int sumValue = sum(3,8,10,9);
	cout << sumValue << endl;   // 输出结果:27
	getchar();
}

步骤:

  1. 定义头文件 stdarg.h

  2. 定义参数:参数格式 (int count, …)

  3. 函数定义中创建一个 va_list 变量

  4. 初始化: va_start(ap, count);

  5. 循环将各个参数进行相加

  6. 清理:调用清理 va_end(va_list); 清理内容,最后调用

7.static 关键字
  1. 静态属性在 c++ 中必须要初始化
class Test {
    public static int tag;
}
int Test::tag = 1;   // 只能这样初始化
8.C++程序在内存中的分布
  1. 栈(Stack):局部变量,函数参数等存储在该区,由编译器自动分配和释放.栈属于计算机系统的数据结构,进栈出栈有相应的计算机指令支持,而且分配专门的寄存器存储栈的地址,效率很高,内存空间是连续的,但栈的内存空间有限。
  2. 堆(Heap):需要程序员手动分配和释放(malloc,free),属于动态分配方式。内存空间几乎没有限制,内存空间不连续,因此会产生内存碎片。操作系统有一个记录空间内存的链表,当收到内存申请时遍历链表,找到第一个空间大于申请空间的堆节点,将该节点分配给程序,并将该节点从链表中删除。一般,系统会在该内存空间的首地址处记录本次分配的内存大小,用于 free 释放该内存空间。
  3. 全局/静态存储区:全局变量,静态变量分配到该区,到程序结束时自动释放,包括 DATA 段(全局初始化段)与 BBS 段(全局未初始化段)。其中,初始化的全局变量和静态变量存放在 DATA 段,未初始化的全局变量和静态变量存放在 BBS 段。BBS 段特点:在程序执行前 BBS 段自动清零,所以未初始化的全局变量和静态变量在程序执行前已经成为 0.
  4. 文字常量区:存放常量,而且不允许修改。程序结束后由系统释放。
  5. 程序代码区:存放程序的二进制代码
9.类的大小

类中只计算除了静态属性的大小之和,方法什么的都不算,但定义字节数大的要排在字节数小的,例如 double 属性要在 int 之前,若定义不按此规则,则类的大小可能会变大,这样做有利于提高性能

10.友元函数、友元类

友元函数

class A{
    // 友元函数声明
    friend void modify_i(A *p, int a);
private:
    int i;
public:
    A(int i){
        this->i = i;
    }
    void myprint(){
        cout << i << endl;
    }   
};

// 友元函数的实现
void modify_i(A *p, int a){
    p->i = a;
}

void main(){
    A* a = new A(10);
    a->myprint();
    modify_i(a,20);
    a->myprint();
}

友元类

class A{        
    // 友元类声明,表示 B 这个友元类可以访问 A 类的任何成员
    friend class B;
private:
    int i;
public:
    A(int i){
        this->i = i;
    }
    void myprint(){
        cout << i << endl;
    }   
};

class B{
private:
    A a;
public:
    void accessAny(){
        a.i = 30;       
    }
};
11.虚函数

相当于 Java 的抽象,解决继承二义性问题

virtual 关键字,确保继承过来的相同属性或者函数,只存在一份拷贝

  • 当一个类具有一个纯虚函数,这个类就是抽象类
  • 抽象类不能实例化对象
  • 子类继承抽象类,必须要实现纯虚函数,如果没有,子类也是抽象类
class A{
public:
	char* name;
};

class B : virtual public A{ // 确保继承过来的相同属性或者函数,只存在一份拷贝
	
};

class C :virtual public A{

};

class D : public B ,public C
{

};
12.模板函数

相当于 Java 的泛型

template <typename T>// 模板函数的定义
T add(T number1, T number2){
	return number1 + number2;
}

void main(){
	
	int sum1 = add(1,2);

	cout << sum1 << endl;

	int sum2 = add(1.0, 2.0);

	cout << sum2 << endl;

	int sum3 = add(1.0f, 2.0f);

	cout << sum3 << endl;

	getchar();
}

当同名普通函数和模板函数同时存在的时候,优先会调用普通函数

13. 类型转换
  1. static_cast 类型转换,意图明显,增加可阅读性,静态转换 用于基本数据类型之间的转换,如把int转换成char
  2. const_cast 常量指针转换 用于修改常量的值
  3. reinterpret_cat 强制类型转换 ,用于转换任意类型,函数指针转型,不具备移植性
  4. dynamic_cast 动态转换 ,更安全,转换成功返回类型,失败返回空 ,
    必须要包含多态类型,和 static_cast 很类似,但是更安全,常用于子类类型转为父类类型

例:父类(Person)转子类(Student)

// 1.static_cast,一般用于转换有继承关系的类型 
Student *student = static_cast<Student*>(person);
// 2.reinterpret_cast,除了字符类型,各种类型的转换,常用
Student *student = reinterpret_cast<Student*>(person);
14.异常处理
  1. 在 c++ 层如果是自己写的代码或者调用别人的方法,记得要 try 住, 如果不 try 在 java 层 try 是没有意义的
  2. 如果异常需要往外抛给 java 层,一定要按照java层抛异常的方式
  3. 如果是自己写的 NDK 层的代码,最好抛自己写的异常,声明异常
  4. 如果是做 c++/c , 或者是帮 c/c++ 写代码,最好抛系统定义好的异常或者继承系统的异常
  5. 系统异常的体系 exception 基类 https://www.cnblogs.com/QG-whz/p/5136883.html
15.string 和 char* 互转
// string 转 char*
string str = "666";
const char* str2char = str.c_str();

// char* 转 string
char* char2str = "666"
string strValue(char2str);
16. 字符串的遍历
string str("1234567");

	// 1. 字符串的遍历
	for (int i = 0; i < str.length(); i++)
	{
		cout << str[i] << endl;
		// or
		cout << str.at(i) << endl;
	}

	// 2. 迭代器遍历
	for (string::iterator it = str.begin(); it < str.end(); it++)
	{
		cout << *it << endl;
	}
17.添加、删除、替换、查找、大小写转换
  • 添加
	string str1 = "123";
	string str2 = "456";

	str1 = str1 + str2;
	str1.append(str2);
  • 删除
	string str1 = "123456789";
    // 第一个参数:从哪里开始 ; 第二个参数:删除几个(默认值,字符串的结尾)
	str1.erase(0, 3);

	// 迭代器删除
	for (string::iterator it = str1.begin(); it<str1.begin() + 3; it++)
	{
	    // 删除一个字后都会从头开始计算
		str1.erase(it);
	}
  • 替换
    string str1 = "123456789";
	// 第一个参数:从哪里开始
	// 第二个参数:替换几个
	// 第三个参数:替换成谁
	str1.replace(0, 6, "0000");
	// 结果:前六个替换成 0000
  • 查找
    string str1 = "123abc123abc123";
	// (查找谁,从哪里开始),查不到返回 -1
	// int position = str1.find("123",0);
	// 从后面往前面查
	int position = str1.rfind("123");
#define D_SCL_SECURE_NO_WARNINGS
#include <iostream>
#include <algorithm>// STL 算法包
#include <cctype> // 用到一个函数指针,回调函数

    string str1 = "AAA abc BBB abc 123";
	// 转换成大写
	transform(str1.be gin(), str1.end(), str1.begin(), toupper);
    // 转换成小写
	transform(str1.begin(), str1.end(), str1.begin(), tolower);
18.引用知识系列
1. 引用加强
// 其实引用本质就是 指针
void change(int& number1){// 相当于 c 的 change(int* number1)
	number1 = 12;// 相当于 c 的 *number1 = 12; 
}
2. 引用的使用
void main(){
    int a = 10;
    // &是C++中的引用,引用:变量的另外一个别名,共用同个地址
    int &b = a; 
    cout << b << endl;
}
3. 引用与指针的区别
  • 不存在空引用,引用必须连接到一块合法的内存。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。
4. 引用与指针写法上的差异
struct Teacher{
    char* name;
    int age;
};
// 带有结构体指针的写法
void myprint(Teacher *t){
    cout << t->name << "," << t->age << endl;   
    //(*t).name 
}
// 带有结构体引用的写法
void myprint2(Teacher &t){
    cout << t.name << "," << t.age << endl;
    t.age = 21;
}
// 指针值交换
void swap_1(int *a, int *b){
    int c = 0;
    c = *a;
    *a = *b;
    *b = c;
}
// 引用值交换
void swap_2(int &a, int &b){
    int c = 0;
    c = a;
    a = b;
    b = c;
}
void main(){
    Teacher t;
    t.name = "Vegen";
    t.age = 24;
    // 指针的写法
    myprint(&t);
    // 引用的写法
    myprint2(t);

    int x = 10;
    int y = 20;
    // 指针的写法
    swap_1(&x, &y);
    // 引用的写法
    swap_2(x,y);
}
5. 引用的作用
  • 把引用作为参数:C++ 支持把引用作为参数传给函数,这比传一般的参数更安全
  • 把引用作为返回值:可以从 C++ 函数中返回引用,就像返回其他数据类型一样
6. 指针引用,代替二级指针
struct Teacher{
    char* name;
    int age;
};
// 引用的写法
void getTeacher(Teacher* &p){
    p = (Teacher*)malloc(sizeof(Teacher));
    p->age = 24;
}
// 二级指针的写法,原本应该这样写,但是已经被上面引用的写法代替了
void getTeacher(Teacher **p){
    Teacher *tmp = (Teacher*)malloc(sizeof(Teacher));
    tmp->age = 24;
    *p = tmp;
}

void main(){
    Teacher *t = NULL;
    //传递引用的指针t,相当于二级指针
    getTeacher(&t);
}
7. 常引用,类似于 java 中 final
// 常引用在方法中的引用
void myprint(const int &a){
    cout << a << endl;  
}

void main(){    
    // 引用必须要有值,不能为空,下面写法是错误的
    //const int a;
    //int &a = NULL;

    // 常引用属性使用一
    int a = 10, b = 9;
    const int &c = a;
    // 常引用属性使用二
    const int &d = 70;
}
8. 引用与指针的大小
struct Teacher{
    char name[20];
    int age;
};

void main(){
    Teacher t;
    // 引用
    Teacher &t1 = t;
    // 指针
    Teacher *p = &t;

    // 结果是 24,引用相当于变量的别名
    cout << sizeof(t1) << endl;
    // 结果是 4,指针只是存放的地址
    cout << sizeof(p) << endl;
    system("pause");
}
19. 运算符重载
1. 运算符重载的写法一
class Point{
public:
    int x;
    int y;
public:
    Point(int x = 0, int y = 0){
        this->x = x;
        this->y = y;
    }
    void myprint(){
        cout << x << "," << y << endl;
    }
};
// 重载 + 号
Point operator+(Point &p1, Point &p2){
    Point tmp(p1.x + p2.x, p1.y + p2.y);
    return tmp;
}
// 重载 - 号
Point operator-(Point &p1, Point &p2){
    Point tmp(p1.x - p2.x, p1.y - p2.y);
    return tmp;
}

void main(){
    Point p1(10,20);
    Point p2(20,10);
    Point p3 = p1 + p2;
    // 输出结果 30,30
    p3.myprint();
}
2. 运算符重载的写法二
class Point{
public:
    int x;
    int y;
public:
    Point(int x = 0, int y = 0){
        this->x = x;
        this->y = y;
    }
    // 成员函数,运算符重载
    Point operator+(Point &p2){
        Point tmp(this->x + p2.x, this->y + p2.y);
        return tmp;
    }
    void myprint(){
        cout << x << "," << y << endl;
    }
};

void main(){
    Point p1(10, 20);
    Point p2(20, 10);
    // 运算符的重载,本质还是函数调用
    //p1.operator+(p2)
    Point p3 = p1 + p2;
    p3.myprint();
}

持续更新中…

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值