C++ 语言常见重要结论。

c++对于c语言的加强

namespace 命名空间

没啥可以讲的,避免了类名字,方法,成员变量的冲突。

实用性增强

变量可以边定义边用。

变量检测增强

int a;
int a=8;会报错了,不像c 不会报错。

struct 类型增强

c++的struct 类似于class ,唯一的不同是class定义的函数默认是private 的,但是struct 定义的函数是public 的。而且,c不认为这是一种新类型,仍然需要struct AAA,进行定义,但是c++则不需要,直接使用AAA 进行定义对象。即认为是一种新的类型。

C++中所有变量和函数都必须有类型

如下代码在c语言是OK的,但是在c++就不行咯。

f(i) {
printf("i = %d\n", i); }
g() {
return 5; }

新增bool 类型,返回值为true 和false

三目运算符增强

返回的是引用,而不是值,所以可以对其进行赋值。

const 增强。

可以分清输入与输出的参数。带const往往是输入。在c语言中,const 是冒牌货。
c++的const由编译器处理,提供类型检查与作用于检查。
#define由预处理器处理,单纯的文本替换。
c++的const 可能分配空间,也可能不分配空间。
当const 常量是全局的,并且需要在其他文件使用。会分配空间。
当使用&操作符。取const 地址,会分配空间。
const int &a=10; const 修饰引用,也会分配空间。

引用。

Type& name = var;
引用的规则。
1.引用没有定义,是一种关系声明,声明和他原有的某一个变量关系,故不分配 内存,与被引用变量有着同样的指向地址。
2.声明的时候,必须得初始化,一经声明,不能改变。
引用的本质

#include <iostream>
int main() {
int a = 10;
int &b = a; // 注意: 单独定义的引⽤用时,必须初始化。
b = 11;
printf("a:%d\n", a); 
printf("b:%d\n", b); 
printf("&a:%p\n", &a); 
printf("&b:%p\n", &b);
return 0; }

引用在c++中的内部实现是一个常指针,即一个指向不能改变的指针。
Type& name <===> Type* const name
c++在编译过程中。使用常指针作为内部实现。因此,引用所占用的空间与指针大小相同。
从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这个是c++为了实用性,而做出的细节隐藏。

void func(int &a)
{
a = 5; }
void func(int *const a)
{
*a = 5; }

间接赋值的3各必要条件
1定义两个变量 (一个实参一个形参)
2建立关联 实参取地址传给形参
3*p形参去间接的修改实参的值.
引用的实现上。只不过是把,间接赋值成立的三个条件的后面2个合二为一了。当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工。取了一个实参的地址,传给了一个常引用。(常量指针)

引用作为函数的返回值(引用当左值)
当函数返回值为引用时候。若返回栈变量,不能成为其他引用的初始值。(不能作为左值引用。)
当函数返回值为引用时,如果返回静态变量或全局变量,可以成为其他引用的初始值。可以作为右值,也可以成为左值。

什么是引用当左值?

int& fun(){
    int a=10;
    return a;
}
fun()=10;
//如下的只是值拷贝。
int x=fun();

指针引用

const 引用

const对象的引用必须是const的,将普通引用绑定到const 对象是不合法的。如下。

const int a=1;
int &b=a;
//常引用在初始化其实变成了2个步骤
int temp = val;
const int &ref = temp;
这也是为什么
int &a=1;//是错误的
const int &a=1;//却是正确的的原因。

结论:
1.const int &a;相当于const int * const a;
2.普通引用相当于int * const e;
3.当使用const对应用初始化的时候,c++会为常量值分配空间,并将引用名作为这段空间的别名。
4.使用字面量对const引用初始化后,将生成一个只读的变量。

inline 函数

可以想是宏函数的拓展,内联的特点。
1.没有函数调用的开销。(压栈,跳转,返回)
2.内联函数由编译器处理,直接将编译后的代码插入调用的地方。宏片段由预处理器处理,进行简单的文本替换,没有任何的编译过程。
同时存在着一定的限制。

  • 不能存在任何形式的循环语句
  • 不能存在过多的条件判断语句
  • 函数体不能过于庞大
  • 不能对函数进行取址操作
  • 函数内联声明必须在调用语句之前

