C++ 基础和进阶笔记

序言

C++巩固笔记

1. 构造函数和析构函数

(1) 构造函数

@ 与类的名称完全相同
@ 特殊的成员函数 
@ 默认无参,支持传参
@ 没有返回值类型,也没有返回值,不能返回void
@ 创建对象实例时执行
@ 构造函数不能显示调用
@ 构造函数可以在类内或类外定义
@ 构造函数支持重载:一个类可以有多个构造函数,通过形参个数和类型区分
@ 构造函数内不能做任何有可能失败的操作,比如执行MML命令等
@ 严禁在构造函数中创建线程,仅作成员变量的初始化工作,其他操作通过成员函数完成
  • 构造函数的作用
    • 给新建对象建立一个标识符
    • 为对象数据开辟内存空间
    • 完成对象数据成员的初始化
  • 注意
    • 默认构造函数不能完成对象数据成员的初始化

(2) 析构函数

@ 与类的名称完全相同,前面加波浪号~
@ 特殊的成员函数
@ 与构造函数不同,不能带有任何参数
@ 不会返回任何值
@ 删除对象实例时执行,在跳出程序前释放资源
@ 析构函数只能有一个,不能被重载
@ 不同于构造函数,析构函数可以被显示调用:Instance.~xigouFun();
  • 注意
    • 默认西沟函数只能释放普通数据成员所占空间,无法释放通过new/malloc申请的空间

2. 类的定义与实现

(1) 抽象

@ 类 = 属性 + 方法
@ 类:基类-派生类 or 父类-子类
@ public/private/protected是属性/方法限制的关键字,出现顺序任意
@ 封装:尽量隐藏类的内部实现,只向用户提供有用的成员函数

(2) 实现

@ 类定义结束加分号;与结构体定义一致
@ 实现:类内 or 类外
  类外定义成员函数:
  returnType ClassName::MemberFunc(arg1, arg2, ...) {}
@ 类内定义时编译器会默认争取将方法定义为inline内联函数

(3) 使用

@ 实例化:创建类的对象
@ 访问:
  访问属性:对象名.public数据成员
  访问方法:对象名.public函数名(参数列表)
@ 类对象的作用域、可见域以及生存周期与普通变量相同
@ new 或 malloc 申请的动态内存需要手动清理,避免内存泄漏
@ 尽量避免定义public成员:考虑类的功能,尽量减少对外接口的暴露
@ 在类中声明并定义的成员函数,自动转换为内联函数

3. 继承和派生

(1) 类成员的访问权限

访问\权限publicprotectedprivate
基类YesYesYes
派生类YesYesNo
外部类YesNoNo
友元类/函数YesYesYes
对象YesNoNo
[1] public/protected/private:成员访问限定符
    不写成员访问限定符,默认为private。类体中访问父可以出现多次,建议一次
[2] 类内,public/protected/private可以互相访问,没有访问权限的限制
[3] private成员:只能在基类内或通过友元函数访问,不能通过对象访问
[4] protected成员:在基类内/派生类内/友元函数访问,不能通过对象访问
[5] 基类对象不能直接访问基类的私有成员,否则破坏了信息隐藏的目的
[6] 基类对象可以通过基类public/protected成员友元函数访问

(2) 公有继承/私有继承/保护继承

  • [1] 继承方式:
    • public/protected/private继承,如果没有访问修饰符,默认为私有继承
    • 通常使用public继承,几乎不使用protected/private继承
  • [2] 派生类继承的基类方法
    • 派生类继承了所有的基类方法,除了
      • 基类的构造函数析构函数拷贝构造函数
      • 基类的友元函数
      • 基类的重载运算符
  • [3] 继承的权限变化
继承方式publicprotectedprivate
公有继承publicprotected不可见
私有继承privateprivate不可见
保护继承protectedprotected不可见
@ 公有继承:基类的公有和保护成员可见,私有不可见。基类的私有成员可以通过基类的公有和派生成员来访问
@ 私有继承:基类的公有和保护成员作为派生类的私有成员,派生类的派生类不可访问
@ 保护继承:基类的公有和保护成员作为派生类的保护成员,派生类的成员和有源函数可访问
  • 补充:
