HSH's Blogs

  • Home

  • Tags

  • Categories

  • Archives

C++ 类的对象属性成员初始化

Posted on 2019-03-03 | Edited on 2019-03-21 | In C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 构造函数的对象属性初始化列表
class Student{
private:
int id;
// 属性对象
// 为属性对象赋值只有两种方式,一种是创建时直接初始化
Teacher t0 = Teacher("lili");

// 另一种是在构造函数后面,挨个为它们初始化
Teacher t1;
Teacher t2;
public:
// 属性对象被要求必须初始化
//Student(int id){
// this->id = id;
//}

// 初始化时,同时初始化属性对象
Student(int id,char *t1_name):t1(t1_name),t2(t1_name){
this->id = id;
}
};

C++ 申请和释放动态堆内存

Posted on 2019-03-03 | Edited on 2019-03-21 | In C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void funcc(){

// C++的写法在开辟和释放内存时,会调用构造函数和析构函数
// 动态申请堆内存,保存对象
Teacher *t1 = new Teacher("Jack");
// 释放堆内存
delete t1;

// C的写法在开辟和释放时,不会调用构造函数和析构函数
Teacher *t2 = (Teacher*)malloc(sizeof(Teacher));
free(t2);
}

void funA(){
// C数组
int *p1 = (int*)malloc(sizeof(int)* 10);
p1[0] = 1;
free(p1);

// C++
int *p2 = new int[10];
p2[0] = 2;
delete[] p2;
}

NDK实现热修复的原理

Posted on 2019-03-03 | Edited on 2019-03-21 | In C++

C和C++中,内存分区:栈、堆、全局(静态、全局)、常量区(字符串)、程序代码区

同样,Java也分为五大区:
1,虚拟机栈(JVM Stack),存对象引用,基本数据类型
2,本地方法栈(Native Method Stack)
3,方法区(常量,静态变量以及编译后的代码)
4,程序计数器
5,直接内存

热修复原理:
每一个类对象中都保存有每个方法的地址指针,调用方法实际就是通过这些指针去方法区调用
对应的方法对象。热修复就是通过这种原理实现的。在不进行重启的情况下,将不同的方法加载
到内存中,然后,动态地将原来的对象方法指针重新指向该新的方法。

常量和常函数

Posted on 2019-03-03 | Edited on 2019-03-21 | In C++
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class B{
public:
int i;
int j;
int k;
void myPrintf(){
cout << "打印" << endl;
}

// 常函数,修饰的是this的内容,即是常量指针
// 不能在函数中修改this指向的变量的内容,使函数更加安全;
// const B* const this
void testConst()const{
//this->i = 10;
}

// B* const this
void testConst2(){
this->j = 10;
// this 是指针常量,不能修改指向
//this = (B*)0x00009;
}
};

void main(){
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;

// C/C++内存分区:栈、堆、全局(静态、全局)、常量区(字符串)、程序代码区
// 普通类属性与结构体中的属性有相同的内存布局(各属性大小的和),
// 静态成员存在于全局区中;
// 函数存在于程序代码区中;

// 普通对象能调用常函数
B b;
b.testConst();
b.testConst2();

const B b1;
// 常量对象只能调用常函数
// 在常函数中,当前对象指向的变量内容不能被修改,能够起到防止数据成员被非法访问
//b1.testConst2();

system("pause");
}

C++ 运算符重载

Posted on 2019-03-03 | Edited on 2019-03-21 | In C++

Java中的字符串String之所以能够相加,其实就是重载了+号运算符。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Point{
public:
int x;
int y;
public:
Point(int x = 0, int y = 0){
this->x = x;
this->y = y;
}

// 采用成员函数,定义运算符
Point operator-(Point &p){
Point temp(this->x - p.x, this->y - p.y);
return temp;
}
};

// 重载运算符+(本质还是函数调用)
// 为了减少中间传递的拷贝,参数使用引用;(也可以用指针,当时运算符使用起来更麻烦,需要使用*)
Point operator+(Point &p1, Point &p2){
Point temp(p1.x + p2.x, p1.y + p2.y);
return temp;
}

// 当属性私有时,重载的运算符无法访问到私有的属性,此时可以通过友元函数来实现
class Point{

// 声明友元函数式的操作符,这样操作符就能够访问私有属性
friend Point operator+(Point& p1, Point& p2){
Point temp(p1.x + p2.x, p1.y + p2.y);
return temp;
}

private:
int x;
int y;
public:
Point(int x, int y){
this->x = x;
this->y = y;
}
};