当函数体执行的开销远远大与压栈,跳转,返回的开销,内联将失去意义

函数重载

C++利用 name mangling(倾轧)技术,来改名函数名,区分参数不同的同 名函数。
实现原理:用 v c i f l d 表示 void char int float long double 及其引 用。
每一个方法都有一个特殊的Symbol 符号。

拷贝构造函数调用的场景

场景1

Test t1(10);
Test t2=t1;

场景2

Test t1(10);
Test t2(t1);//使用对象t1初始化对象t2

1.函数返回值是一个元素(复杂类型),返回的是一个新的匿名对象(所以会调用匿名对象的copy构造函数)

2.有关匿名对象的去和留
如果匿名对象去初始化另外一个同类型的对象,匿名对象被扶正
如果用匿名对象去赋值给另外一个同类型的对象,匿名对象被析构

new delete

在这里插入图片描述
注意,new 与delete 返回的都是指针类型。是一块内存地址。不能够用普通对象直接接收,与malloc delete 有着同样的功能,唯一的不同是,malloc free在初始化类对象的时候,是不会自动的帮我们调用构造函数,但是new 与delete 是会自动的帮我们调用。

静态成员的定义

static 数据类型 成员变量; //在类的内部
//初始化
数据类型 类名::静态数据成员 = 初值; //在类的外部
//调⽤用 类名::静态数据成员 类对象.静态数据成员

编译器对属性和方法的处理机制。

普通成员变量,存储于对象中,与struct变量有着相同的内存布局和字节对齐方式。
静态成员变量,存储于全局数据区
成员函数,存储于代码区

很多对象共用一块代码,代码如何区分具体对象是那?
如下的图应该算是很经典的一张图了。
在这里插入图片描述

c++的普通成员函数都隐含一个包含当前对象的this指针
静态成员函数不包含指向具体对象的指针。普通成员函数含有一个指向具体对象的指针。

友元

友元声明以关键字friend开始,只能在类定义中出现,因为不是授权类的成员,所以他不受类声明的public private 和protected的影响
利弊:可以直接访问类中的私有成员,破坏了类的封装性和隐蔽性。但是提高了编码的灵活性。
注意事项
友元关系无法继承
友元关系无法交换
友元关系不具有传递性

运算符重载

在这里插入图片描述

在这里插入图片描述

执行重载的时候,记得对参数加const

双目运算符重载

//使⽤用: L#R operator#(L,R); 
//全局函数 L.operator#(R); //成员函数
Complex& operator+=(const Complex &c) {
	this->_x += c._x; this->_y += c._y;
    return * this;
}

重载规则请务必记住

//重载+号 (全局)
friend const Complex operator+(const Complex &c1,const Complex &c2);
//重载+号(成员)
const Complex operator+(const Complex &another);
//a+=b;对于a应该返回的还是原来的a 所以不能为const 
Complex& operator+=(const Complex &c)
//a-=b;同样的是返回可以修改的内容。
friend Complex& operator-=(Complex &c1, const Complex & c2);
//前置++ 注意返回的是引用
friend Complex & operator++(Complex& c);
friend const Complex operator++(Complex &c,int);
friend ostream & operator<<(ostream &os, const Complex & c);
friend istream & operator>>(istream &is, Complex &c);

//赋值运算符重载 返回的是引用,不能用const 修饰,其目的是连等式
A& operator=(const A& another)
//数组符号的重载
类型 类 :: operator[] ( 类型 ) ;

结论:
1,一个操作符的左右操作数不一定是相同类型的对象,这就涉及到将该操作符函 数定义为谁的友元,谁的成员问题。
2,一个操作符函数,被声明为哪个类的成员,取决于该函数的调用对象(通常是左 操作数)。
3,一个操作符函数,被声明为哪个类的友员,取决于该函数的参数对象(通常是右 操作数)。

重载&& 和||会丢失短路特性。

无参构造函数调用不能带括号!