@ 继承是权限收缩的过程
@ public继承不改变基类的访问权限
@ 基类private不受继承方式影响,派生类永远无权访问
@ private继承后,想恢复public/protected的访问权限,可通过两种方式
  1) 使用using语句
  2) 使用访问声明,base Class::member

(3) 派生类调用基类方法

  • [1] 派生类调用基类普通成员函数
int main()
{
	Derived d;
	d.show();		// 运行的是派生类的show()函数

	d.Base::show();	// 运行的是基类的show()函数
	
	Base b;			// 基类直接生成实例
	b.show();		// 运行的是基类的show()函数
	return 0;
}
  • [2] 派生类调用基类静态成员函数
int main()
{
	B b;
	b.func1();
	A::func2();		// 静态方法
	
	A *a;
	a = new B;
	a->func1();
	a->func2();		// 静态方法
	return 0;
}

(4) 多继承和虚继承

  • 继承方式
class C : public A, public B {
	...
};
  • 举例
A->D, B->D, C->(A,B),多继承方式:

	class D {...};
	class A : public D {...};
	class B : public D {...};
	class C : public A, public B {...};

继承时会使D创建两个对象,可使用虚继承

	class D {...};
	class A : virtual public D {...};
	class B : virtual public D {...};
	class C : public A, public B {...};

4. 静态类/静态成员变量/静态成员函数

(1) 静态类

@ 只用于包含静态成员的类型,不能进行实例化
@ 为什么要有静态类:防止继承,防止外部进行new操作
@ 静态类用于无需创建类实例就能访问的数据和函数
@ 静态类成员可用于分离任何对象标识的数据和行为,对象变更不会影响这些数据和函数
  当类中没有依赖对象的数据和行为,就可以使用静态类
  当一个类完全脱离实例数据和对象时就可以使用静态类
 @ 静态类可以有自己的成员变量和函数,但都必须是静态的
 @ 静态类没有基类

(2) 静态成员变量

@ 类内static关键字修饰,初始化时不能再加static
@ 静态成员变量和普通静态变量一样,存储在全局区,不占用对象内存,用于数据共享
@ 静态成员变量属于类,但不属于具体的对象,所有对象均可访问
@ 静态成员变量只能在类外初始化,不能在类内初始化。没有类外初始化的static变量不能使用

(3) 静态成员函数

@ 类内static关键字修饰,类外定义时不能加static
@ 静态成员函数不能访问对象的成员,只能访问静态成员变量和静态成员函数
  原因:编译器不会为静态成员函数分配this指针
@ 与静态成员变量一样,不管有没有创建对象,都可以调用静态成员函数
  原因:没有this指针,无法在函数体内部访问某个对象,包括变量和函数
@ 静态成员函数和普通成员函数的根本区别
  普通成员函数:有this指针,可以访问类中任意成员
  静态成员函数:没有this指针,只能访问静态成员

5. 友元函数

(1) 使用场景

  • 友元函数破坏了封装机制,除非不得已的情况下才使用
    • [1] 两个类要共享数据时
    • [2] 运算符重载的某些场合要使用友元

(2) 友元函数参数

  • 友元函数没有this指针
    • [1] 访问非static成员,对象做参数
    • [2] 访问static成员或全局变量,不需要对象做参数
    • [3] 参数是全局对象,不需要对象做参数

(3) 友元函数的位置

  • 类内可放在私有或公有,没有区别,通常写为私有
  • 能访问protected和private成员

(4) 与普通成员函数的区别

  • 普通成员函数有this指针,友元函数没有this指针
  • 友元函数不能被继承
  • 友元函数不是当前类的成员函数,而是独立于类的外部函数

6. 重载:函数重载和运算符重载

选择最合适的重载函数或重载运算符的过程,称为重载决策

(1) 函数重载

  • 功能类似的同名函数,参数个数、类型或者顺序必须不同,包括返回类型
  • 构造函数支持重载,通过参数个数、类型或顺序来区分

