从C开始的CPP学习生活01-基础内容
0、写在前面
本笔记为学习cpp过程中记录的一些知识,并不全面,也并不一定完全正确
参考视频教程:bilibili
代码测试环境:win10 + vs2017
1、换行
endl换行并清空并刷新缓冲区
\n只进行换行
2、newdelete和mallocfree的区别
new和delete会触发构造函数和析构函数
malloc和free不会
3、引用
3.1、引用是给我们的变量起别名
1 |
|
打印13
注意:引用声明的时候一定要初始化int& d;是非法的
一个变量可以有无数个引用
3.2、常量的引用
1 |
|
a不可修改,const是必须的
3.3、数组的引用
1 |
|
3.4、指针的引用
1 |
|
3.5、引用做参数
1 |
|
3.6、引用做返回值
不要引用局部变量
1 |
|
warning:warning C4172: returning address of local variable or temporary: a
操作非法内存的结果是未知的,所以不要这样使用
局部变量所占用的内存空间的分配和销毁取决于编译器的实现,编译器为了优化程序性能,可能有不同的策略来分配、释放内存
比如:VC编译器可能在函数入口处即分配这里的全部变量,GCC编译器也可能在定义出才分配变量
因此除了之哟用于的局部变量是不允许访问的,该空间可能暂时没有被释放,也可能被释放,所以不要访问这样的内存。
3.7、引用与指针的区别
- 引用声明就要初始化,指针不用
- 引用初始化之后就不能指向其他空间了,指针可以
- 引用不占存储空间,指针占空间
- 引用效率更高,指针属于间接操作
- 引用更安全,指针可以偏移
- 指针更灵活,直接操作地址,指针更通用
4、参数缺省
4.1、函数定义
全部指定:
1 |
|
部分指定,部分指定必须从右向左连续指定,不能指定左侧的参数而右侧参数未指定
1 |
|
不满足这条规则会报错:Error (active) E0306 default argument not at end of parameter list
Error C2548 'fun1': missing default parameter for parameter
4.2、函数调用
函数调用时,有默认值的参数,可以不用传递实参
没有指定默认值的,一定要传递实参
有默认值还传递实参,实参会覆盖掉默认值
5、函数重载
5.1、定义
同一个作用域内,函数名字相同,参数列表不同(参数类型不同或者参数个数不同)
1 |
|
根据参数不同可以调用不同的函数
5.2、二义性问题
但是,考虑到缺省值的问题,不合理的使用缺省值会导致重载函数产生二义性,比如:
1 |
|
这里面函数互为重载函数,但是再调用fun(12)
是编译器会报错:Error C2668 'fun': ambiguous call to overloaded function
所以在使用函数重载时要谨慎使用缺省值
5.3、返回值问题
函数重载不包括返回值,因为C++是可以不考虑返回值的,void类型的数据也可以return 0;bool类型的数据也可以return 1;
6、防止头文件重复包含
两种方法
1 |
|
另一种方法
1 |
|
第二种方法能否使用取决于编译器,因此使用这种方法移植性特别差,所以不建议使用
7、类
类是具有相同属性和行为的对象的集合
7.1、类的声明
1 |
|
7.3、声明对象及成员调用
1 |
|
类的所有成员(个别特殊的static),必须通过对象访问。
7.4、访问修饰符
作用范围:书写位置开始,一直到下一个修饰符,或者类结尾的花括号
7.4.1、public
public修饰符对外可见,其他的类也可以访问
1 |
|
C++结构体是特殊的类,访问修饰符默认是public
7.4.2、private
类里面不注明修饰符默认是private修饰符
private是私有修饰符,其成员只有类内可以访问
1 |
|
a是私有成员,fun方法中可以调用
7.4.3、protected
protected修饰符修饰的成员对类内以及子类可见
8、友元
友元 类内成员对于这个函数/类来说相当于public,protected修饰符也可以
1 |
|
特点:
- 不受访问修饰符的影响
- 破坏了类的封装性
9、函数成员
9.1、构造函数
9.1.1、普通构造函数
类中的成员初始化有三种方法:
- 定义时直接赋值(比较古老的C++版本不支持)
- 初始化列表
- 构造函数里面初始化
1 |
|
三种初始化方式优先级依次递增,所以age打印出来的时候是3
构造函数在对象创造过程中调用的,只有栈区对象会调用构造函数,栈区对象就是普通的变量,堆区的对象不会调用构造函数,比如指针对象
1 |
|
此时不会调用构造函数
9.1.2、带参数的构造函数
1 |
|
参数并不需要完全用于初始化
构造函数也可以指定默认值
1 |
|
但是会被实际的传参值覆盖掉,优先级问题。
指针对象初始化
1 |
|
9.1.3、多个构造函数构成重载
根据参数传递区分使用哪个构造函数
9.1.4、类中函数的声明与定义(实现位置)
1 |
|
可以在类内实现,也可以在类外实现
但是,在定义函数时不可以添加缺省值
9.2、构造函数初始化列表
9.2.1、初始化与赋值的区别
初始化时一个变量或者对象产生时就赋予一个初始值,伴随性质
赋值是一个变量或者对象产生之后的任意时刻可以赋予一个值,随意性质
宏观代码上的区别
- 基本数据类型:作用完全相同;
- 数组、结构体:数组和结构体初始化时可以采用花括号的形式赋值,初始化之后的赋值不可以使用花括号赋值;作用也完全相同
- 引用、const:一定要进行初始化,不能赋值;const变量不初始化报错:
'const' object must be initialized if not 'extern'
9.2.2、构造函数与初始化列表的区别
构造函数的本质是赋值
1 |
|
初始化列表的本质是初始化
1 |
|
9.2.3、通过参数进行构造函数初始化
1 |
|
在CStu初始化时需要使用
1 |
|
我们不需要通过参数初始化所有的列表
1 |
|
或者有不被使用的参数也可以
9.2.4、类内变量的初始化顺序
变量的初始化顺序只与声明顺序有关,与初始化列表中的顺序无关
在前面的例子中,我们对CStu初始化时首先初始化a,然后初始化f
改变写法
1 |
|
同样先对a初始化,然后再初始化f
9.2.5、在成员变量之间进行初始化
1 |
|
同样,根据9.2.4中的描述我们可知,我们会先对a进行初始化,然后对f初始化,所以两个变量都是12
1 |
|
上面这种初始化方式则不正确,我们将a初始化为f,这是一段未赋值的数据,会出现随机数,也可能根据编译器的不同造成报错。
9.3、引用和const的初始化
1 |
|
注意:初始化之后相当于a和f是一个变量
同时,对引用进行初始化的时候需要注意生命周期的问题
1 |
|
此时打印的a会是一个无效的数字,因为此时12这个局部变量的生命周期已经结束,引用不再有效。在构造函数内部打印则是12。
9.4、析构函数
1 |
|
- 析构函数中没有参数(没有重载)
- 析构函数前面需要一个
~
- 默认的析构函数什么都不做
- 对象生命周期结束时,自动调用
- 指针对象存放在堆中,声明时需要使用new才能触发构造函数,在使用delete或者整个程序结束时触发析构函数
- 临时对象
1 |
|
CStu();就是一个临时对象,临时对象的作用域就是当前所在的语句。
9.5、mallocfree和new delete的区别
malloc free不会触发构造函数和析构函数
new delete会触发构造函数和析构函数
9.6、this
看下面这段代码
1 |
|
这段代码在执行之后,打印a的值为-858993460
。
原因是在构造函数的作用域中,构造函数只认得a是通过参数传递过来的a,而类本身的成员(属性)a构造函数并不承认,所以最后在打印a的值的时候,会是一个随机值。
为了解决这样的问题,C++给出了this指针
1 |
|
this指针是指向当前对象的指针。
- this指针是在对象创建时才存在的
- this指针的类型是其所指向的对象的类型
- this指针不是成员,不能使用
stu.this
等方法 - this指针是类成员函数的隐含参数,每个函数都隐含一个this参数,所以我们只能在函数里面使用this指针,函数外不可使用
- this指针的作用域在类的内部,准确来说在类的函数内部
9.7、常函数
1 |
|
- 函数名() 后面增加一个const即为常函数
- 构造函数和析构函数不能是常函数
- 常函数不能修改数据成员,但是可以使用。这里的数据成员是指类内的数据成员,在函数中定义的数据成员可以被修改。
- 常函数对函数的功能有更明确的限定
- 长函数的this指针的数据类型是const CStu*
- 常对象只能调用常函数
1 |
|
上述代码中st1.fun();
是错误的。st1是常对象,只能调用常函数
9.8、static
9.8.1、静态变量
静态成员属于类作用域,但不属于类对象。它的声明周期和普通的静态变量一样,程序运行时进行分配内存和初始化,程序结束时则被释放。所以不能在类的构造函数中进行初始化。
但是可以赋值!!!不要搞混
- 静态成员无法通过初始化列表进行初始化
1 |
|
- 也无法在类内直接初始化
1 |
|
- 但是可以通过构造函数赋值的方法进行初始化
1 |
|
- 也可以在类外初始化
1 |
|
- 有一种比较特殊:静态常量整形数据成员可以在类内进行初始化
1 |
|
9.8.2、函数成员
1 |
|
函数调用方法有两种
1 |
|
9.8.3、注意
- 静态成员没有this指针
- 类内的静态函数不能调用普通的数据成员,只能调用静态成员
9.9、拷贝构造
1 |
|
9.9.1、何时调用?
新建一个对象,并将其初始化为同类现有对象
CStu st1;
- CStu stNew(st1);
- CStu stNew = st1;
- CStu stNew = CStu(st1);
- CStu* stNew = new CStu();
注意:使用对象进行赋值的时候不会调用拷贝构造函数
1
2CStu st2;
st2 = st1;当程序生成对象副本时,调用拷贝构造函数
1 |
|
在调用fun函数时会使用拷贝构造生成一个临时的对象
- 做函数的返回值时
1 |
|
注意,是在返回的时候调用拷贝构造函数
9.9.2、有何功能
默认的拷贝构造函数,逐个赋值非静态成员(成员的复制为浅复制)值,复制的是成员的值
9.9.3、浅拷贝的问题–指针
1 |
|
这段程序可以编译通过,但是运行的时候会崩溃,这就是浅拷贝的问题,指针内存的释放问题
浅拷贝只是复制了指针的值,在释放st的时候a(stu.a)指针的内存就已经被释放,而再次释放stNew的时候,仍需要对a(stNew.a)进行释放,由于此前已经释放过一次,所以程序会发生崩溃。
9.4、深拷贝
深拷贝就是亲自实现拷贝构造方法
考虑到,对象释放的时候肯定会执行析构函数,而指针是每个Stu对象必不可少的内容,所以我们需要将每个对象的a指针都申请一段空间,这样在释放的时候就不会出现问题。
1 |
|
这样的拷贝构造函数就可以实现每个Stu对象的a指针都有一段内存
9.5、内联函数
9.5.1、常规内联函数
常规函数调用需要根据函数地址跳转到函数的代码空间,然后执行指令
内联函数则是将函数的代码直接复制到函数调用的位置
二者的区别:
- 比常规函数稍快
- 代价是占用内存过多
内联函数声明的方式:
1 |
|
函数声明与函数定义位置都需要添加inline
在使用内联函数的时候需要考虑内联函数的性价比问题。而实际上我们在使用内联函数的时候,编译器不一定会答应的。
9.5.2、类内的内联函数
1 |
|
在上述的四个函数中,fun1肯定是内联函数,fun4()肯定不是内联函数
fun2()与fun3()一个在类内声明,而定义没有inline;另一个声明没有inline而定义有inline,这两个函数是否为内联函数需要看编译器的执行情况。
9.5.3、注意
内联函数的声明与定义必须在一个文件中,即使在头文件中,也需要将定义写在头文件中,不然不会得到内联的效果