类的继承方法

class 派⽣生类名:[继承⽅方式] 基类名 { 派⽣生类成员声明;
};

在这里插入图片描述

private成员在子类中依然存在,但是却无法访问到。不论何种方式继承 基类,派生类都不能直接使用基类的私有成员 。

三种继承关系

保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生
类 中,而基类的私有成员不可访问。

当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成
员身 份出现在派生类中,而基类的私有成员在派生类中不可访问。

当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生
类中 不变,而基类私有成员不可访问。

不论是什么类型的继承,在基类中不能使用的,只有私有成员,其余就算因为私有继承而退化为私有的,子类也是依然可以进行使用 的。

1.子类对象在创建时会先调用父类构造函数
2.父类构造函数执行结束后,执行子类构造函数
3.当父类构造函数有参数时,需要子类初始化列表中显式调用
4.析构函数的调用顺序与构造函数刚好相反。

先构造父类,再构造成员变量,最后再构造自己
先析构自己,再析构成员变量,最后析构父类

继承总结

1、当子类成员变量与父类成员变量同名时
2、子类依然从父类继承同名成员
3、在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基
类的同名成员,显式地使用类名限定符)
4、同名成员存储在内存中的不同位置

cout<<c.age<<endl;
cout<<c.Parent::age<<endl;
cout<<c.Child::age<<endl;

派生类中的static关键字

基类定义的静态成员,将被所有派生类共享

根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具 有不同的访问性质 (遵守派生类的访问控制)

派生类中访问静态成员,用以下形式显式说明:
类名::成员、对象名 . 成员

静态成员变量,无论使用父类名,还是子类名都可以直接调用得到。或者是实例,也可以调用。

cout<<c.count<<endl;
cout<<Parent::count;
Parent::count++;
cout<<Child::count;
Child::count++;
cout<<Object::count;
Object::count++;
cout<<Object::count;

多态

一般情况下,如果基类与子类有着同名的方法,即子类重写了父类的方法。如果使用父类指针去承接子类对象,那么调用被重写的方法的时候,是执行父类的方法,而不是子类的方法。
在这里插入图片描述
如何实现,根据对象实际类型去打印?virtual 可以解决。通过使用virtual 修饰函数。可以视作是java中的abstract 抽象方法,不过这个virutal方法在是可以有方法体的。

virtual int fun()

多态成立的3个条件。
1.要有继承
2.要有虚函数重写
3.要有父类指针(父类引用)指向子类对象。

静态联编 动态联编

静态联编,由于程序没有运行,所以不可能知道父类指针指向的具体对象是父类对象还是子类对象,从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译结果为调用父类的成员函数。

多态是发生在动态联编,是在程序执行的时候,判断父类指针应该调用的方法。

虚析构函数

虚析构函数用于指引delete操作符正确析构动态对象。
在手动delete obj时候,如果是父类指针,没有写virutal 析构,那么只会执行父类的析构函数代码,而不会执行子类的析构函数。

重载 重写 重定义

函数重载
必须在同一个类中进行,子类无法重载父类的函数,父类同名函数将被名称覆盖,重载是在编译期间根据参数类型和个数决定函数调用

//以下都是重载
void a();
void a(int)
void a(char)
void a(int,char)

函数重写

必须发生在父类与子类之间 ,并且父类与子类必须有完全相同的原型
使用virtual声明之后能够产生多态(如果不使用virutal ,那就叫重定义)

virtual void fun();
-->
void fun();//多态如果没有virutal 那就是重定义

虚函数表和Vptr指针

1.当类声明虚函数时,编译器会生成一个虚函数表
2.虚函数表是一个存储类成员函数指针 的数据结构
3.虚函数表是 编译器自动生成和维护的。
4.virtual 成员函数会被编译进入虚函数表中。
5.存在虚函数的时候,每个对象中都有一个指向虚函数表的指(vptr指针。)

在这里插入图片描述

在这里插入图片描述

编译器确定func 是不是虚函数
1)不是,编译器可直接确定被调用的成员函数(静态联编,根据父类类型决定)

