目录
1.0 这篇文章讲什么及使用事项
1.1 C++对C的扩展
1.1.1 双冒号作用域运算符
1.1.2 namespace命名空间
1.1.3 using声明及using编译指令
1.1.4 C++对C的增强---全局变量、函数、类型转换、结构体、bool类型、三目运算符
1.1.5 C++对C的增强---const(修饰全局局部变量区别、分配内存的情况)
1.1.6 C++引用(重点)
1.1.7 内联函数 inline
1.1.8 函数默认参数和占位参数
1.1.9 函数重载(重点)
1.1.10 extern C
1.2 类和对象
1.2.1 小例子
1.2.2 C++ struct与C struct区别
1.2.3 C++类与C++struct区别及类访问权限(重点)
1.2.4 案例复习:点和圆的关系(单多文件联合编译)
1.2.5 构造函数和析构函数(重点)
1.2.6 类对象作为类中成员
1.2.7 new、delete关键字(动态对象创建)(重点)
1.2.8 静态成员变量与函数(static关键字)
1.2.9 单例模式
1.2.10 this指针 (重点)
1.2.11 空指针访问成员函数出现的问题
1.2.12 常函数和常对象
1.2.13 友元
1.2.14 案例:数组类封装---复习1.1及1.2节
1.3 运算符重载 (可以不看....对实际没啥用处,了解即可)
1.3.1 运算符重载的意义
1.3.2 加法运算符重载
1.3.3 左移运算符重载
1.3.4 前置后置递增运算符重载
1.3.5 指针运算符重载
1.3.6 赋值运算符重载
1.3.7 []重载
本人已经系统地学过C/C++及数据结构、算法设计、计算机系统等课程,对代码如何通过预处理、汇编编译链接有深入理解,此篇为复习笔记和以后以备不时复习使用。在学习C++前,希望读者系统地学习遍C语言,理解C的指针、结构体思想才能更好的学习C++。
本博文内容偏于结论化,适合复习使用,不适合初学者使用。
Ⅰ.查看下段代码,估计其运行结果
#include
using namespace std; int year = 23; void test01() { int year = 50000; cout << "my year = " << year < Ⅲ.解释:
双冒号代表作用域,如果在一个变量前加双冒号在双冒号前啥也不加代表全局作用域。
1.一个命名冲突的例子
Ⅰ.先来简单看一个例子,我们创建game.h文件和其关联的game.c文件
// // Created by lhw on 2022/6/2. // #ifndef DAY1E_GAME1_H #define DAY1E_GAME1_H #include
using namespace std; void goatk(); #endif //DAY1E_GAME1_H // // Created by lhw on 2022/6/2. // #include "game1.h" void goatk() { cout << "aaaaaaaa" <
在主函数中调用:
#include
#include "game1.h" using namespace std; void test01() { goatk(); } int main() { test01(); return 0; } 执行程序,发现执行成功。
Ⅱ.这没什么问题,但是我如果建立了一个game2.h及其相关的game2.cpp文件,也包含goatk调用方法,再在main函数中调用,会出现什么结果呢?
代码game2.h:
// // Created by lhw on 2022/6/2. // #ifndef DAY1E_GAME2_H #define DAY1E_GAME2_H #include
using namespace std; void goatk(); #endif //DAY1E_GAME2_H 代码game2.cpp
// // Created by lhw on 2022/6/2. // #include "game2.h" void goatk() { cout << "bbbbbb" <
代码main.cpp
#include
#include "game1.h" #include "game2.h" using namespace std; void test01() { goatk(); } int main() { test01(); return 0; } 此时会输出什么呢?学过计算机系统的兄弟们应该知道,两个强符号同时出现会导致链接错误,此时编译器会报错。
Ⅲ.如何改正呢?用命名空间:解决命名冲突
我们更正.h与其对应的.cpp文件,加上命名空间,并在主函数中调用,发现已经解决问题了
要点:我们在用namespace关键字将方法归属于某一作用域,调用该方法时要加作用域。
game.h
// // Created by lhw on 2022/6/2. // #ifndef DAY1E_GAME1_H #define DAY1E_GAME1_H #include
using namespace std; namespace game1atk { void goatk(); } #endif //DAY1E_GAME1_H game.cpp
// // Created by lhw on 2022/6/2. // #include "game1.h" void game1atk::goatk() { cout << "aaaaaaaa" <
main.cpp
#include
#include "game1.h" #include "game2.h" using namespace std; void test01() { game1atk:: goatk(); game2atk:: goatk(); } int main() { test01(); return 0; }
2.命名空间下可以放什么:
变量、函数、结构体类
3.命名空间必须声明在全局作用域
void test02()
{
cout << "B variable a is "<< B::a << endl;
cout << "C variable a is "<< B::C::a << endl;
}
在函数体中声明nameapace会报错
4.命名空间可以嵌套命名空间
Ⅰ.如下,如何访问两个a呢?
namespace B { int a = 10; namespace C { int a = 20; } }
Ⅱ.通过剥洋葱的方法访问:
void test02() { cout << "B variable a is "<< B::a << endl; cout << "C variable a is "<< B::C::a << endl; }
5.命名空间是开放的,可以随时添加新成员
Ⅰ.code
namespace B { int a = 10; namespace C { int a = 20; } } namespace B { int c = 79; }
Ⅱ.演示
void test03() { cout << "c = " << B::c <
c = 79 a = 10
Ⅲ.结论:
两次声明命名空间并不是下面的给上面的覆盖了,而是向其中添加新的元素(与C语言结构体是不是很不一样啊)
6.命名空间可以是匿名的(很少去用)
Ⅰ.code
namespace { int anonymous_x = 10; int anonymous_y = 200; } void test06() { cout << "x = " << anonymous_x << endl ; cout << "y = " << ::anonymous_y << endl ; }
Ⅱ.结论:
匿名的命名空间的两种调用方式,但究其本质,相当于声明了 static 的变量,也就是相当于: static int anonymous_x = 10; ,不过好像很少用,有个印象就可。
7.命名空间可以起别名
Ⅰ.code:
namespace LONGNAME { int longname_x = 100; } void test07() { namespace shortname = LONGNAME; cout << LONGNAME::longname_x << endl ; cout << shortname::longname_x << endl ; }
void test07() { namespace shortname = LONGNAME; LONGNAME::longname_x = 20 ; cout << LONGNAME::longname_x << endl ; cout << shortname::longname_x << endl ; }
Ⅱ.结论
改名后原名和改过之后的名字都能用,且两块都指向一块内存空间。
1.using的作用
作用域控制
2.using声明具体介绍
Ⅰ.code①
namespace SCHOOL { int nefu = 1 ; int qsing = 2 ; int peking = 3 ; } void test08() { //cout << nefu << "is the best school" << endl ; cout << SCHOOL::nefu << "is the best school" << endl ; }
这里我们声明了一个SCHOOL的命名空间,下设三个变量,然后我们在函数打印其中一个值。我们发现,如果不写命名空间直接调用会报错。
Ⅱ.code②
void test09() { using SCHOOL::nefu; cout << SCHOOL::nefu << "is the best school" << endl ; cout << nefu << "is the best school" << endl ; }
使用了using声明就能实现访问。
注意:若在test09中又声明了一个nefu变量会报错!多次声明报错!当using与就近原则同时声明会报错!尽量避免!
3.using编译指令
Ⅰ.code:
两个语句都能正常打印void test10() { using namespace SCHOOL; cout << SCHOOL::nefu << "is the best school" << endl ; cout << nefu << "is the best school" << endl ; }
但又有一个问题,像test09一样在test10下再声明一个nefu会出错吗?
void test10() { int nefu = 0 ; using namespace SCHOOL; cout << SCHOOL::nefu << "is the best school" << endl ; cout << nefu << "is the best school" << endl ; }
意外的是,没有出错,打印结果是0;
Ⅱ.结论
当就近原则和using编译指令同时出现时,就近原则优先。
Ⅰ.对全局变量检测增强:
全局变量不能重定义Ⅱ.对函数检测增强
检测返回值,形参类型,函数调用个数检测,返回值检测报错
Ⅲ.类型转换增强
Ⅳ.结构体增强(C++下结构体可以有函数)
Ⅴ.c++增加了bool类型(真假 true false)
Ⅵ.c++增加了对三木运算符的增强
a > b ? a : b = 100
这个在c下是跑不通的,因为c语言下返回的是一个值,如果a>b返回a,
而在c++下则是 如果a>b 则赋值a为100 如果b>a 则赋值b =100 (结合性)
Ⅰ.关于const修饰全局变量和局部变量的区别
C语言中:
全局变量进行const修饰,比如 const int a = 10,无论通过指针修改或是直接修改值都不行,因为学过计算机系统的朋友们知道,全局变量是放在内存的.data节中,受保护;局部变量可以通过指针修改,因为其放在堆栈区。C语言下const修饰的局部变量是伪常量,不可以初始化数组。
C++语言中:
第一种赋值方式错误,不可直接修改const修饰的局部变量。
第二种:注意,为什么要进行强制转换?我们在Ⅲ中说的类型转换增强就是这个意思,需要在将不同形式的数据转换后显示指明转换的类型,但也运行失败,因此C++和C语言中对于const修饰的全局变量都不能进行修改其值。
code①:
const int a = 0; void test11() { //const_a = 10; int * const_p = (int*)(&const_a); *const_p = 11 ; }
那对于局部变量呢?
直接修改还是不行,区别在于间接修改,看一下下面代码和执行结果吧!
void test12() { const int const_a = 10; int * p = (int*)(& const_a); *p = 11; cout << "const_a = " << const_a ; }
这说明,在C++下用const修饰的局部变量不能通过指针修改其值。
结论:C语言和C++用const修饰的全局变量由于存放在常量区受保护无法修改其值,但C语言可修改const修饰的局部变量的值,C++不可以,这也说明C++语言下const修饰的变量是常量,而不是伪常量,可以用来初始化数组。
下面来画图解释一下为什么C++中用const修饰的局部变量不能通过指针修改其值:
Ⅱ.C语言下const修符的全局变量是默认外部链接属性
什么意思呢,比如在同一目录下建立一个.c文件,那个.c文件里面有一个全局变量,
const int a =100;
则在另一个.c文件中声明extern便可访问。
extern const a ;
而在C++中const修饰的全局变量默认是内部符号,即只有在本文件中才能访问。
如果想在其他文件中访问,要显式的加上extern属性。
extern const int a = 100;
才能在另一个文件中访问。
Ⅲ.const分配内存的情况
①对const变量取地址会分配内存
void test12() { const int const_a = 10; int * p = (int*)(& const_a); }
②用变量初始化const
void test13() { int b = 99; const int const_a = b; int * p = (int*)(& const_a); *p = 11; cout << "const_a = " << const_a ; }
运行结果:发现const修改的值被修改成功,说明用变量初始化const会给const分配内存导致其值被修改。
/home/lhw/桌面/C++/day1e/cmake-build-debug/day1e const_a = 11 进程已结束,退出代码为 0
③对于自定义的数据类型
void test14() { const Person P{}; Person * person_p = (Person *)(&P); //P.age = 10 ; person_p ->name = "nefu" ; (*person_p).age = 20 ; cout << "name is " << P.name << endl << "age is " << P.age ; }
1.引用的目的
起别名,和c++指针用法一致。
2.引用注意事项
Ⅰ.引用必须要初始化 int * b; &b=a; 这么用是错误的,int &b = a;
Ⅱ.引用初始化后不可以再引用其他变量:
int * b = &a; b = c;
这是赋值操作....,将c变量的地址赋值给b,使b和c都指向的是a变量的地址。
Ⅲ.如何对数组建立引用
①直接建立引用
void test15() { int arr[10]; int(&parr)[10] = arr; }
②先定义出数组的类型,再通过类型进行引用
定义一个10个int类型的数组
typedef int(ARRAY_TYPE)[10];
void test15() { int arr[10]; typedef int(ARRAY_TYPE)[10]; ARRAY_TYPE &prr = arr; for(int i=0; i<10; i++) { arr[i] = 100 + i ; } for(int i = 0; i<10 ; i++) { cout << prr[i] << endl ; } }
/home/lhw/桌面/C++/day1e/cmake-build-debug/day1e 100 101 102 103 104 105 106 107 108 109
3. 函数传递方式以及引用事项
经典三种方式交换a,b两个数
Ⅰ.值传递(略)
Ⅱ.指针传递(略)
Ⅲ.引用传递
void test16(int &a,int &b) { int temp = a; a = b; b = temp; cout << "a = " << a << endl << "b = " << b << endl; } int main() { int a = 10; int b = 15; test16(a,b); return 0; }
/home/lhw/桌面/C++/day1e/cmake-build-debug/day1e a = 15 b = 10 进程已结束,退出代码为 0
引用必须引用合法的空间,比如不能引用常量。
不要返回局部变量的引用:局部变量超出其作用域后内容就被释放掉了,再去读取它就是脏数据了。
int& func() { int a = 1; return a; } void test17() { int& ret = func(); cout << "ret = " << ret <
/home/lhw/桌面/C++/day1e/cmake-build-debug/day1e ret = 进程已结束,退出代码为 0
但如果用static去修饰则可以这样用,因为static修饰的局部变量数据是放在内存的.data节的,其作用域与main作用域相同。
int& func2() { static int a = 1; return a; } void test18() { int& ret = func2(); cout << "ret = " << ret << endl; } int main() { test18(); return 0; }
/home/lhw/桌面/C++/day1e/cmake-build-debug/day1e ret = 1 进程已结束,退出代码为 0
特别的,如果函数的返回值是引用,则函数的调用可以作为左值:
void test18() { int& ret = func2(); cout << "ret = " << ret << endl; func2() = 1000 ; cout << "ret = " << ret << endl; }
/home/lhw/桌面/C++/day1e/cmake-build-debug/day1e ret = 1 ret = 1000 进程已结束,退出代码为 0
4.引用的本质:
引用的本质在C++内部实现是一个指针常量。
什么是指针常量:暂且这么认为,指针指向不可以改。
int & rcf = val ; 等价于 int * const rcf = &val ;
void test19(int & ref) //trans int * const ref = &a; { ref = 100; } int main() { int a = 10; int & aref = a; //int * const aref = &a; aref = 20 ; //*aref = 20 cout << "a = " << a << endl ; cout << "aref = " << aref << endl ; test19(aref); cout << "a = " << a << endl ; cout << "aref = " << aref << endl ; return 0; }
/home/lhw/桌面/C++/day1e/cmake-build-debug/day1e a = 20 aref = 20 a = 100 aref = 100 进程已结束,退出代码为 0
5.指针的引用
void allocatemermory(Person * &p) { p = (Person * )malloc(sizeof (Person)); } void test20() { Person * p = NULL ; allocatemermory(p); }
解释:在test20里面,实参p是person * 类型的,传递给allocatemermory中,
其形参代表的是对Person * 类型变量的引用,因此直接修改p就行,不用引入高级指针操作,为方便理解,下面的代码是只用指针传递变量的。
void allocatemermory2(Person ** p) { (*p) = (Person *)malloc(sizeof (Person)); } void test21() { Person * p = NULL ; allocatemermory2(&p); }
这里为什么要传入p的地址:因为要修改p的内容,直接将p传进去,allocatemermory2在创建时就会新在内存里开辟出自己的栈帧,用局部变量接收它,用局部变量操作,待结束后,就释放栈帧,原来的p没有任何改变;于是,要改变p的值,必须传入其地址。
Person ** p的值是指向 Person * p的地址,对其解引用就是指向p的内存,用p去申请堆空间,就不会造成函数一结束啥也做不了的情况了。
6.常量的引用
void test22() { //int & ref = 10 ; error: const int & ref = 10 ; //int temp = 10 ; const int & ref = temp ; int * p = (int*)(&ref); * p =10000; cout << "ref = " << ref << endl ; }
/home/lhw/桌面/C++/day1e/cmake-build-debug/day1e ref = 10000 进程已结束,退出代码为 0
上面这么用的场景不多,下面介绍常量的引用经常在程序中怎么用:
一般用于工程中禁止他人修改自己的参数时使用:看两组代码做对比:code①
void showvalue1(int & a) { a++ ; cout << "a = " << a << endl ; } void test23() { int a = 100 ; showvalue1(a); cout << "test23_a = " << a << endl ; } int main() { test23(); return 0; }
/home/lhw/桌面/C++/day1e/cmake-build-debug/day1e a = 101 test23_a = 101 进程已结束,退出代码为 0
我们发现,如果他人在其他接口恶意修改我们的数据是防不胜防的;我们修改代码如下,showvalue函数中就不可修改a的值了。(通过指针也可改......)
1.为什么需要内联函数
宏缺陷1:必须要加括号才能使运算完整
#define myadd(x,y) x+y void test03() { int a = 10; int b = 20; int ret = myadd(a,b) * 20; // a + b * 20 = 10 + 20 * 20 = 410 != 600 cout << "ret = " << ret << endl; }
/home/lhw/桌面/C++/day2/cmake-build-debug/day2 ret = 410
改进:
#define myadd(x,y) ((x)+(y)) void test03() { int a = 10; int b = 20; int ret = myadd(a,b) * 20; // a + b * 20 = 10 + 20 * 20 = 410 != 600 cout << "ret = " << ret << endl; }
宏缺陷2:加括号了,或许还和预期不符合
#define mycompare(a,b) (((a)<(b))?(a):(b)) void test04() { int a = 10; int b = 20; int ret = mycompare(++a,b); cout << "ret = " << ret << endl ; }
预期结果是11,但结果是12,这是怎么回事呢,我们展开宏定义:
( (++a) < b ) ? (++a) :b a自增了两次,a当然是12。
2.为解决这些问题,我们引出了内联函数
在c++中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。
在普通函数(非成员函数)函数前面加上inline关键字使之成为内联函数。但是必须
注意必须函数体和声明结合在一起,否则编译器将它作为普通函数来对待。即函数的声明和实现都需要添加inline关键字。可以解决普通函数的缺陷又可实现宏定义的优势。
inline int funadd2num(int a ,int b); inline int funadd2num(int a ,int b) { return a > b ? a : b; } int main() { int a = 30; int b = 20; cout << funadd2num(++a , b) << endl; return 0; }
/home/lhw/桌面/C++/day2/cmake-build-debug/day2 31 进程已结束,退出代码为 0
3.类内部的内联函数:
为了定义内联函数,通常必须在函数定义前面放一个 inline关键字。但是在类内部定
义内联函数时并不是必须的。任何在类内部定义的函数自动成为内联函数。
4.C++内联编译会有一些限制,以下情况编译器可能考虑不会将函数进行内联编译:
不能存在任何形式的循环语向
不能存在过多的条件判断语句
函数体不能过于庞大
不能对函数进行取址操作
1.默认参数
如果调用函数不传值,则传入被调用函数默认值:
int add2int(int a = 10 ,int b = 10) { return a+b; } int main() { cout << add2int(2) << endl; cout << add2int(2,3) << endl ; return 0; }
/home/lhw/桌面/C++/day2/cmake-build-debug/day2 12 5 进程已结束,退出代码为 0
注意事项:
如果有一个位置有了默认参数,那么从这个位置起,从左到右都必须有默认值!!
函数的声明和实现只能有一个有默认参数!
2.占位参数
只写一个类型进行占位,调用时候必须要传入占位值。
那么占位参数有什么用处呢?往后再说。
1.什么是函数重载?
在传统c语言中,函数名必须是唯一的,程序中不允许出现同名的函数。在c++中是
允许出现同名的函数,这种现象称为函数重载。
2. 函数重载条件
Ⅰ.在同一作用域下
Ⅱ.函数的名称要相同
Ⅲ.参数的个数、类型、顺序可以不同
Ⅳ.返回值不可以作为重载条件
3.code
void funchongzai(int a ,float b) { cout << "int float" << endl; } void funchongzai(float b,int a) { cout << "float int" << endl; } void funchongzai(int a) { cout << "int" << endl; } int main() { funchongzai(2); funchongzai(4.4,2); funchongzai(2,4.4); return 0; }
/home/lhw/桌面/C++/day2/cmake-build-debug/day2 int float int int float 进程已结束,退出代码为 0
4.函数重载中引用的两个版本
void myfuc(int & a) { cout << "myfuc int & a" << endl; } void myfuc(const int &a ) { cout << "myfuc const int & a" << endl; } int main() { int num = 10 ; myfuc(num); //int& a = num; myfuc(10); //const int & a =10 int temp = 10 int & a = temp return 0; }
/home/lhw/桌面/C++/day2/cmake-build-debug/day2 myfuc int & a myfuc const int & a
5.函数重载遇见默认参数时,也会出现二义性。
void test07(int a) { } void test07(int a,int b=10) { }
这就存在了二义性
6. 函数重载实现原理:
编译器为了实现函数重载,也是默认为我们做了一些幕后的工作,编译器用不同的参数
类型来修饰不同的函数名,比如void func();编译器可能会将函数名修饰成_func,当编译
器碰到void func(int x),编译器可能将函数名修饰为_func_int,当编译器碰到void func(int x,char c),编译器可能会将函数名修饰为_func_int_char。使用”可能”这个是因为编译器如何修饰重载的函数名称并没有一个统一的标准,所以不同的编译器可能会产生不同的内部名。
看一个案例吧:
void test08() { show(); } int main() { test08(); return 0; }
不可以执行,报错。找不到show函数
====================[ 构建 | day2 | Debug ]======================================= /opt/clion/bin/cmake/linux/bin/cmake --build /home/lhw/桌面/C++/day2/cmake-build-debug --target day2 -- -j 12 Scanning dependencies of target day2 [ 33%] Building C object CMakeFiles/day2.dir/test.c.o [ 66%] Linking CXX executable day2 /usr/bin/ld: CMakeFiles/day2.dir/main.cpp.o: in function `test08()': /home/lhw/桌面/C++/day2/main.cpp:118: undefined reference to `show()' collect2: error: ld returned 1 exit status make[3]: *** [CMakeFiles/day2.dir/build.make:118:day2] 错误 1 make[2]: *** [CMakeFiles/Makefile2:95:CMakeFiles/day2.dir/all] 错误 2 make[1]: *** [CMakeFiles/Makefile2:102:CMakeFiles/day2.dir/rule] 错误 2 make: *** [Makefile:137:day2] 错误 2
那么为什么呢??C语言没有函数重载,C++有函数重载会重新修饰函数名,因此无法链接,解决方法1:
extern "C" void show();
加入这么一行代码,表示要在外部文件通过C的链接方式找到show函数。
再将test.h头文件包含取消,再次编译运行,成功打印出。
但我们很少这么用,因为如果.c文件中有很多函数,岂不是要一个一个extern列出来?
解决方法2:
我们经常在C文件下做手脚:
test.h
// // Created by lhw on 2022/6/4. // #ifdef __cplusplus extern "C" { #endif #include "stdio.h" void show(); #ifdef cplusplus } #endif
在main.cpp中,只需正常包含头文件test.h即可。
!!!注意,第二种方式这会让c++文件中的函数重载失效:
因此,具体情况具体分析,如果你的c++文件中写了函数重载,请用方法1;不然用方法2;
const double PI = 3.1415926; class Circle { public: int m_R; //zhouchang double calzhouchang() { return 2 * PI * m_R; } }; void test01() { Circle c1; //通过类创建一个对象(实例化一个对象) c1.m_R = 10 ; cout << "square is :" << c1.calzhouchang() << endl ; } int main() { test01(); return 0; }
/home/lhw/桌面/C++/day2/cmake-build-debug/day2 square is :62.8319
类中的函数,称为成员函数或者成员方法;
类中的变量称为成员变量或者成员属性。
函数封装的思想:
class student { public: string name; int age; void setname(string string1) { name = string1; } void setage(int a) { age = a ; } }; void test02() { student stu1; stu1.setname("zahngsan"); stu1.setage(20); cout << "stu1's name is " << stu1.name << endl << "stu1's age is " << stu1.age << endl ; }
/home/lhw/桌面/C++/day2/cmake-build-debug/day2 stu1's name is zahngsan stu1's age is 20 进程已结束,退出代码为 0
Ⅰ.C语言对封装的缺点
C语言下结构体不能封装函数且类型检测不健全,导致属性分离。
#include
#include struct Person { char name[100]; int age; }; struct Dog { char name[100]; int age; }; void Peopleeat(struct Person * person) { printf("%s is eating people's meal\n",person->name); } void dogeat(struct Dog * dog) { printf("%s is eating dog's meal\n",dog->name); } void test01() { struct Person person; strcpy(person.name,"zhangsan"); person.age = 20; Peopleeat(&person); } void test02() { struct Dog dog; strcpy(dog.name,"wangcai"); dog.age = 11 ; dogeat(&dog); } void test03() { struct Person person; strcpy(person.name,"laowang"); person.age = 50 ; dogeat(&person); } int main() { test01(); test02(); test03(); return 0; } 运行结果:
/home/lhw/桌面/C++/untitled/cmake-build-debug/untitled zhangsan is eating people's meal wangcai is eating dog's meal laowang is eating dog's meal 进程已结束,退出代码为 0
Ⅱ.C++的改进:结构体可以放函数
struct Person { string name; int age; void personeat() { cout << name << "is eating people's meal" << endl; } }; struct Dog { string name; int age; void dogeat() { cout << name << "is eating dog's meal" << endl; } }; void testt01() { Person person; Dog dog; person.name = "zhangsan"; person.age = 10; person.personeat(); //person.dogeat(); } int main() { testt01(); return 0; }
这时,人就调用不了狗吃饭的方法了,不会导致属性分离。
Ⅰ.class默认权限是私有的,而struct默认权限是公共的。
将struct关键字改为class,只需加上公共权限即可使用。
Ⅱ.class访问权限
public:公共权限
成员在类内类外都可以访问
private:私有权限
成员在类内可以访问,类外不可以访问且子类不可访问父类成员
protected:保护权限
成员在类内可以访问,类外不可以访问且子类可访问父类成员
class person2 { public: string m_name; private: int pwd; protected: string m_car; public: void func() { m_name = "zhangsan"; m_car = "tracker"; pwd = 123456; } }; void test02() { person2 pe ; pe.m_name = "dasabi" ; //public limits, accessed //pe.pwd = 222 ; //private limits , rejected //pe.car = "hh" ; //pritected limits, rejected pe.func(); //public limits, access }
Ⅲ.尽量将成员属性设置为私有:向外部提供接口
class Person3 { public: void setname(string recieve) { name = recieve; } string getName() { return name; } int getage() { return age; } void setlover(string love) { lover = love; } string getlover() { return lover; } private: string name; int age = 18; string lover; }; void test03() { Person3 per; per.setname("laowang"); per.setlover("sunxiaochuan"); cout << "He/She's name is :" << per.getName() << endl << "age: " << per.getage() <
/home/lhw/桌面/C++/day2/cmake-build-debug/day2 He/She's name is :laowang age: 18 lover : sunxiaochuan
1.单文件编程
#include
class Point { public: void setxy(int input_x , int input_y) { x = input_x; y = input_y; } int getX() { return x; } int getY() { return y; } private: int x; int y; }; class Circle { public: void set_radius(int rad) { m_R = rad; } int get_radius() { return m_R; } void set_middle(Point po) { point = po; } Point get_middle() { return point; } void pointisincircle(Point poi) { int distance = (poi.getX()-point.getX())*(poi.getX()-point.getX()) +(poi.getY()-point.getY())*(poi.getY()-point.getY()); int test = m_R * m_R ; if(distance < test) { std::cout << "point in circle" << std::endl; } else if(distance > test) { std::cout << "point away from circle" << std::endl; } else { std::cout << "point at circle" << std::endl; } } private: int m_R; Point point; }; int main() { Point point; Point circlemiddle; Circle circle; point.setxy(0,10); circlemiddle.setxy(0,0); circle.set_radius(10); circle.set_middle(circlemiddle); circle.pointisincircle(point); return 0; } /home/lhw/桌面/C++/day3/cmake-build-debug/day3 point at circle 进程已结束,退出代码为 0
2.多文件编程
circle.h
// // Created by lhw on 2022/6/5. // #ifndef DAY3_CIRCLE_H #define DAY3_CIRCLE_H #include "point.h" #include
class Circle { public: void set_radius(int rad); int get_radius(); void set_middle(Point po); Point get_middle(); void pointisincircle(Point poi); private: int m_R; Point point; }; #endif //DAY3_CIRCLE_H circle.cpp
// // Created by lhw on 2022/6/5. // #include "circle.h" void Circle::set_radius(int rad) { m_R = rad; } int Circle::get_radius() { return m_R; } void Circle::set_middle(Point po) { point = po; } Point Circle::get_middle() { return point; } void Circle::pointisincircle(Point poi) { int distance = (poi.getX() - point.getX()) * (poi.getX() - point.getX()) + (poi.getY() - point.getY()) * (poi.getY() - point.getY()); int test = m_R * m_R; if (distance < test) { std::cout << "point in circle" << std::endl; } else if (distance > test) { std::cout << "point away from circle" << std::endl; } else { std::cout << "point at circle" << std::endl; } }
point.h
// // Created by lhw on 2022/6/5. // #ifndef DAY3_POINT_H #define DAY3_POINT_H #include
class Point { public: void setxy(int input_x , int input_y); int getX(); int getY(); private: int x; int y; }; #endif //DAY3_POINT_H point.cpp
// // Created by lhw on 2022/6/5. // #include "point.h" void Point::setxy(int input_x , int input_y) { x = input_x; y = input_y; } int Point::getX() { return x; } int Point::getY() { return y; }
main.cpp
#include
#include "circle.h" #include "point.h" int main() { Point point; Point circlemiddle; Circle circle; point.setxy(0,10); circlemiddle.setxy(0,0); circle.set_radius(10); circle.set_middle(circlemiddle); circle.pointisincircle(point); return 0; } 运行结果同上。
1.构造函数
Ⅰ.作用
对象初始化时构造函数会被调用
Ⅱ.注意事项
①必须声明在全局的作用域public下才行
②其没有返回值,不用写void
③函数名称与类名一样,可以进行函数重载
④构造函数由编译器自动调用一次
Ⅲ.code
#include
class Person { public: Person() { std::cout << "构造函数调用" << std::endl ; } }; void test01() { Person p; } int main() { test01(); return 0; } /home/lhw/桌面/C++/day3/cmake-build-debug/day3 构造函数调用 进程已结束,退出代码为 0
2.析构函数
Ⅰ.用处
当前对象释放调用析构函数进行清理
Ⅱ.注意事项
①没有返回值,不用写void
②函数名与类名相同,前面加~
③不可以发生重载
④不可以有参数
⑤编译器自动调用
Ⅲ.code
class Person { public: Person() { std::cout << "构造函数调用" << std::endl ; } ~Person() { std::cout << "析构函数调用" << std::endl ; } };
/home/lhw/桌面/C++/day3/cmake-build-debug/day3 构造函数调用 析构函数调用
3.构造函数的分类
Ⅰ.无参构造函数(默认)和有参构造函数
class Person { public: Person() { std::cout << "无参构造函数调用" << std::endl ; } Person(int a) { std::cout << "有参构造函数调用" << std::endl ; } };
Ⅱ.普通构造函数和拷贝构造函数
除了拷贝构造函数其他构造函数均为普通构造函数,拷贝构造函数主要是用传进去的这个类的对象做初始化;由于用其做初始化而不是操作。为防止误操作,要在传参中加入const关键字。
class Person { public: Person(const Person & p) { std::cout << "拷贝构造函数调用" << std::endl ; age = p.age; } private: int age; };
Ⅲ.观察下段代码,预测执行结果
class Person { public: Person() { std::cout << "无参构造函数调用" << std::endl ; } Person(int a) { age = a; std::cout << "有参构造函数调用" << std::endl ; } Person(const Person & p) { std::cout << "拷贝构造函数调用" << std::endl ; age = p.age; } ~Person() { std::cout << "析构函数调用" << std::endl ; } int getage() { return age; } private: int age; }; void test01() { Person p(18); Person p2(p); std::cout << "p2's age is : " << p2.getage() << std::endl; } int main() { test01(); return 0; }
/home/lhw/桌面/C++/day3/cmake-build-debug/day3 有参构造函数调用 拷贝构造函数调用 p2's age is : 18 析构函数调用 析构函数调用
4.构造函数的调用
Ⅰ.括号法:
class Person { public: Person() { std::cout << "无参构造函数调用" << std::endl ; } Person(int a) { age = a; std::cout << "有参构造函数调用" << std::endl ; } Person(const Person & p) { std::cout << "拷贝构造函数调用" << std::endl ; age = p.age; } ~Person() { std::cout << "析构函数调用" << std::endl ; } int getage() { return age; } private: int age; };
void test01() { Person p; Person p1(18); Person p2(p1); std::cout << "p2's age is : " << p2.getage() << std::endl; }
运行结果:
/home/lhw/桌面/C++/day3/cmake-build-debug/day3 无参构造函数调用 有参构造函数调用 拷贝构造函数调用 p2's age is : 18 析构函数调用 析构函数调用 析构函数调用
Ⅱ.注意:
不要用括号法去调用无参构造函数,系统认为是一个函数的声明
Person p2();
系统认为这是一个返回值为Person类型的函数的声明。
Ⅲ.显示法
Person p3 = Person(18); Person p4 = Person(p3);
解释一下第一行代码:Person(18); 创建了一个匿名对象,匿名对象的特点是当前行执行完立即被回收,于是这行代码的意思就是,用Person类创建出的p3对象来接收匿名对象。
且不要用拷贝函数初始化匿名对象(正常人应该也不会这么做)
5.拷贝构造函数的调用时机
Ⅰ.用已经创建好的对象初始化另一个对象:
void test01()
{
Person p;
Person p1(18); //有参构造,将p1的年龄初始化18
Person p2(p1); //拷贝构造,用p1初始化p2
std::cout << "p2's age is : " << p2.getage() << std::endl;
}Ⅱ.值传递的方式给函数的参数传值
void dowork(Person p1) { } void test02() { Person p1(10); dowork(p1); }
执行test02函数。发现调用了拷贝构造函数
/home/lhw/桌面/C++/day3/cmake-build-debug/day3 有参构造函数调用 拷贝构造函数调用 析构函数调用 析构函数调用 进程已结束,退出代码为 0
Ⅲ.以值的方式返回局部对象
Person dowork2() { Person p; return p; } void test03() { Person a = dowork2(); }
代码解释:
①为什么dowork2函数中不返回p的引用:p是局部变量,在栈中创建内存空间,一旦函数释放,其就不存在了,若返回的是引用,本质是地址,那就会犯使用野指针错误。
②返回的Person到底是什么:p是局部变量,dowork2函数结束其栈帧就会被清空,这时系统又创建出来一个Person对象并用p初始化,相当于用p初始化了一个匿名对象,然后用a去接收,因此回调用拷贝构造函数。
6.构造函数的调用规则
Ⅰ .其实我们创建一个类,就会自动创建三个函数:析构函数、构造函数、拷贝构造函数(将对象的所有的值赋值操作)。
观察下段代码。
class Person { public: Person() { std::cout << "无参构造函数调用" << std::endl ; } Person(int a) { age = a; std::cout << "有参构造函数调用" << std::endl ; } ~Person() { std::cout << "析构函数调用" << std::endl ; } int getage() { return age; } private: int age; };
这段代码里没有拷贝构造函数。
我们调用下面这段代码:
void test01() { Person p; Person p1(18); Person p2(p1); std::cout << "p2's age is : " << p2.getage() << std::endl; }
看一下执行结果:
无参构造函数调用 有参构造函数调用 p2's age is : 18 析构函数调用 析构函数调用 析构函数调用
说明编译器自动帮我们实现了拷贝构造的实现,第三行代码不是无参和有参,因此少了一个调用的输出。
Ⅱ.如果我们自己提供了有参构造函数,编译器就不会提供默认构造函数,但是依然会提供拷贝构造函数。
Ⅱ.如果我们自己提供了拷贝构造函数,编译器就不会提供其他的默认的构造函数。
7.深拷贝与浅拷贝
Ⅰ.讲了这么多,构造函数和析构函数到底有什么用呢?
看看下面这段代码:构造函数完成了初始化,让初始化的步骤不再那么繁琐!
class Person { public: Person() { std::cout << "无参构造函数调用" << std::endl ; } Person(int inputAge , char * inputName) { name = (char *) malloc(strlen (inputName)+1); strcpy(name,inputName); age = inputAge; std::cout << "有参构造函数调用" << std::endl ; } ~Person() { std::cout << "析构函数调用" << std::endl ; } int getage() { return age; } char* getname() { return name; } private: int age; char * name ; }; void test01() { Person p(18,"demaxiya"); std::cout << "name is " << p.getname() << std::endl << "age is " << p.getage() << std::endl; }
/home/lhw/桌面/C++/day3/cmake-build-debug/day3 有参构造函数调用 name is demaxiya age is 18 析构函数调用
Ⅱ.我们更改test01函数:
void test01() { Person p(18,"demaxiya"); std::cout << "name is " << p.getname() << std::endl << "age is " << p.getage() << std::endl; Person p2(p); std::cout << "name is " << p2.getname() << std::endl << "age is " << p2.getage() << std::endl; }
执行查看结果
/home/lhw/桌面/C++/day3/cmake-build-debug/day3 有参构造函数调用 name is demaxiya age is 18 name is demaxiya age is 18 析构函数调用 析构函数调用
我们发现我们没有写拷贝构造函数,类的默认的拷贝构造函数已经帮我们把p的数据初始化到了p2中,那么程序有个问题:name占据的空间什么时候释放呢?test01结束后呗,test01结束后会调用它的析构函数,因此我们可以在析构函数中加入释放内存空间的代码防止内存泄漏!修改类如下,防止内存泄露!
class Person { public: Person() { std::cout << "无参构造函数调用" << std::endl ; } Person(int inputAge , char * inputName) { name = (char *) malloc(strlen (inputName)+1); strcpy(name,inputName); age = inputAge; std::cout << "有参构造函数调用" << std::endl ; } ~Person() { if(name!=NULL) { free(name); name = NULL; } std::cout << "析构函数调用" << std::endl ; } int getage() { return age; } char* getname() { return name; } private: int age; char * name ; };
但是执行这段代码!发现代码崩溃?这是为什么呢?因为系统自带的默认拷贝构造函数是逐字节拷贝,我们创建对象p时,name所指向的空间也是p2所指向的空间,因此,函数test01结束时会调用两次free释放同一块空间,导致报错!因此自己实现拷贝构造函数称为必要!
完整版如下:
class Person { public: Person() { std::cout << "无参构造函数调用" << std::endl ; } Person(int inputAge , char * inputName) { name = (char *) malloc(strlen (inputName)+1); strcpy(name,inputName); age = inputAge; std::cout << "有参构造函数调用" << std::endl ; } Person(const Person & per) { name =(char *) malloc(strlen(per.name)); strcpy(name,per.name); age = per.age; } ~Person() { if(name!=NULL) { free(name); name = NULL; } std::cout << "析构函数调用" << std::endl ; } int getage() { return age; } char* getname() { return name; } private: int age; char * name ; };
Ⅰ.观察代码
class people { public: people(std::string name1,std::string name2,std::string name3):name(name1), phone(name2), game(name3) { std::cout << "people的有参构造函数调用" << std::endl; } ~people() { std::cout << "people的析构函数调用" << std::endl; } public: std::string name; Phone phone; Game game; }; void test04() { people pi("zhangsan","ios","wangzherongyao"); }
执行:
/home/lhw/桌面/C++/day3/cmake-build-debug/day3 Phone的有参构造函数调用 Game的有参构造函数调用 people的有参构造函数调用 people的析构函数调用 Game的析构函数调用 Phone的析构函数调用
我们发现当其它类的对象作为本类的成员,先构造其他类再构造自身,析构的顺序和构造相反。
Ⅰ.new、delete关键字
看一下这样一个类
class Person_1 { public: Person_1() { cout << "Person的构造函数调用" << endl; } ~Person_1() { cout << "Person的析构函数调用" << endl; } };
我们执行下面一行代码会发生什么?
Person_1 * person1 = new Person_1;
C++会在堆区分配一块内存,返回一个Person_1类型的指针,来去接收这块内存。相比于malloc,calloc函数等,其优势在于不用写内存大小(sizeof)的步骤,且我们执行这一语句:
/home/lhw/桌面/C++/day3/cmake-build-debug/day3 Person的构造函数调用
我们看到,只调用了构造函数没有调用析构函数,这是因为堆区的内存空间只能手动释放,那么我们如何去释放它呢?用delete关键字
Ⅱ.malloc与new区别
①malloc和free是库函数,new和delete属于运算符
②malloc不会调用构造函数,而new会
③malloc返回值是void * 类型,要强制转换才能用,而new返回创建对象的指针。
Ⅲ.用new开辟数组及类:注意:这里Person没有无参构造函数
代码的第三行是创立了十个Person类,返回Person * 类型的类数组
但前面我们说过,堆区创建数组一定会调用默认构造函数的,我们执行下段代码
void test06() { int * pInt = new int[10]; float * pFloat = new float [10]; Person * person_10 = new Person[10]; }
class Person { public: Person(int inputAge , char * inputName) { name = (char *) malloc(strlen (inputName)+1); strcpy(name,inputName); age = inputAge; std::cout << "有参构造函数调用" << std::endl ; } Person(const Person & per) { name =(char *) malloc(strlen(per.name)); strcpy(name,per.name); age = per.age; } ~Person() { if(name!=NULL) { free(name); name = NULL; } std::cout << "析构函数调用" << std::endl ; } int getage() { return age; } char* getname() { return name; } private: int age; char * name ; };
发现报错了,因此:在用堆区构造数组,一定会调用默认构造函数
释放数组时要多加个中括号,代码格式为
delete [] person_10;
但栈上开辟类数组可以没有默认构造函数
Person parray[10] = {Person(10),Person(20)}
这就可以正常运行不报错
在类定义中,它的成员(包括成员变量和成员函数),这些成员可以用关键字static声明为静态的,称为静态成员。
不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的
对象共享。
1.静态成员变量:
这个对象是共享的因此编译阶段就分配了内存给它。需要类内声明,类外初始化。
静态成员变量所有对象都共享同一份数据
Ⅰ.code①通过对象进行访问
class A { public: static int A_a; }; int A::A_a = 0; void test01() { A a; A b; cout << "a.A_a = :" << a.A_a << endl; b.A_a = 10 ; cout << "a.A_a = :" << a.A_a << endl; } int main() { test01(); return 0; }
观察其运行结果:
/home/lhw/桌面/C++/day4/cmake-build-debug/day4 a.A_a = :0 a.A_a = :10
Ⅱ.code②通过类名进行访问
cout << "a.A_a = :" << A::A_a << endl;
Ⅲ.此外
静态成员变量也是有访问权限的,如果权限是private则不能访问。
2.静态成员函数:
和静态成员变量差不多,也可由对象和类名调用。但非静态成员变量是不可访问的。
1.定义
通过一个类只能创建一个对象
单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊
类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便
对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模
式是最好的解决方案。
如任务管理器。
2.如何创建单例模式对象(思路)
①将构造函数私有化,不可创建多个对象。
②创建静态类型的指针;在类内声明,类外初始化。
class Chairman { private: Chairman(){}; public: static Chairman * singleMan ; }; Chairman * Chairman::singleMan = new Chairman;
void test02() { Chairman * c1 = Chairman::singleMan; Chairman * c2 = Chairman::singleMan; if(c1==c2) { cout << "c1 = c2" << endl; } else { cout << "c1 != c2" << endl; } }
执行:
/home/lhw/桌面/C++/day4/cmake-build-debug/day4 c1 = c2
但此时有一个问题,如果我们用这么一行代码,
Chairman * c3 = Chairman::singleman; c3 = NULL;
此时之前做的操作不是都失效了!
将对象私有化并提供访问接口:
class Chairman { public: static Chairman * Getinstance() { return singleMan; } private: Chairman(){}; Chairman(const Chairman & chairman){}; static Chairman * singleMan ; }; Chairman * Chairman::singleMan = new Chairman;
3.单例模式案例:打印机
class Printer { public: static Printer * getInstance() { return p; } void PrintText(string text) { m_count ++; cout << text << endl; } int m_count; private: Printer(){m_count=0;}; Printer(const Printer & printer){}; static Printer * p; }; Printer * Printer::p = new Printer; void test03() { Printer * p1 = Printer::getInstance(); p1 -> PrintText("dashabi"); }
this指针 指向 被调用的成员函数 所属的对象。
1.this指针解决名称冲突
class Person { public: Person(int age) { this->age = age; } int age; }; void test04() { Person * p1 = new Person(10); cout << "p1's age is :" << p1->age << endl ; } int main() { test04(); return 0; }
分析this指针指向的是啥,这里p1在调用Person的有参构造,被调用的成员函数就是它的有参构造,所属对象是p1,this指针指向p1。因此this指针会解决命名冲突。
2.再看下面一个例程深入理解this指针与拷贝构造函数
class Person { public: Person(int age) { this->age = age; } Person& addAge(Person & person) { this->age = this->age + person.age; return *this; } int age; }; void test04() { Person * p1 = new Person(10); Person * p2 = new Person(20); p1->addAge(*p2).addAge(*p2).addAge(*p2); cout << "p1's age is : " << p1->age << endl; }
/home/lhw/桌面/C++/day4/cmake-build-debug/day4 p1's age is : 70 进程已结束,退出代码为 0
让我们分析这段代码:
①this->age = age; this指向的是传进来的对象,比如Person * p1 = new Person(10);
这里this指针就指向p1.
②为什么ruturn *this,this代表指向当前调用成员的指针,*this表示解引用指向本体,传给Person&返回是this指针指向的对象。
③p1->addAge(*p2).addAge(*p2).addAge(*p2);
形参为p2的引用,返回的是p2的地址,p2的地址再解引用传递给addage函数......
④如果换成Person addAge(Person & person)最后输出结果是什么:30
在执行到p1->addAge(*p2)时,p2的引用传入addage中,将p2的age+20,但因为返回值返回的是局部变量,这时编译器自动调用拷贝构造函数创建一个匿名对象p2'再进行之后的操作.....,因此返回值为30。
如果成员函数中没有用到this指针,可以调用否则不可以;
class Person { public: void printClass() { cout << "class name is : Person" << endl; } void printage() { cout << "age is : " << this->age ; } int age; }; int main() { Person * person = NULL; person->printClass(); person->printage(); return 0; }
学过计算机系统的同学知道,这里发生了段错误。是因为this指向的是Person的地址,而Person的地址是0,因此出现了非法访问内存空间的错误。
改正:增加代码健壮性
class Person { public: void printClass() { cout << "class name is : Person" << endl; } void printage() { if(this==NULL) { cout << "NULL can't access !" << endl ; } cout << "age is : " << this->age ; } int age; };
/home/lhw/桌面/C++/day4/cmake-build-debug/day4 class name is : Person NULL can't access ! 进程已结束,退出代码为 139 (interrupted by signal 11: SIGSEGV)
1.this指针的本质
类名 * const this,就是指向的对象不能更改,但指向的值可以更改。
比如 this->age = 100 是允许的
但是 this = NULL 是不允许的
那么如何才能让this->age = 100是不允许的呢 const 类名 * const this这样就可以了
2.什么是常函数
内部不能修改对象的属性的函数
3.如何创建常函数
在成员函数的后面加上const,使指针指向的值不能修改
class Person { public: Person(int age) { this->age = age ; } void showPerson() const { cout << "age is : " << this->age << endl; } private: int age; };
这里 showPerson就是常函数。
如果在常函数中想要修改某些成员属性的话,在成员属性前面加上mutable关键字即可。
比如,修改后的常函数showPerson就可以修改m_age属性
class Person { public: Person(int age) { this->age = age ; } void showPerson() const { m_age = 10; cout << "age is : " << this->age << endl; } private: int age; mutable int m_age; };
4.什么是常对象
在创建对象前面加上const关键字,其内值是不可以被修改的
5.如何创建常对象及可访问常对象内成员变量的情况
class Person { public: Person(int age1,int age2) { this->age = age1 ; this->m_age = age2 ; } void showPerson() const { cout << "age is : " << this->age << endl; } void black() { } int age; mutable int m_age; }; void test04() { const Person person(10,11); person.age ++ ; //error 常对象的内部除了用mutable声明的成员属性不可修改 person.m_age ++; //ok mutable声明的成员属性可修改 }
再来看一个问题,person调用black能调用起来吗?答案是不可以的,常对象的使用初衷是不能修改成员变量,如果能调用普通函数而普通函数里面有能修改成员变量的代码,不是就前功尽弃了嘛.....
1.场景
类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部(作用域之外)访问。但是,有时候需要在类的外部访问类的私有成员,怎么办?
解决方法是使用友元函数,友元函数是种特权函数,c++允许这个特权函数访问私有
成员。
2.全局函数做友元函数
class Building { friend void Goodgay(); public: Building(string bathroom,string bedroom) { this->Bathroom=bathroom; this->Bedroom=bedroom; } public: string Bathroom; private: string Bedroom; }; void Goodgay() { Building building("weishengjian","priqiwoshi"); cout << "bathroom is : " << building.Bathroom << endl; cout << "bedroom is : " << building.Bedroom << endl; }
加上friend关键字。
3.类作为友元类
class GoodGay; class Building; class GoodGay { public: GoodGay(); Building * building; void visit(); }; class Building { friend class GoodGay; public: Building(); string Bathroom; private: string Bedroom; }; Building::Building() { this->Bathroom = "xishoujian"; this->Bedroom = "woshi"; } GoodGay::GoodGay() { this->building = new Building; } void GoodGay::visit() { cout << "goodgay is visiting " << this->building->Bedroom; cout << "goodgay is visiting " << this->building->Bathroom; } void test05() { GoodGay goodGay; }
此外,a是b好朋友,b是c好朋友,a并不是c的好朋友,友元没有传递性。
4.类中的成员函数作为友元函数
class GoodGay { public: GoodGay(); Building * building; void visit(); //access to Building private void visit2(); //rejected to Building private }; class Building { friend void GoodGay::visit() ; public: Building(); string Bathroom; private: string Bedroom; }; Building::Building() { this->Bathroom = "xishoujian"; this->Bedroom = "woshi"; } GoodGay::GoodGay() { this->building = new Building; } void GoodGay::visit() { cout << "goodgay is visiting " << this->building->Bedroom; cout << "goodgay is visiting " << this->building->Bathroom;
1.做什么
实现一个数组,复习构造函数、多文件协同编程的用法
2.头文件 myarary.h
// // Created by lhw on 2022/6/7. // #ifndef DAY4_MUARRAY_H #define DAY4_MUARRAY_H #include
class Myarray { public: Myarray(); Myarray(int capicity); Myarray(const Myarray & myarray); ~Myarray(); //尾插法 void pushBack(int val); //获取数据 int getData(int pos); //设置数据 void setData(int pos,int val); //获取数组容量 int getCapicity(); //获取数组大小 int getSize(); private: int m_capicity; int m_size; int* pAdderss; }; #endif //DAY4_MUARRAY_H
3.源文件 myarray.cpp
// // Created by lhw on 2022/6/7. // #include "muArray.h" Myarray::Myarray() { this->m_capicity = 100; this->m_size = 0; this->pAdderss = new int[this->m_capicity]; } Myarray::Myarray(int capicity) { this->m_capicity = capicity; this->m_size = 0; this->pAdderss = new int[this->m_capicity]; } Myarray::Myarray(const Myarray & myarray) { this->m_capicity = myarray.m_capicity; this->m_size = myarray.m_size; this->pAdderss = new int[myarray.m_capicity]; for(int i=0;i
pAdderss[i] = myarray.pAdderss[i]; } } Myarray::~Myarray() { if(this->pAdderss!=NULL) { delete [] this->pAdderss; this->pAdderss = NULL; } } //尾插法 void Myarray::pushBack(int val) { this -> pAdderss[this->m_size] = val ; this -> m_size ++; } //获取数据 int Myarray::getData(int pos) { return this->pAdderss[pos]; } //设置数据 void Myarray::setData(int pos,int val) { this->pAdderss[pos] = val; } //获取数组容量 int Myarray::getCapicity() { return this->m_capicity; } //获取数组大小 int Myarray::getSize() { return this->m_size; }
4.main.cpp
Ⅰ.test01
#include "muArray.h" void test01() { //调用默认构造函数 Myarray myarray; for(int i = 0; i<50 ;i++) { myarray.pushBack(i+100); } for(int i=0 ; i
/home/lhw/桌面/C++/day4/cmake-build-debug/day4 第1位的数据是100 第2位的数据是101 第3位的数据是102 第4位的数据是103 ..... 第49位的数据是148 第50位的数据是149 进程已结束,退出代码为 0
运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数
据类型。
1.利用成员函数进行运算符重载
class Person { public: Person(int a,int b):m_A(a),m_B(b){}; //利用operator关键字进行运算符重载 Person operator+(Person & person) { Person temp(0,0); temp.m_A = this->m_A + person.m_A; temp.m_B = this->m_B + person.m_B; return temp; } public: int m_A; int m_B; }; void test01() { Person p1(10,10); Person p2(20,30); Person p3 = p1 + p2 ; cout << "Person p3's a = " << p3.m_A << " b = " << p3.m_B << endl; }
/home/lhw/桌面/C++/day5/cmake-build-debug/day5 Person p3's a = 30 b = 40
相当于:
p1.operator+(p2)
2.利用全局函数进行运算符重载
Person operator+(Person &p1,Person & p2) { Person temp(0,0); temp.m_A = p1.m_A + p2.m_A; temp.m_B = p1.m_B + p2.m_B; return temp; } 本质: Person p3 = operator+(p1,p2); p1 operator+ p2 时调用(p1 p2 是Person类型时候)
3.运算符重载可以发生函数重载
class Person { public: Person(int a,int b):m_A(a),m_B(b){}; public: int m_A; int m_B; }; Person operator+(Person &p1,Person & p2) { Person temp(0,0); temp.m_A = p1.m_A + p2.m_A; temp.m_B = p1.m_B + p2.m_B; return temp; } Person operator+(Person &p1,int p2) { Person temp(0,0); temp.m_A = p1.m_A + p2; temp.m_B = p1.m_B + p2; return temp; } void test01() { Person p1(10,10); Person p2(20,30); Person p3 = p1 + p2 ; Person p4 = p1 + 10; cout << "Person p3's a = " << p3.m_A << " b = " << p3.m_B << endl; cout << "Person p4's a = " << p4.m_A << " b = " << p4.m_B << endl; }
/home/lhw/桌面/C++/day5/cmake-build-debug/day5 Person p3's a = 30 b = 40 Person p4's a = 20 b = 20
1.用全局函数做左移重载
等价于
class Person { public: Person(int a , int b) { this->m_a = a; this->m_b = b; } int m_a; int m_b; }; ostream& operator<<(ostream & cout , Person & person) // cout operator<< person1 { cout << "m_a = " << person.m_a << " m_b = " << person.m_b; } void test01() { Person p1(10,10); cout << p1; }
/home/lhw/桌面/C++/day5/cmake-build-debug/day5 m_a = 10 m_b = 10 进程已结束,退出代码为 0
2.但一般情况下成员属性是私有的,将左移重载加入到友元函数即可
class Person { friend ostream& operator<<(ostream & cout , Person & person); public: Person(int a , int b) { this->m_a = a; this->m_b = b; } private: int m_a; int m_b; }; ostream& operator<<(ostream & cout , Person & person) // cout operator<< person1 { cout << "m_a = " << person.m_a << " m_b = " << person.m_b; } void test01() { Person p1(10,10); cout << p1; }
1.先看下面这段代码如何解决
class myInt { public: myInt() { m_num = 0; } private: int m_num; }; void test02() { myInt myint; out << myint << endl; }
我们想让我们声明的类int像正常的数字进行加减输出,此时执行test02函数是会报错的,因为cout不支持对我们这个运算符的输出。
解决办法:cout重载
class myInt { friend ostream & operator<<(ostream& cout, myInt& myint); public: myInt() { m_num = 0; } private: int m_num; }; ostream & operator<<(ostream& cout, myInt& myint) { cout << "my integer is :" << myint.m_num << endl ; return cout; } void test02() { myInt myint; cout << myint << endl; }
2.实现前置++操作
class myInt { friend ostream & operator<<(ostream& cout, myInt& myint); public: myInt() { m_num = 0; } myInt& operator++() { this->m_num ++; return *this; } private: int m_num; }; ostream & operator<<(ostream& cout, myInt& myint) { cout << "my integer is :" << myint.m_num << endl ; return cout; } void test02() { myInt myint; cout << myint << endl; cout << ++myint << endl; cout << myint << endl; }
/home/lhw/桌面/C++/day5/cmake-build-debug/day5 my integer is :0 my integer is :1 my integer is :1
代码解析:
1.正常是如何调用的:operator++()a,a是myInt类型的,简化为 ++a
2.为什么返回mynt&,因为要实现链式编程序,如果下面一段代码:
cout << ++myint << endl;
会直接报错,这是为什么呢?++myint是什么类型,void类型,void类型cout没有重载去打印,那为什么要返回引用呢?
这就回到拷贝构造函数那块的知识了,如果返回是对象类型的数据,系统自动调用拷贝构造函数创建当前对象的一个匿名构造函数myint_1返回到函数体,而再对其进行++操作是对不同对象的操作。
3.实现后置++
myInt operator++(int) { myInt temp = *this; this->m_num ++; return temp; }
实际用处并不多,只提供代码:
class Person { public: void showage() { cout << "age is : " << m_age; } Person(int age) { this->m_age = age; } int m_age; }; class Smartpoint { public: Person* operator->() { return this->person; } Person& operator*() { return *person; } Smartpoint(Person * person) { this->person = person; } ~Smartpoint() { if(this->person!=NULL) { delete this->person; this->person = NULL; } } private: Person * person; }; void test01() { //use smartpoint,manage Person's object's free Smartpoint sp(new Person(18)); sp->showage(); //本质 ->sp->showage(); IDE 做了简化 (*sp).showage(); }
1.我们先看这段代码引出问题
class Person { public: Person(char * name,int age) { this->name = new char[strlen(name)+1]; strcpy(this->name,name); this->age = age; } // ~Person() // { // if(this->name!=NULL) // { // delete[]this->name; // this->name = NULL; // } // // } char * name; int age; }; void test01() { Person person1("Tom",19); Person person2("xiaoqiao",18); person1 = person2; cout << "p1's name is : " << person1.name << endl << "p1's age is : " << person1.age << endl; cout << "p2's name is : " << person2.name << endl << "p2's age is : " << person2.age << endl; }
执行这段代码:
/home/lhw/桌面/C++/day5/cmake-build-debug/day5 p1's name is : xiaoqiao p1's age is : 18 p2's name is : xiaoqiao p2's age is : 18
我们发现p1将p2的所有数据都给复制过来了,但我们取消注释,发现代码出错了,原因是p1=p2这步动作是一个浅拷贝,如果调用析构函数会导致内存被释放两次程序报错。
2.改进--重载等号
class Person { public: void operator=(const Person & person) { //先判断原来的堆区(自己的堆区)是否有内容,有内容先清空再将参数中的对象复制给自己 if(this->name!=NULL) { delete [] this->name; this->name = NULL; } this->name = new char [strlen(person.name)+1]; strcpy(this->name,person.name); this->age = person.age; } Person(char * name,int age) { this->name = new char[strlen(name)+1]; strcpy(this->name,name); this->age = age; } ~Person() { if(this->name!=NULL) { delete[]this->name; this->name = NULL; } } char * name; int age; }; void test01() { Person person1("Tom",19); Person person2("xiaoqiao",18); person1 = person2; cout << "p1's name is : " << person1.name << endl << "p1's age is : " << person1.age << endl; cout << "p2's name is : " << person2.name << endl << "p2's age is : " << person2.age << endl; }
现在就不会出现释放两次相同地址堆空间的情况了。
3.情况说明
在c++中,考虑下面的情况:
int main() { int a = 10; int b = 20; int c; c = a = b; cout << "a = " << a << endl << "b = " << b << endl << "c = " << c << endl; return 0; }
/home/lhw/桌面/C++/day5/cmake-build-debug/day5 a = 20 b = 20 c = 20 进程已结束,退出代码为 0
相当于做了a =b=20 c = a = 20,即从右向左赋值。
返回我们的案例:修改test01
void test01() { Person person1("Tom",19); Person person2("xiaoqiao",18); Person person3("",0); person3 = person1 = person2; cout << "p1's name is : " << person1.name << endl << "p1's age is : " << person1.age << endl; cout << "p2's name is : " << person2.name << endl << "p2's age is : " << person2.age << endl; }
我们发现编译器报错了,这是为什么呢?
p2 = p1 返回结果为void,导致p3 = void出了问题。
4.完整版代码
class Person { public: Person& operator=(const Person & person) { //先判断原来的堆区(自己的堆区)是否有内容,有内容先清空再将参数中的对象复制给自己 if(this->name!=NULL) { delete [] this->name; this->name = NULL; } this->name = new char [strlen(person.name)+1]; strcpy(this->name,person.name); this->age = person.age; return *this; } Person(char * name,int age) { this->name = new char[strlen(name)+1]; strcpy(this->name,name); this->age = age; } ~Person() { if(this->name!=NULL) { delete[]this->name; this->name = NULL; } } char * name; int age; }; void test01() { Person person1("Tom",19); Person person2("xiaoqiao",18); Person person3("",0); person3 = person1 = person2; cout << "p1's name is : " << person1.name << endl << "p1's age is : " << person1.age << endl; cout << "p2's name is : " << person2.name << endl << "p2's age is : " << person2.age << endl; cout << "p3's name is : " << person3.name << endl << "p3's age is : " << person3.age << endl; } int main() { st01(); return 0; }
/home/lhw/桌面/C++/day5/cmake-build-debug/day5 p1's name is : xiaoqiao p1's age is : 18 p2's name is : xiaoqiao p2's age is : 18 p3's name is : xiaoqiao p3's age is : 18
1.问题
回顾1.2.14节,我们创建的数组并不能通过[]来访问数组元素和修改。
2.解决:
在myarray.h中添加运算符重载的定义
// // Created by lhw on 2022/6/7. // #ifndef DAY4_MUARRAY_H #define DAY4_MUARRAY_H #include
class Myarray { public: Myarray(); Myarray(int capicity); Myarray(const Myarray & myarray); ~Myarray(); //尾插法 void pushBack(int val); //获取数据 int getData(int pos); //设置数据 void setData(int pos,int val); //获取数组容量 int getCapicity(); //获取数组大小 int getSize(); //通过数组下标访问修改数组元素 int& operator[](int index); private: int m_capicity; int m_size; int* pAdderss; }; #endif //DAY4_MUARRAY_H 在myarray.cpp中添加运算符重载的实现
// // Created by lhw on 2022/6/7. // #include "muArray.h" Myarray::Myarray() { this->m_capicity = 100; this->m_size = 0; this->pAdderss = new int[this->m_capicity]; } Myarray::Myarray(int capicity) { this->m_capicity = capicity; this->m_size = 0; this->pAdderss = new int[this->m_capicity]; } Myarray::Myarray(const Myarray & myarray) { this->m_capicity = myarray.m_capicity; this->m_size = myarray.m_size; this->pAdderss = new int[myarray.m_capicity]; for(int i=0;i
pAdderss[i] = myarray.pAdderss[i]; } } Myarray::~Myarray() { if(this->pAdderss!=NULL) { delete [] this->pAdderss; this->pAdderss = NULL; } } //尾插法 void Myarray::pushBack(int val) { this -> pAdderss[this->m_size] = val ; this -> m_size ++; } //获取数据 int Myarray::getData(int pos) { return this->pAdderss[pos]; } //设置数据 void Myarray::setData(int pos,int val) { this->pAdderss[pos] = val; } //获取数组容量 int Myarray::getCapicity() { return this->m_capicity; } //获取数组大小 int Myarray::getSize() { return this->m_size; } int& Myarray::operator[](int index) { return this->pAdderss[index]; } 这时就和真正的数组一样啦!
void test01() { //调用默认构造函数 Myarray myarray; for(int i = 0; i<50 ;i++) { myarray.pushBack(i+100); } myarray[1] = 20; cout << myarray[1] << endl; }