Point operator+(Point& p1, Point& p2){
Point temp(p1.x + p1.x, p1.y + p2.y);
return temp;
}

C++ 函数

Posted on 2019-02-24 | Edited on 2019-03-21 | In C++

默认参数

1
2
3
4
5
6
7
8
9
// 函数默认参数
// 一旦有一个参数设置默认值,后面的参数就必须跟着设置默认值
void testFun(int x, int y = 1, int z = 2){
}

void main(){
testFun(1);
system("pause");
}

可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 可变参数
void fundc(int i, ...){
// 拿到可变参数指针
va_list args_p;

// 开始读取时,需要指定哪个参数是可变参数前的最后一个参数
va_start(args_p, i);
// 拿到可变参数列表中的值
int a = va_arg(args_p, int);
char c = va_arg(args_p, char);
int b = va_arg(args_p, int);

cout << a << "," << c << "," << b << endl;
}

void main(){
fundc(1, 2, 'c', 12);
system("pause");
}

常函数

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class B{
public:
int i;
int j;
int k;
void myPrintf(){
cout << "打印" << endl;
}

// 常函数,修饰的是this的内容,即是常量指针
// 不能在函数中修改this指向的变量的内容,使函数更加安全;
// const B* const this
void testConst()const{
//this->i = 10;
}

// B* const this
void testConst2(){
this->j = 10;
// this 是指针常量,不能修改指向
//this = (B*)0x00009;
}
};

void main(){
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;

// C/C++内存分区:栈、堆、全局(静态、全局)、常量区(字符串)、程序代码区
// 普通类属性与结构体中的属性有相同的内存布局(各属性大小的和),
// 静态成员存在于全局区中;
// 函数存在于程序代码区中;

// 普通对象能调用常函数
B b;
b.testConst();
b.testConst2();

const B b1;
// 常量对象只能调用常函数
// 在常函数中,当前对象指向的变量内容不能被修改,能够起到防止数据成员被非法访问
//b1.testConst2();

system("pause");
}

C++ 类和结构体

Posted on 2019-02-20 | Edited on 2019-03-21 | In C++

调用顺序

如果一个类里面存在其他类的属性对象,那么构造函数和析构函数的调用顺序为:

创建时:先调用属性对象的构造函数,在执行自己的构造函数;
销毁时:先调用自己的析构函数,再执行属性对象的析构函数;

构造函数

构造函数在对象被销毁时调用;

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
29
30
31
32
33
class Student{
private:
char* name;
int age;
public:
// 无参构造函数(会覆盖父类的无参构造函数)
Student(){
cout << "调用无参构造函数" << endl;
this->name = (char*)malloc(sizeof(char)*100);
strcpy(name, "json");
}

// 定义有参的构造函数后,默认无参构造函数将被覆盖
Student(int age, char* name){
// 字符串后面会带有0作为结束符,所以要加1
int len = strlen(name);
this->name = (char*)malloc(len + 1);
strcpy(this->name, name);

this->age = age;
}
};

void main(){
// 只要声明变量,就会调用无参构造函数
//Student s;

// 调用有参构造函数
//Student s1(20, "lili");
//Student s3 = Student(20, "Damn");

system("pause");
}

析构函数

析构函数在对象被销毁时调用;

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
29
30
31
32
33
34
35
36
37
class Student{
private:
char* name;
int age;
public:
// 无参构造函数(会覆盖父类的无参构造函数)
Student(){
cout << "调用无参构造函数" << endl;
this->name = (char*)malloc(sizeof(char)*100);
strcpy(name, "json");
}

// 定义有参的构造函数后,默认无参构造函数将被覆盖
Student(int age, char* name){
// 字符串后面会带有0作为结束符,所以要加1
int len = strlen(name);
this->name = (char*)malloc(len + 1);
strcpy(this->name, name);

this->age = age;
}

// 对象被销毁时调用,可以做善后处理,比如,释放内存
~Student(){
cout << "调用析构函数" << endl;
free(name);
}
};

void func(){
Student t;
}

void main(){
//func();
system("pause");
}

拷贝构造函数