2) 是,编译器根据对象的vptr指针,所指向虚函数表查询到func 函数并且调用。注意,查找与调用是在运行的时候执行

由于虚函数是在运行的时候动态进行调用。效率低是一个问题。构造函数无法实现多态,父类构造的时候,vptr 指向父类的虚函数表,子类构造时候,指向子类。

base(){
	this->print()///调用的是父类的print方法
}
child(){
	this->print()//调用的是子类的print 方法
}

纯虚函数

virtual 类型 函数名(参数表) = 0;类似于java 的抽象方法。主要特点是无方法体。子类继承必须实现,否则子类也是抽象类’

1.含有纯虚函数的类,称为抽象基类,不可实列化。即不能创建对象,存在 的意义 就是被继承,提供族类的公共接口。
2,纯虚函数只有声明,没有实现,被“初始化”为 0。
3,如果一个类中声明了纯虚函数,而在派生类中没有对该函数定义,则该虚函数在 派生类中仍然为纯虚函数,派生类仍然为纯虚基类。

class Triangle:public Shape{
protected:
    double w;
    double h;
public:
    Triangle(double w,double h):w(w),h(h){}
    virtual double getArea() {
        return w*h/2;
    };
};
class Rectangle:public Triangle{
public:
    Rectangle(double w,double h):Triangle(w,h){
    }
    double getArea(){
        //可以调用到父类方法,只不过需要加上作用范围。
        return this->Triangle::getArea()*2;
    }
};

C 语言面向接口编程

c 语言通过函数指针实现面向接口编程

关于模板类使用友元重载<<的部分说明

如果单独把代码写在h文件里面,此时需要注意的细节会比较少,但是如果需要分文件,即使h cpp
如下只是重载了<<运算符。

friend ostream& operator<< <T>(ostream &o,Animal<T> t);

//此时,在h文件头我们需要做如下声明
template<class T>
class Animal;

template<class T>
ostream& operator<<(ostream &o,Animal<T> t);
//同时,我们需要在cpp文件里面完善代码。
template<class T>
ostream& operator<<(ostream &o,Animal<T> t){
    o<<t.t;
    return o;
}

//最后,在main里面,别忘记了,要引入我们的cpp文件。

在这里插入图片描述
所以这也是为什么我们对于这些模板类的编写,一般就用一个文件hpp,我们不把代码分离开,因为如果分开用户在使用的时候,还需要手工引入hpp,多麻烦啊。所以干脆写在一个文件里面算了。

一般性结论
friend ostream & operator<< <T> (ostream &os, Complex<T> &c); //在模板类中 如果有友元重载操作符<<或者>>需要 在 operator<< 和 参数列表之间 //加⼊入
//滥⽤用友元函数,本来可以当成员函数,却要⽤用友元函数
//如果说是⾮非<< >> 在模板类中当友元函数
//在这个模板类 之前声明这个函数
friend Complex<T> mySub <T>(Complex<T> &one, Complex<T> &another); //最终的结论, 模板类 不要轻易写友元函数, 要写的 就写<< 和>> 。

传说中的hpp在这里插入图片描述

类型转换

C++风格的类型转换提供了 4 种类型转换操作符来应对不同场合的应用。
static_cast 静态类型转换。如 int 转换成 char
reinterpreter_cast 重新解释类型
dynamic_cast 命名上理解是动态类型转换。如子类和父类之间的多态类型转换。 const_cast, 字面上理解就是去 const 属性。

由于二次编译,模板类在.h在第一次编译之后,并没有最终确定类的具 体实现,只是编译器的词法校验和分析。在第二次确定类的具体实现后,是 在.hpp文件生成的最后的具体类,所以main函数需要引入.hpp文件。
综上:引入hpp文件一说也是曲线救国之计,所以实现模板方法建议在同 一个文件.h中完成

在这里插入图片描述

异常

捕捉万能异常?


//设置未知异常处理回调函数
set_terminate(my_tm_h);

//typedef void (*terminate_handler)();
//编写回调函数
void my_tm_h(){
	cout<<"error occure with clear reason!"<<endl;
}

