NDK-c++基础 三
c++语言基础,
c++中能运行c语言,但c语言不能运行c++
C++语言面向对象 + 标准特性
C语言面向过程,函数+结构体
C++里面可以运行C语言,可以调用C语言,反之 就不行C语言无法运行C++
以后我们85%以上 都是 用C++去写功能
在c语言中基本运行需要引入#include <stdio.h>
在c++中需要引入#include <iostream>
打印语句
- cout *
1
2
3
4
5
6
7cout << "HELLO WORLD!" << endl;
cout << "YYYYYY\n";
cout << "Line1\n"
<< "Line2\n"
<< "line3\n";
需要引入using namespace std;
namespace 命名空间
<< 是一个操作重载符,cout 更正确的写法是: std::cout
因为我们已经引入 命名空间,所以可以省略为: cout
常量
在c或者c++中,常量使用 const 声明,但是在c语言中,常量是个伪命题,因为c中可以直接修改指针
1 | const int i = 90; |
i 已经被定义成常量
上面这段代码在c语言中可以运行,并能修改i地址处的值为10,达到了修改常量值的目的。
但是在c++中会报错,ide工具就会直接报错:Cannot initialize a variable of type ‘int ‘ with an rvalue of type ‘const int ‘
引用
- & *
c++中 & 标示引用类型。
1 | int i = 0; |
打印结果:
a地址:0x7ffeefbff4c8
i地址:0x7ffeefbff4c8
c地址:0x7ffeefbff4bc
d地址:0x7ffeefbff4b8
因为 a 是 i 的引用。 a 指向 i, 所以 a 与 i 的地址一致。
而c 与 d 不同,c、d都是一个新的对于9的引用。
类
c++ 申明类使用关键字 class1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Person {
private:
int age;
char * name;
public:
void setAge(int age) {
this->age = age;
}
void setName(char * name) {
this->name = name;
}
int getAge(){
return this->age;
}
char * getName(){
return this->name;
}
};
如此一个基本类就定义完成,成员变量使用private修饰,在外部不可访问,提供public修饰的set、get函数来操作成员变量。
构造函数
1 | public: |
如上所示,第2个与第三个其实效果是一样的,都是对成员变量直接赋值。
还有构造函数的相互调用问题:1
2
3
4Person(int age, char * name): Person(age){
this->name = name;
cout << "两个参数的构造函数: name" << endl;
}
调用两个参数的构造函数时,会调用到一个参数的构造函数,
- 而且打印会先打印一个参数的构造函数的日志,然后再打印两个参数构造函数的日志 *
类的引用
我们在上面申明了一个类,然后我们可以根据类申明引用
1 | cout << "HELLO TESTCLASS" << endl; |
打印结果如下:HELLO TESTCLASS
空构造函数
age:29, name:justin
一个参数的构造函数: age
两个参数的构造函数: name
age:24, name:novia
在上述代码中,生成的person都是普通的引用,数据内存都是在栈区。
接下来看一下在C++中如何使用堆区内存
堆内存
与c语言不同,c++中使用 new 生成的对象都存放在堆内存中,而存放在堆内存中的数据,需要自己手动
c++使用 delete 释放内存。
1 | Person * person = new Person(29, "justin"); |
QA: 为何栈区内存不需要释放内存
我们都知道函数在出栈时,会释放栈区内存,那为何不需要使用delete释放。
其实函数并不是没有调用delete来释放,在函数出栈时,有静默的调用了delete,来释放栈区的内存QA:为何调用了delete后,还能调用指针并获取到值
因为delete的原理并不是直接清空数据,而是标记该块内存地址为可用,当应用有向堆内存重新申请内存时,就可能申请到这块内存,此时数据才会被修改,
如果此块内存一直没有被申请到时,调用该指针后,还是能访问到之前的数据的。
当调用 delete 时,该指针即成为悬空指针,继续调用就有可能出现异常,规范用法时指向NULL;
析构函数
当对象内存被回收时,会执行对象的析构函数,类似与java对象的finalize函数
但与finalize函数不同的是,java对象可以在finalize中实现自我拯救,
c++析构函数中,不能实现自我拯救,主要是用于一些数据清除、销毁工作,如解绑、释放堆内存空间,是的,对象中如果也有开辟堆内存时,就需要在此处释放,不然该内存会被一直占用。
析构函数的格式:
1 | ~Person(){ |
拷贝构造函数
1 | // 拷贝构造函数 |
当执行复制拷贝时,就会执行复制拷贝函数,系统默认的复制拷贝函数会自动完成复制值,
手动重写复制拷贝函数时,我们也需要手动赋值
此时传入的person就是旧对象,this即是拷贝后生成的新对象
拷贝的场景
- person1 = person2
1 | Person person1(24, "novia"); |
当执行 Person person2 = person1; 即会执行 拷贝构造函数 来对person2赋值,
- 参数传递
1 | void test(Person person) { |
打印结果:
一个参数的构造函数: age
两个参数的构造函数: name
拷贝构造函数
Person1.age:24, Person1.name:novia
Person2.age:24, Person2.name:novia
person1地址—0x7ffeefbff4b8 ,person2地址—0x7ffeefbff4a8
拷贝构造函数
test函数中,参数person的地址: 0x7ffeefbff488
析构函数
析构函数
析构函数
可以看到test函数中打印的person地址 与我们调用 test 函数传递的 person2 地址并不相同。这被叫做 行参
像避免上面的操作,避免产生大量的副本对象,占用内存,有几个办法,
1、使用引用
在上面就讲过引用,引用也是直接执行地址的,所以当我们使用引用时,就不会重新拷贝赋值了。
修改一下test函数的入参1
2
3void test(Person & person) {
cout << "test函数中,参数person的地址: " << &person << endl;
}2、运算符重载
重写 = 运算符,使之返回之前的地址。
- 注意事项 *
1
2
3Person person2 = person1; // 1
Person person2;
person2 = person1; // 2
方式2是不会调用 拷贝构造函数, 方式1 才会。
为什么?
因为person2其实已经调用了无参构造函数,
Person person2; 这一句已经调用了无参构造函数,
person2 = person1; 只是重新赋值而已。
常量指针 常量引用 指针常量 常量指针常量
之前讲过常量,代表不可更改值
常量下还有 常量指针 常量引用 指针常量 常量指针常量
注意常量修饰的位置
常量指针
1 | int number = 10; |
const 用来修饰类型,这个指针即为常量指针
声明了常量指针 a_p,
*a_p = 100; 会提示错误:Read-only variable is not assignable
常量代表只读, 常量指针 常量是修饰值的,不可以直接修改指针指向的数据,只能通过修改指针指向的地址来修改值
常量引用
1 | const int & a = 18; |
const 用来修饰引用,这个引用即为 常量引用
常量引用指向的数据也是不能直接修改的。
拷贝构造函数 重的参数就是一个常量引用。
指针常量
1 | int * const b_p = &number; |
const 用来修饰指针,这个指针即是 指针常量
上面的输出结果:
*bp指向的值为190
指针常量,不可修改指向的地址,但可以修改指针指向的值
常量指针常量
常量指针常量 其实相当于 常量指针 与 指针常量 两个的合并。1
2
3const int * const c_p = &number;
c_p = &number2; // 报错
*c_p = 99; // 报错
常量指针常量 既不能修改指向的地址,也不能修改指针指向的数据,即完全只读