调用场景:
1,变量间的赋值: T t1 = t2;
2,函数调用时传参,值传递时调用;
3,作为函数返回值返回,给其他变量赋值时(与第1种情况同 )

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Student{
private:
char* name;
int age;
public:
// 无参构造函数(会覆盖父类的无参构造函数)
Student(){
cout << "调用无参构造函数" << endl;
this->name = (char*)malloc(sizeof(char)*100);
strcpy(name, "json");
}

// 定义有参的构造函数后,默认无参构造函数将被覆盖
Student(int age, char* name){
// 字符串后面会带有0作为结束符,所以要加1
int len = strlen(name);
this->name = (char*)malloc(len + 1);
strcpy(this->name, name);

this->age = age;
}

// 对象被销毁时调用,可以做善后处理,比如,释放内存
~Student(){
cout << "调用析构函数" << endl;
free(name);
}

// 拷贝构造函数
// 调用场景:
// 1,变量间的赋值: T t1 = t2;
// 2,函数调用时传参,值传递时调用;
// 3,作为函数返回值返回,给其他变量赋值时(与第1种情况同 )
// 默认情况下,拷贝构造函数是浅拷贝(值拷贝)
//Student(const Student &obj){
// this->name = obj.name;
// this->age = obj.age;
// cout << "copy obj" << endl;
//}

// 深拷贝
// 开辟一段新的内存,不只拷贝变量值,还会拷贝对应的地址内容
Student(const Student &obj){
int len = strlen(obj.name);
// 字符串后面会带有0作为结束符,所以要加1
this->name = (char*)malloc(len + 1);
strcpy(this->name, obj.name);
this->age = obj.age;
}
};

void main(){

Student s(30,"lilian");
Student s_cp = s;

copyPro();

system("pause");
}
浅拷贝问题

默认情况下,拷贝构造函数是浅拷贝,也就是值拷贝。当我们在构造函数中为成员变量开辟了动态
堆内存空间,进行浅拷贝后,拷贝变量也会指向同一个堆内存空间,此时,在变量销毁时,调用
析构函数,源变量和拷贝变量都会对同一内存空间进行释放,此时就会报错。
(浅拷贝是值拷贝,而指针的值是地址,所以拷贝出来都是指向同一个地址空间)

1
2
3
4
5
6
7
8
void copyPro(){
Student stu(10, "llll");

// stu2拷贝了stu的变量值,其成员变量name同时指向了同一个内存空间
Student stu2 = stu;

// 函数销毁时,stu和stu2都会调用析构函数去释放内存空间,导致重复释放的问题
}

深拷贝解决浅拷贝问题:
开辟一段新的内存,不只拷贝变量值,还会拷贝对应的地址内容;

1
2
3
4
5
6
7
Student(const Student &obj){
int len = strlen(obj.name);
// 字符串后面会带有0作为结束符,所以要加1
this->name = (char*)malloc(len + 1);
strcpy(this->name, obj.name);
this->age = obj.age;
}

C++ 类和结构体

Posted on 2019-02-20 | Edited on 2019-03-21 | In C++

类

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
#define PI 3.14

// C++ 的标准输入,输出头文件
#include<iostream>

class Circle{
private:
int r;
public:
double getArea(){
return r*r*PI;
}

void setR(int r){
this->r = r;
}
};

void main(){
// C++ 中,访问成员变量和成员方法时,类是不需要进行实例化的
// C++的类,作用更类似于结构体struct
Circle cl;
cl.setR(5);

cout << "面积为:" << cl.getArea() << endl;

system("pause");
}

类的大小

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class A{
public:
int i;
int j;
int k;
static int l;
};

class B{
public:
int i;
int j;
int k;
void myPrintf(){
cout << "打印" << endl;
}

// 常函数,修饰的是this的内容,即是常量指针
// 不能在函数中修改this指向的变量的内容,使函数更加安全;
// const B* const this
void testConst()const{
//this->i = 10;
}

// B* const this
void testConst2(){
this->j = 10;
// this 是指针常量,不能修改指向
//this = (B*)0x00009;
}
};

void main(){
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;

// C/C++内存分区:栈、堆、全局(静态、全局)、常量区(字符串)、程序代码区
// 普通类属性与结构体中的属性有相同的内存布局(各属性大小的和),
// 静态成员存在于全局区中;
// 函数存在于程序代码区中;
// 类的每一个对象,其普通属性存在各自的内存中,而函数则是共享的,放在程序代码区中,
// 当类进行调用时,通过this指针区分不同的对象调用;

// 普通对象能调用常函数
B b;
b.testConst();
b.testConst2();

const B b1;
// 常量对象只能调用常函数
// 在常函数中,当前对象指向的变量内容不能被修改,能够起到防止数据成员被非法访问
//b1.testConst2();

system("pause");
}

