1、inline 关键字
- 使用内联函数可以节省运行时间,因为编译的时候,内联函数相当于直接把代码copy到调用处,可以节省函数调用的堆栈开销。
- 一般只将规模很小,使用频繁的函数生命为内联函数,从代码整洁角度来说,这是很合理的(但是真正的原因是?)。
- 内联函数不能包含复杂的控制语句。
- 类的成员函数也可以指定为内联函数。
2、 通过引用变量访问对象中的成员
- 引用变量相当于为一个变量起别名,有时候他的功效和指针很像,底层是怎么实现的有待探讨。
3、构造函数的规则
- 构造函数不需要用户调用,而是在建立对象时候自动调用(注意,这个在对象没有初始值的时候,是不会调用的,这个说话有误。)
- 构造函数的名字必须和类名字相同,而不能有用户任意命名
- 构造函数不具有任何类型,不返回任何值
- 构造函数的功能有用户定义的,用户根据初始化的要求设计函数体和函数参数
- 带默认参数的构造函数,会和默认构造函数产生歧义,比如 student(); 和 student(int age = 0); 会导致编译错误。 正确的写法为
正确的写法:
student();
student(int); // 不带默认参数
4、关于释构函数的注意点
- 释构函数不返回任何值,没有函数类型,也没有函数参数
- 释构函数不能被重载,一个类可以有多个构造函数,但是只能有一个释构函数
- 释构函数的作用并不仅限于释放资源,它还可以被用来执行”用户希望在最后一次使用对象之后所执行的任何操作“
- 如果用户没有定义释构函数,C++编译系统会自动生成一个释构函数
5、const关键字的作用
常对象
用const修饰的对象,里面的所有成员变量不能被修改
凡是希望数据成员不被修改的对象,都可以声明为常对象,声明方法: const object(0) 或者 object const(0);
常对象成员变量必须有初始值,否则编译报错。
指向对象的常指针
将指针变量声明为const类型,其指针的值不能改变
一般定义方式为 object * const abc;
object a(0) , b(0) ; object * const pt = &a; pt = &b; //错误
指向常变量的指针变量
const object * a / object const * a;
如果一个变量已被声明为常变量/对象,只能用指向常变量/对象的指针指向它,而不能用指向非
指向常变量的指针变量可以指向未被声明为const的变量,但不能通过此指针变量改变该变量的值。
指向常对象的指针最常用于函数的形参,以保护形参指针所指向的对象在函数执行过程中不被修改。
void doSomething1(const Test *p1)
{
p1->setX(5); //非法!
p1->printxy( );
}
| 数据成员 | 非const成员函数 | const 成员函数 |
| --- | --- || --- |
| 非const数据成员 | 可引用,可改变值 | 可引用,不可改变值
| const数据成员 | 可引用,不可改变值 | 可引用,不可改变值
| const对象数据成员 | 不可引用,不可改变值 | 不可引用,不可改变值
6、对象的赋值和复制
- 同类对象之前可以互相赋值
- 对象赋值的一般形式为 obj1 = obj2
- 对象赋值的实现原理是赋值运算符的重载
7、对象的复制
- 用已有的对象克隆出一个新对象
- 其一般形式为 类名 对象2(对象1);
- 原理是编译系统提供默认复制构造函数
- C++的所有类型都实现了复制构造函数,基本类型也是,比如 int a(5),调用int的赋值构造函数,把参数5传入生成一个int 类型。
- 当函数参数为类对象的时候,调用函数时,实参赋值给形参会调用对象的复制构造函来生成一个实参的拷贝
- 当函数返回值为对象时,在函数调用完毕,会复制一个对象返回给调用处
复制构造函数:
Object::Object(const Object& b){
a = b.a
}
8、友元
友元函数
- 如果在本类以外其他地方定义的函数,在类体中用friend进行声明,此函数成为本类的友元函数,该函数可以访问类的私有成员变量。
- 友元函数可以属于不是任何类的非成员函数,也可以是其他类的成员函数。
- 一个函数(普通函数和成员函数)可以声明为多个类的友元函数,这样就可以访问多个类的私有数据。
友元类
- 如何一个类中声明了其他类为友元类,那么其他类就可以随意反问这个类的私有数据。
- 一般情况下不需要用到友元类,虽然友元类有助于数据共享,但是严重影响了类的封装性,不利于信息隐藏,大多数问题用友元函数就能解决。
9、运算符重载
运算符重载的意义
- 通过运算符重载,扩大C++已有运算符的作用,使用运算符可以作用于类
- 使用运算符重载技术,能使程序易于编写,阅读和维护
- 运算符被重载后,其原有的功能依然保留,没有丧失或改变
- 运算符重载实质是函数的重载
运算符重载的实现方法
对象+法运算符重载
成员函数实现
// 写法一
object object::operator+(object & obj2){
object obj3;
obj3.a = obj2.a;
return obj3;
}
// 写法二
object object::operator+(object & obj2){
// 直接调用构造函数返回
return object(obj2.a);
}
非成员函数实现方案
object operator+(object & obj1, object & obj2){
// 直接调用构造函数返回
return object(obj1.a + obj2.a);
}
运算符重载的规则
- 不允许创造新的运算符,只能对已有C++的运算符进行重载
- C++ 不允许的重载运算符有5个分别是:
成员运算符.
成员指针访问运算符.*
作用域运算符::
sizeof
条件运算符?:
- 重载不能改变运算符运算对象(就是不能改变运算符的操作数)
- 重载不能改变运算符的优先级
- 重载不能改变运算符的结合性
- 重载的运算符的函数不能带有默认参数
- 重载的运算符必需和用户定义的自定义类型对象一起使用,参数至少有一个是类对象或其引用
10、类(继承类)访问权限
- 访问权限不影响类的内存布局
- struct 成员变量和继承方式默认都是public
- class 成员变量和继承方式默认都是private
- 如果成员变量和继承方式同时都有访问权限限制,则遵从最小访问权限原则,比如类继承是protected,但是成员变量是private,那么最终这个成员变量的权限还是private
| * |public | protected | private |
| --- | --- || --- | --- |
| 同一个类 | YES | YES | YES |
| 派生类 | YES | YES | NO |
| 外部类 | YES | NO | NO |
11、匿名函数——lambda表达式
- 声明Lambda 表达式
[capture list] (params list) mutable exception -> return type { function body}
各项具体含义
1. capture list 捕获外部变量列表
2. params list 形参列表
3. mutable 指示符,用于说明是否可以修改捕获的变量
4. exception 异常设定
5. return type 返回类型
6. function body 函数体
// 实例
int a0 = 1;
int b0 = 2;
auto funcs = [=, &b0](int c)->int {return b0 += a0 + c;};
int ress = funcs(6);
12、命名空间
- 为了解决C++标准库中的标识符与程序 中的全局标识符之间以及不同库中的 标识符之间的同名冲突,标准C++库的 所有的标识符都定义在一个名为std的 命名空间中。
- C++有一个默认的命名空间,为全局命名空间为::
- 命名空间是ANSI C++引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突。
- 同名字的命名空间会在编译过程中进行合并
13、有继承关系的构造函数调用
- 子类会默认调用父类的无参构造函数
- 如果子类的构造函数显示地调用了父类的有参构造函数,就不会再去调用默认构造函数
- 如果父类缺少无参构造函数,子类的构造函数必须显示地调用父类的有参构造函数
14、多态
静态多态
- 函数重载和运算符重载实现的多态性属于静态多态性
- 在程序编译时系统就能决定调用的是哪个函数,因此,又称编译时的多态性
- 静态多态性是通过函数的重载实现的(运算符重载实质上也是函数重载)
动态多态
动态多态性是在程序运行过程中才动态地确定操作所针对的对象,动态多态性又称运行时的多态性
- C++默认没有实现动态多态,只会根据指针类型去调用对应的函数。
- C++的动态多态通过virtual 函数来实现
- 如果父类的函数是虚函数,子类重写该函数也是虚函数
- 调用new object 和 new object() 是有区别的,后者会把成员变量初始化为0。
15、C++类型转换
- 在C++中某些标准类型的数据之间可以自动转换
- 隐式类型转换由C++编译系统自动完成的,用户不需要干预
int i = 6;
i = 7.5 + i;
- 强制(显式)类型转换是指在程序中将一中类型数据明确转换成另一指定的类型
const_cast
- 一般用于去除const属性,将const转换成非const,比如
const Object obj1 = new Obejct();
Object * obj2 = static_cast<Object>(obj1);
const Person * pp1 = new Person();
// pp1->age = 10; // error 因为是const 对象
Person * pp2 = const_cast<Person *>(pp1) ;
pp2->display();
pp2->age = 20;
pp2->display();
dynamic_cast
- 一般用于多态类型的转换,有运行时安全检测。如果指针转换的时候,子类指针类型可以转换成父类,但是父类不能转换成子类。
class Person{
virtual do();
};
class Student:public Person {
};
class Other{
};
Person * aa = new Person();
Person * bb = new Student();
Student * stu1 = dynamic_cast<Student*>(aa);// 返回为NULL
Student * stu2 = dynamic_cast<Student*>(bb);
Other * oth1 = dynamic_cast<Other*>(stu2); // 返回为NULL
//返回为NULL 证明类型转换失败
reinterpret_cast
- 属于比较底层的强制转换,没有任何类型检查和格式转换,仅仅是简单的二进制数据拷贝
- 可以交叉转换,可以将指针和整数互相转换
16、模板
- C++中利用模板来实现泛型
- 所谓的泛型,是一种将类型参数化以达到代码复用的技术
- 模板的使用格式如下
template <typename\class T>
typename和class是等价的
- 模板没有被使用时,是不会被实例化出来的
- 模板的声明和实现如果分离到.h和.cpp中,会导致链接错误,所以一般将模板的声明和实现统一放到一个.hpp文件中
17、智能指针
传统的指针需要手动管理内存,非常容易导致内存泄露和野指针等问题,针对这一问题,C++采用了智能指针的方案来解决。
auto_ptr
- auto_ptr 属于C++98标准,在C++11 已经不推荐使用(有缺陷,比如不能用于数组)
- auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放
- 一块动态内存只能被一个auto_ptr 对象持有。
shared_ptr
- 被shared_ptr持有的对象会产生强引用,一个shared_ptr产生一个强引用计数
- 可以通过shared_ptr的use_count 属性查看被引用对象的强引用计算器
- 当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1
- 当有一个shared_ptr销毁时,对象的强引用计算就会-1
- 当一个对象的强引用计算为0是(没有任何shared_ptr指向对象时),对象就会自动销毁(调用释构)
- shared_ptr 会产生循环引用的问题,可以通过weak_ptr解决循环引用的问题
unique_ptr
- unique_ptr也会对对象产生强引用,但是它可以确保同一时间只有一个指针指向对象
- 当unique_ptr销毁时(作用域结束时),其指向的对象也就自动销毁
- 可以使用std::move 函数转移unique_ptr的所有权
unique_ptr<Object>ptr1(new Object());
unique_ptr<Object>ptr2 = std::move(ptr1); //转移所有权
make_shared
利用这个模板函数对智能指针进行初始化效率更好
//传统初始化智能指针的写法
std::shared_ptr<Widget> spw(new Widget);
//利用make_shared 函数初始化的写法
auto spw = std::make_shared<Widget>();
- 每个std::shared_ptr都指向一个控制块,控制块包含被指向对象的引用计数以及其他东西。这个控制块的内存是在std::shared_ptr的构造函数中分配的。因此直接使用new,需要一块内存分配给Widget,还要一块内存分配给控制块。
- std::make_shared申请一个单独的内存块来同时存放Widget对象和控制块。这个优化减少了程序的静态大小,因为代码只包含一次内存分配的调用,并且这会加快代码的执行速度,因为内存只分配了一次。另外,使用std::make_shared消除了一些控制块需要记录的信息,这样潜在地减少了程序的总内存占用
18、联合体
- 在查看oc runtime源码发现,原来C++的联合体 union 像结构体一样,可以编写构造函数,有private和public关键字,可以编写方法。。。。。
union aOBJ {
private:
int value;
public:
aOBJ(int value = 0){ this->value = value;};
void say(){ std::cout << "saySomething" << value << std::endl;};
};
19、override关键字
- C++11 中的 override 关键字,可以显式的在派生类中声明,哪些成员函数需要被重写,如果没被重写,则编译器会报错。