(2) 运算符重载

  • [1] 什么是运算符重载:本质是一个函数,通过运算符重载重新定义运算符的使用
  • [2] 可重载和不可重载运算符
    可重载运算符:
    在这里插入图片描述
    不可重载运算符:
    在这里插入图片描述
  • [3] 运算符重载格式
returnType operator 运算符 (参数)
#include <iostream>

using namespave std;

class Demo {
public :
	// 友元函数形式实现运算符重载
	friend Demo & operator+ (Demo &demo, int n);
	Demo (int a)
	{
		this->a = a;
	}
	int GetA()
	{
		return a;
	}
private:
	int a;
};

// 重载"+"运算符
Demo & operator+ (Demo &demo, int n)
{
	demo.a += n;
	return demo;
}

int main()
{
	Demo demo(1);
	// 重载"+"运算符后,对象可以直接加一个数
	demo = demo + 2;
	cout<<demo.GetA()<<endl;
	return 0;
}
  • 补充:
    • 构造函数支持重载,但不支持运算符重载
    • 重载不会改变运算符的操作对象(双目仍然是双目),也不会改变运算符原有的优先级和结合性
    • C++支持运算符重载可能是为了与内置数据类型统一操作,add(a, b)和a + b

7. 函数调用

(1) 成员函数调用格式

  • C++中,类对象定义为:指针对象 || 一般对象
    • 指针对象:"->"访问类中成员
    • 一般对象:"."访问类中成员
class A {
	A *p;
	p->func();
	(*p).func();
	
	A p;
	p.func();
};

(2) 类内成员函数之间访问

  • 调用public成员:
    • [1] 直接调用:OtherMemberFunc();
    • [2] 类作用域调用:class::OtherMemberFunc();
    • [3] 类对象调用:class inst; inst.OtherMemberFunc();
  • 调用private成员:
    • [1] 直接调用:privateVar;
    • [2] 类作用域调用:class:privateVar;

(3) 不同类之间成员函数的访问

  • 对象实例化引用,private通过public成员调用
void B::DoSomething()
{
	A a;
	a.PrintMe();
}

8. 虚函数和纯虚函数

(1) 虚函数

@ 虚函数存在的唯一目的是为了实现多态
@ 声明:virtual returnType Function();
@ “推迟联编/动态联编”:编写代码的时候不能确定被调用哪个的是基类的函数还是那个派生类的函数,所以称为虚函数
@ 基类虚函数必须实现,否则链接时会报错
@ 虚函数,基类和派生类各有版本,多态方式动态绑定
@ 虚函数重写:基类中可以是private,派生类中可以是publicprotected

(2) 纯虚函数

@ 定义纯虚函数的目的在于:使派生类仅仅继承函数的接口;
@ 纯虚函数引入是为了解决“要使用动态特性,但基类本身生成对象不合理”的情况,如动物类实例
@ 声明:virtual returnType Function() = 0;
  纯虚函数的声明在于告诉派生类设计者:你必须提供一个纯虚函数的实现,但我不知道你会怎么实现它

(3) 抽象类

@ 声明了纯虚函数的类是一个抽象类
@ 抽象类不能定义对象/实例,但可以声明只想该抽象类的具体类的指针或引用
@ 抽象类作为基类,纯虚函数的实现由派生类给出,如果派生类未重新定义纯虚函数只是继承,则派生类仍然是一个抽象类,而不是一个具体的类
@ 派生类实现纯虚函数后,该纯虚函数在派生类中就变成了虚函数,派生类的派生类可重新该函数
  • 抽象类实例化报错:
error: cannot declare varible 'inst' to be of abstract type 'Class'
note: because the following virtual funcs are pure with 'Class'

9. 命名空间 using namespace std;

  • 使用一个名为std的命名空间
  • (1) 作用:
    • [1] 避免命名冲突:避免与标准库标识符命名冲突
    • [2] 使用命名空间中的地故意和声明的所有标识符,如std中cin/cout/endl/hex/函数等。std中定义和声明的所欲标识符在本文件冲都可以作为全局变量使用