结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Teacher{
private:
char name[20];

public:
int age;
void say(){
cout << this->age << "岁;" << endl;
}

};

void main(){
// C++中的结构体是不需要在前面添加struct关键字的
// C++中,结构体与类最大的区别在于,类是可以进行继承的,而结构体是不行的;
Teacher t1;
t1.age = 20;
t1.say();
system("pause");
}

C++中的类定义步骤

1,在C++中,类和函数一般会声明在头文件中:
teacher.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// C++ 中,类和函数的声明都是放在头文件中的

// 确保该头文件在多次include时,只会被引入,编译一次
#pragma once

class MyTeacher{
private:
int age;
char* name;
public:
void setAge(int age);
int getAge();
void setName(char* name);
char* getName();
};

2,实现
teacher.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "teacher.h"

int MyTeacher::getAge(){
return this->age;
}

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

void MyTeacher::setName(char* name){
this->name = name;
}

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

3,使用

1
2
3
4
5
6
7
#include "teacher.h"

void main(){
MyTeacher t;
t.setName = "hsh";
t.setAge = 20;
}

C++ 引用和指针

Posted on 2019-02-20 | Edited on 2019-03-21 | In C++

引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// C++ 的标准输入,输出头文件
#include<iostream>

void main(){
// 变量的内容保存在特定的内存地址中,而变量名则可以认为是该地址名的别名
int a = 10;
// 在C++中,我们可以通过引用,为一个变量定义新的别名
int &b = a;

// &b表示了让b拷贝a的特性,
// 区别于指针,为p赋值a的地址;
// 实际引用和指针的效果是类似的!
// 指针可以理解为多保存一份变量的地址,变量值是地址,要通过地址取实际的变量值,需要多一步,用*,取地址运算
// 引用可以理解为省略了指针用*取值的步骤,因为引用和原变量名的作用是一致的,就是一个别名,直接通过引用就能
// 拿到实际变量的值(可以认为,引用比指针消耗更少,因为引用不需要再保存一份地址)
int *p = &a;

cout << b<<endl;
system("pause");
}

引用传递

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// C++ 的标准输入,输出头文件
#include<iostream>

void swap_1(int *a, int *b){
int c;
c = *a;
*a = *b;
*b = c;
}

// 通过引用进行交换
// a和b此时不再是进行值传递,而是通过了引用传递
// 此时的a和b就是调用处的传进来的参数,而不是新的变量
void swap_2(int &a, int &b){
int c;
c = a;
a = b;
b = c;
}

// 引用的主要作用是作为函数的参数和返回值:
// 1,通过引用进行调用,比通过指针调用更加简便,省去了添加*,进行取值
// 2,通过引用作为参数传递,可以直接传递原值,在函数中直接操作源参数,减少参数传递过程中产生的副本,提高效率
// 3,我们可以直接通过引用操作源变量,而指针必须通过取值(*p)才能间接操作,且从可读性上讲,指针更差;
void changeAge(Teacher &t){
t.age = 100;
}

void main(){
int x = 10, y = 20;

cout << "a = " << x << "b = " << y << endl;

// 通过指针进行值交换
swap_1(&x, &y);
cout << "a1 = " << x << "b1 = " << y << endl;

// 通过引用进行值交换
swap_2(x, y);
cout << "a2 = " << x << "b2 = " << y << endl;

system("pause");
}

指针引用

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void getTeacher(Teacher **p){
// 定义一个指针,并让它指向申请的内存空间的首地址,详单与为其赋值该内存空间地址
Teacher* t = (Teacher*)malloc(sizeof(Teacher));
// p是二级指针,其应该赋值指针变量的地址
//p = &t;
// *p表示取值其地址的内容,其原先保存的是指针的地址,取值则表示取值对应地址的指针变量
// t就是一个指针变量,可以直接赋值给*p
*p = t;
// 一级指针的取值
(*t).age = 20;
t->age = 21;

// 二级指针的取值:先取到一级指针变量值(*p),即指针保存的变量地址,然后取到一级指针指向的变量,及变量地址指向的变量内容(**p)
(*p)->name = "hsh";
(**p).name = "hsh";
}