在这里插入图片描述
在这里插入图片描述
统一的异常处理机制,将让我们捕捉异常的时候,更加方便。能够进行统一的控制操作。

关于标准输入输出流

标准输入流对象cin

cin.get() //一次只能读取一个字符
cin.get(一个参数) //读一个字符
cin.get(三个参数) //可以读字符串
cin.getline() //获取缓冲区的一行
cin.ignore() //跳过缓冲区几个字符
cin.peek() //查看缓冲区有没有数据,阻塞
cin.putback() //塞进去缓冲区
ctrl-z 将产生一个EOF,mac 测试无效。

标准输出流对象cout

控制符详解

控制符,可以直接cout<<控制符,也就是追加

控制符作用
endl不做解释
dec设置数字基数为10进制
hex16进制
oct8进制
setfill(’*’)填充字符,一般与setw 配合使用,一次性用,endl作废
setprecision(n)设置浮点数字的输出精度,多次有效
setw(12)设置字段宽度,本次有效。仅仅数字不足才补足,一旦设置setprecision将优先填充到precision 的位数
setiosflags(ios::a|ios::b…)可以设置标志位
resetiosflags(ios::a|ios::b…)重置io标志
标志位作用讲解
标志位作用
ios::fixed有效数字位数默认为为6位,可以通过setprecision来定制。
ios::scientific设置浮点数以科学计数法表示
ios::left左对齐,仅仅setw ,并且字符串长度超过有效 。
ios:right右对齐
ios::skipwsskipws是作用于流式输入的,而非输出。
cin默认是已经把skipws开启了。 也就是说 a b c 读入三个字符,不会吧空格读进去。
ios::uppercase只有16进制输出才以大写输出
ios::lowercase小写输出
ios::showpos输出正数给出“+”号
ios::internal数值的符号位在域宽左对齐,数值右对齐,中间填充字符
ios:unitbuf每次输出后刷新所有的流
ios::stdio每次输出后清除stdout,stderr
流成员函数

如下方法属于cout成员,使用cout.method进行调用即可。

函数功能
precision(n)设置精度
width(n)设置字符宽度
fill©设置填充字符
setf()设置ios标志
unsetf()取消设置ios标志

文件流

引入头文件

#include <fstream>

文件的输入输出方式控制
在这里插入图片描述

STL

常见的容器有如下的几种
vector list deque set map stack queue

string

如何写一个标准的替换字符串函数

string str="jflkasfijfamsfkajsaf";
string find="j";
string rep="*";
int pos=0;
while((pos=str.find(find,pos))!=-1){
    str.replace(pos,find.length(),rep);
    pos+=rep.length();
}

vector

deque

与vector 不同在于,deque 可以push_front,即双端操作。pop_front()可以取除第一个元素

stack

支持push 与pop ,通过top 获取元素

queue

队列,pop时候,只会把队头元素抛出。支持front 与end

list

不可随机存储读写,但是支持插入?支持push pop back front

总结一句话,能够用下标获取,其底层机构一定是数组,如果无法,但是可以通过不断的++ ,那他一定是一条链路。
各个容器的使用正确时机。

简单的总结

在这里插入图片描述
 deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头 端的快速移除,尾端的快速添加。如果采用 vector,则头端移除时,会移动大量的 数据,速度慢。

  • vector 与 deque 的比较:
  • 一:vector.at()比 deque.at()效率高,比如 vector.at(0)是固定的,deque 的开始位置却
    是不固定的。
  • 二:如果有大量释放操作的话,vector 花的时间更少,这跟二者的内部实现有关。
  • 三:deque 支持头部的快速插入与快速移除,这是 deque 的优点。
  • list 的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实
    位置元素的移除插入。
  • set 的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的
    顺序排列。
  • map 的使用场景:比如按 ID 号存储十万个用户,想要快速要通过 ID 查找对应的用
    户。二叉树的查找效率,这时就体现出来了。如果是 vector 容器,最坏的情况下可 能要遍历完整个容器才能找到该用户。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值