10. {…}后是否加分号问题

  • (1) 一句话总结:语句定义不需要分号结尾,声明需要分号结尾
  • (2) 使用举例
    • [1] 语句:while/for/if语句不用加分号,{}本身是语句的一部分
    • do {…} while(1); 执行语句后判断是否执行下一次循环,}不能代表结尾,需要加分号
    • [2] 定义:函数定义,{}不需要加分号
    • [3] 声明:结构体、枚举、类定义是声明,所以需要分号结尾,类似变量int a;

11. 默认参数和可变参数

(1) 默认参数

  • 定义:函数在定义或声明时,给定参数的默认值。如果用户指定了参数值,就使用用户指定的值,否则使用默认参数的值。
  • 注意点:
@ 默认参数可以放在定义或声明中,但二选一。最好放在声明处便于引用
  声明和定义都有,会有编译错误
  大部分情况下,别人调用哪个代码只能看到函数声明
  写在定义处,使用该函数前要把定义放在前面,否则编译无法确定该函数是否默认参数
@ 如果某个参数是默认参数,那么它后面的参数都必须是默认参数
  即:如果函数含有默认参数,那么最后一个参数一定是默认参数
@ 不要重载一个带默认参数的函数 或 函数重载时谨慎使用默认参数
  含有默认参数的函数调用存在歧义,举例如下
  默认参数可将一系列简单的重载函数合成一个
@ 默认参数不仅可以用常数,还可以用任何有定义的表达式作为参数默认值
/* 例1:带默认参数函数重载,调用存在歧义 */
class Test {
public:
	int func(int a)
	{
		return a;
	}
	int func(int a, int b = 1)
	{
		return a + b;
	}
};

func(2);	// 不知道是调用第一个还是第二个带默认参数的函数

/* 例2: 表达式作为参数默认值 */
int Max(int m, int n);
int a = 1, b = 2;
void Function(int x, int y = Max(a, b), int z = a * b)
{
	...
}

Function(4);	// 正确。等效于Function(4, Max(a, b), a * b);
Function(4, 9);	// 正确。等效于Function(4, 9, a * b);
Function(4, 2, 3);	// 正确
Function(4, , 3);	// 错误。省略的默认参数一定是最右边连续的几个

(2) 变长参数/可变参数

  • 定义:函数参数个数不定。
  • 声明:returnType Func(int num, …);
  • 方法:
va_list args;			// 创建一个可变参数列表
va_start(args, num);	// 初始化args指向强制参数的下一个参数
va_arg(args, type);		// 获取当前参数内容并将args指向下一个参数,for循环获取
va_end(args);			// 释放args
  • 说明:
@ 使用到三个宏:va_start, va_arg, va_end,va_list只是一个char指针
  typedef char *valist ...
@ 由于开始的时候从右至左把参数压栈,va_start传入最左侧的参数,往右的参数依次更早被压入栈,一次地址依次递增(栈顶地址最小)。va_arg传入当前需要获得的参数的类型,便可以利用sizeof()计算偏移量,一次获取后面的参数值
  • 备注:
@ 函数调用过程中参数传递是通过栈实现的,从右到左的顺序将参数压栈
  参数之间是相邻的,知道前一个参数的类型及地址,根据后一个参数的类型就可以获取后一个参数内容,以此类推可实现可变参数函数所有参数的访问。
@ 如果入参少了(<num)则会访问到参数以外的区域,出现异常
/* 参数访问过程 */
#include <stdio.h>

void debug(unsigned int num, ...)
{
	unsigned int i = 0;
	unsigned int *addr = &num;
	for (i = 0; i <= num; i++) {
		/* 从右往左依次取出传递进来的参数,相当于从栈底到栈顶 */
		printf("i=%d, value=%d\r\n", i, *(addr + i));	// 压栈过程地址递减
	}
}

int main(viod)
{
	debug(3, 66, 88, 666);
	return 0;
}

2020.07.18 create by shuaixio

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值