// 定义一个指针引用,指针引用可以省略前面的*号
void getTeacher(Teacher *&p){
// 引用是指针变量的别名(sizeof引用得出的大小就是对应变量的大小),也可以认为,它就是一个一级指针变量
p = (Teacher*)malloc(sizeof(Teacher));

// 一级指针的赋值操作
p->name = "hsh";
p->age = 20;
}

// 此处如果传递一个一级指针变量,则无法对指针进行初始化
void getTeacher2(Teacher *t){
// 传递一个Teacher变量的地址同样是错误的,因为下面的操作实际是:
// 1,申请一段堆内存;
// 2,将堆内存的首地址赋给t;
// 也就是说,t被重新赋值了,原先传递进来的参数值被抹掉了
t = (Teacher*)malloc(sizeof(Teacher));
t->age = 10;
t->name = "hsh";
}

// 此处操作的是原来地址指向的变量(对其进行初始化)
// 可以影响到传递进来的实参
void getTeacher3(Teacher *t){
(*t) = { "hsh", 20 };
}

void main(){
//Teacher *t;
// 如果传递给二级指针,则必须传递地址
//getTeacher(&t);

// 如果形参是引用,则直接传递变量即可
// t就是一个指针变量,当它传递给一级指针的函数形参时,
// 仍然只是值传递,想要达到引用传递的目的,形参必须是一个二级指针或者指针引用
//getTeacher(t);

// 报错!!!,指针并没有被初始化
// 并不是说,指针作为形参,传递过去就是引用传递,因为指针本身也可以认为是变量的一种,
// 指针要实现引用传递的途径和普通变量是一样的,通过多一级的指针接收或者通过引用形参来接收
//getTeacher2(t);

//cout << t->name << t->age << endl;

Teacher teacher;
// 报错!!!,变量并没有被初始化
// 传递一个没有初始化的首地址作为形参,
// 如果函数中使用了动态内存申请来给指针赋值,同样会错误,因为,此时指针指向的地址已经被重新赋值了
//getTeacher2(&teacher);
//cout << teacher.name << teacher.age << endl;

// 正确!!!
// 只是对原来地址指向的变量进行初始化,则可以
getTeacher3(&teacher);
cout << teacher.name << teacher.age << endl;

system("pause");
}

指针常量与常量指针

1
2
3
4
5
6
7
8
9
10
11
void main(){
int a = 1;

// 指针常量,指针的常量,不能改变地址的指针,但可以修改它指向的内容
// const修饰的是指针,所以指针内容不能改变,即地址不能改变
int const *p1 = &a;

// 常量指针,指向常量的指针,内容不能修改
// const修饰的是变量类型,所以变量类型的内容常量化
const int *p2 = &a;
}

常引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main(){
int a = 10;
int c = 2;
// 常引用是不能重新赋值的
const int &b = a;
//b = c;
// 跟上面是一样的,没区别
int const &d = a;
//d = c;
//&d = 1;

// 固定为右边的字面量,实际就是定义常量
const int &e = 10;
}

C++ 与C 的区别

Posted on 2019-02-20 | Edited on 2019-03-21 | In C++

bool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// C++ 的标准输入,输出头文件
#include<iostream>

void main(){
// C++ 比C多了一种基本数据类型,bool
// true和false对应C中的1和0
//bool isSingle = true;
//bool isSingle = 3;
//bool isSingle = -3;
// 0表示false,非0表示true
bool isSingle = 0;
if (isSingle){
cout << "single" << endl;
}
else{
cout << "not single" << endl;
}

system("pause");
}

三元表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// C++ 的标准输入,输出头文件
#include<iostream>

void main(){

// 在C++中,三元运算符?:并不是表示普通的逻辑运算语句(语句(statement):不返回值的代码块,或可执行代码行),而是可以是表达式
// (表达式:允许有返回值),它的返回值是逻辑运算的结果
// 在C中,三元运算符仍然只是一个语句!
int a = 1, b = 3;
// 对逻辑运算结果进行赋值
// a = 1,b= 10
(a > b ? a : b) = 10;
cout << "a = " << a << ",b= " << b << endl;

system("pause");
}
123…8

黄声焕

71 posts
9 categories
21 tags
© 2019 黄声焕
Powered by Hexo v3.7.1
|
Theme — NexT.Gemini v6.3.0