C++学习之路
C++内容概述:
day01 入门
part 1
用C语言+面向对象思维实现面向对象 面向对象的思维方式
1、定义结构体{成员变量;函数指针}
2、定义全局函数,函数参数必须带一个参数,为结构体类型的指针;
3、产生一个结构体,并对结构体赋值:成员变量赋值、函数指针赋值为全局函数;
4、使用结构体变量,注意函数指针成员的使用还必须指定参数;例如:t.run(&t);
给结构体部分成员赋值:用点的方式来赋值
struct Time t = {.show=show, .tick=tick , .run=run};
part 2
主要内容:面向过OP+面向对象(OOP)+泛型(<T>)+标准模板库(STL)
1、创建类class A {};自定义数据类型
2、创建对象,A a;由类可以产生无数对象
3、使用对象(利用对象去做事情,a.doSomething())
show(a);OP
a.show();OOP
面向过程OP |
面向对象OOP |
函数层次复用 |
类层次复用 |
struct Screen {}; move(struct Screen* p); |
class Screen {}; screen s; s.move(); |
按步骤设计 |
按功能设计 |
C++语言主要内容:
1、构造函数:
功能:创建对象的标志;初始化成员变量;
特点:(1)名字和类名相同Time::Time();(2)没有返回值;(3)创建对象的时候自动调用;
理解:(1)自定义了构造函数,不论是否带参数,则系统不再分配默认构造函数;
例子:
void func()
{
A a1; // 正确
A a2(); // 错误,函数申明,不会创建对象;
A a3(10);
A a4 = 10;
A a5 = A(5); // 创建无名对象赋值给a5,一共创建了2个对象
A(); // 无名对象
A* p=new A(4); // 创建堆对象
A* p = new A [4] // 创建对象数组
delete [ ] p;
}
2、析构函数:
功能:用来释放资源
特点:(1)Time::~Time();(2)没有返回值,没有参数;(2)对象生命期结束时自 动调用
3、初始化列表:
语法:在构造函数的申明和函数体之间,写法,A(int i):i成员(形参){}
必须用初始化列表初始化的:
(1)常量;
(2)引用,从一而终,必须初始化,且只能初始化一次,对引用的赋值就是对引用从事的变量的赋值;
(3)
int i; int j;
int &b=i; // b为i的别名
b = j // 将j的值复制给b,也就是赋值给i(b是从一而终的)
哲学原理:具体问题具体分析,特殊问题特殊对待
(4)成员对象;
(5)父类的成员;
4、this指针
指向当前对象的指针,每个成员函数都有一个隐藏的this指针,在调用成员函数的时候自动将对象的地址给this指针;
5、常量对象,只能访问常函数(注意const在名字和大括号之间)
const A a;
const A::show()const {};
6、A a4=4; A a5=A(5); A a6=(A)6 三种方式是等同的,等同于A(5);
强制类型转换 == 拷贝构造函数
例如:double i=5 double i=double(5) double i=(double)5 double i(5)
day02 进阶
1、拷贝构造函数(盲点)
功能:复制对象
特点:
(1)用户不定义拷贝构造函数,则有一个系统分配的copy构造函数,逐字节拷贝;
(2)用户一旦自己定义了copy构造函数,系统不再自动分配copy构造函数
定义:必须为B(const B& b),这样实参形参传递才不会重复拷贝,从而陷入死循环;
编译器将B(B b)这种陷入循环拷贝的形式禁止了;利用了引用不拷贝对象的 特性;
拷贝构造的调用形式: B b(a) = B b = a;第二种由编译器优化第一种
class A{};
void fn(A aa,A ab){// A aa=a1; A ab=a2;}
void fn2(const A& aa, const A& ab){}
void gn(){
A a; // 如果为static,或者全局,返回要调用拷贝构造
return a; // inline优化,没有调用拷贝构造
}
A a1;
A a2=a1; // copy
A a2(a1); // copy
fn(a1,a2); // copy
fn2(a1,a2);
A a4 = gn(); 只拷贝一次,inline优化,返回时没有调用拷贝构造
如果 gn内部的a为static或者全局的,则gn返回时候要拷贝
深拷贝、浅拷贝,为什么要自定义拷贝构造函数?
(1)拷贝构造函数默认逐字节拷贝:如果一个类中有指针变量,则会出现浅显拷贝;内存释放的时候会出现double free错误;
(2)深拷贝,自定义拷贝构造函数,实现拷贝后每个对象有自己的堆空间,即自己的指针指向自己的堆空间;
法则:一旦类中有指针,必须自定义拷贝函数,实施深拷贝;
2、返回引用:(特殊问题特殊对待)
int& func(void) ,不能返回局部变量的引用,因为引用的实体已经消失了。
错误:
技巧点:C++规定,对于临时对象如果有一个引用,那么这个临时对象的生命期会延长。
但引用需要申明为常引用;
3、静态成员、静态成员函数(局限在类中的全局变量、全局函数)
语法:静态成员在编译时分配空间并初始化,写程序时在类外写初始化语句,格式为下,
class A{static string a; static void fn();} // 内部声明,外部定义
string B::a = “liurong”;
理解:
(1)相当于是一个全局变量,只是这个全局变量有了类的限定而已,只在类内部是全局的;
(2)按照上面理解,必须在外部定义静态变量,并初始化,类内部只是申明了一下而已
(3)静态函数中不能访问非静态成员,因为对象可能还没有创建;
4、继承
派生类的构建:构建子类之前要先构建父类,可以在初始化表中选择性调用父类构造函数;
派生类的构造函数:初始化表中调用父类构造函数,如果不显式调用,则使用父类无参数
因此一般在父类中一般都要定义一个无参构造函数。
同名覆盖(overwrite):当父类行为无法满足子类需要时需要重写
(1)子类中中有一个和父类同名的函数show,则子类调用show是调用自己的show
即所谓的overwrite
(2)子类同名函数中,要调用父类的同名函数,必须用域作用符:Father::show(),这样show函数就青出于蓝了:
保护继承:protected为继承而生,本类中protected和private相同,子类可以访问父类的protected成员(protected成员:本类中和子类中可以访问)
初始化列表:
A:必须放在初始化列表初始化:
常变量、引用、成员对象带参构造函数、父类对象带参构造函数
B:不能放在初始化列表初始化:数组、结构体
5、sizeof运算符
(1)A a;sizeof(A) = sizeof(a),只与类型有关,与内容无关;
(2)一个空类的大小时1个字节,原因是:编译器给定,主要是为了保证空类产生对象;
保证c++中所有对象都是唯一的;
6、单子模式
方法1:静态局部变量
返回静态局部对象指针的静态函数(静态函数,返回静态对象指针,构造函数私有)
最初的一种想法:构造函数的私有属性:创建对象的前提是至少有一个公有的构造函数,构造函数为私有成员,则无法创建对象;但构造函数为私有,无法创建哪怕1个对象,因此此方案不可行;
改进:
(1)构造函数为私有;
(2)写一个static的返回对象指针的函数,在static函数创建一个静态对象;
并返回静态对象的地址
方法2:使用静态成员变量
7、关于初始化列表的深入:
法则:默认情况下,创建一个对象访问别人的无参构造函数,如果没有无参构造函数,就必须主动调用有参构造函数;
8、访问修饰符、继承方式
继承时,public、protected、private逐级降低访问权限:
(1)父类的私有成员子类不可访问,也就是说子类看不见;
(2)public不改变父类public和protected的访问权限;
protected将父类public和protected改为protected;
private将public和protected改为privated;
9、多态
用父类的指针指向子类的实例,可以调用子类的函数!
用父类的引用指向子类的实例,可以调用子类的函数!
注意:用子类的实例构造父类的实例,会出现子类对象的分割,因此不能调用子类的函数!
day04 提高
1、常指针、常对象
常引用可以指向一个常量,也可以指向一个非常量(常的东西更加通用)
理解:
(1)常指针只能指向一个对象,以后不能修改指针的值;
(2)常对象不能修改,只能有指向常对象的指针来指向;
技巧:赋值兼容和强制转换必须坚持一个原则,类型像精确兼容,访问权限不能升高;
2、内部类:成员内部类、局部内部类
优点:
(1)隐藏名字,避免命名冲突;
(2)提供统一的内部类名,统一个各个类的处理方法(迭代器)
(2)将异常类封装为内部类;
语法:
(1)class A{public:class B},使用方法:A::B b;
(2)void f(){class A{};A a;}
理解:内部类可以当成外部类的静态成员类型看待,同时受访问修饰符的限定;
3、protected继承方式下,派生类对象的指针不能直接转换成指向基类对象的指针;
4、友元函数、友元成员:
友元:一个函数或者一个类的成员要访问一个类的成员,必须被一个类授权为友元
友元的申明:(1)友元类:friend func();(2)友元类:friend class B;
注意:友元关系不能继承、不能反转
关于访问的理解:
类外面访问:c.member
类里面访问(不带点的访问):(1)本类里面访问;(2)子类里面访问;
5、运算符重载:
什么时候:当默认运算符不能满足我们的运算要求时候;
重载本质:编写新的运算符重载函数;
(1)重载单目运算符:
(2)重载双目运算符;
(3)强制类型转换运算符重载,operator int(){return x;}
6、c++工具,异常处理:try语句块,catch过滤器处理,throw抛出异常
(1)将可能出现异常的语句放在try语句块中,并在可能语句出抛出异常throw
(2)在try语句块之后设置过滤器catch,并在catch中设置各种类型的参数来捕获各种可 能的异常,并处理,例如:catch(int & e)。
(3)注意:catch(。。。)可以捕获各种可能的异常。
(4)异常不仅可以在产生异常的当前函数中捕获,一个函数中产生的异常可以在调用者来捕获并处理;(异常可以在外层函数中捕获,并且经常这么做)
可以讲try语句块中的内容写成一个函数,try{fn();},同样可以捕获异常
7、多重继承和虚拟继承(multi_virtual)
虚拟继承:继承时添加virtual关键字,将公共基类申明伟虚拟基类
作用:A的直接派生类采用虚拟继承方式,A的间接派生类D只保留公共基类的一份拷贝
day05 深入(STL+泛型编程)
1、IO流
标准IO(控制台IO,console IO)
cin,cout,cerr,clog
注意:cerr和clog不支持重定向,只能输出到屏幕
(1)istream:的成员函数
>>:
get():获取一个字符,例如:char=cin.get() 或者 cin.get(ch)
getline():获取一行字符,遇到指定的终止字符时提前结束;
获得一个字符串,例如:cin.getline(buf,sizeof(buf),‘\n’),用buf去获 得,用‘\n’结尾buf缓冲区,实际获取sizeof(buf)-1个字符;
当输入内容超过buffersize-1个,cin流出错,需要调用cin。clear()清除;
注意:全局的getline(iostream&,string& s,分隔符)
clear():清除错误标志,当输入的字符数目大于接收缓冲区大小,cin出错,此时cin 需要clear来清除错误才能重新恢复正常工作,例如:cin.clear()
read():
ignore():忽略n个字符,遇到指定的终止字符时候
清除缓冲区,例如:cin.ignore(len,‘\n’),忽略100或者‘\n’之前的字符
peek():查看数据,不取走,查看输入缓冲区的第一个字符,例如根据字母或者数字 来区分年龄和名字
putback():将数据放回到缓冲区
(2)ifstream:ifstream fin(“filename”);char ch = fin.get();cout<<ch;fin.close();
fin 。seekg(),fin。seekp();
(3)ofstrream:ios::binary(window下二进制写);ios::app(接着写)
(4)ostrream:
文件操作的步骤:
① 建立文件流对象;
② 将文件流对象和对应的文件建立对应关系;
③ 指定文件工作方式:输入文件还是输出文件(ios::out ios::in);文本文件还是
二进制文件ascii文件还是data文件;
对于1、2步,可以一次完成(调用文件流类的带参构造函数);
对于1、2步,可以二次完成(建立文件流对象、调用open函数);
operator<<:向屏幕输出
put(): 输出单个字符,返回ostream的引用
fill(): cout。fill(‘#’)设置填充为# 永久操作
write(): cout。write(buffer,len)
width(): cout。width(10),设置宽度为10,一次性操作
setf(): ios:: left、right、dec、oct、hex、showbase、showpoint、uppercase、showpos、 ios:: scientific
输出控制符:<iomanip>
定点数就是以小数形式输出小树,输出的就是定点数;浮点数就是以科学技术法输出,输出的就是浮点数,setiosflags(ios::fixed)和setiosflags(ios::scientific)只能用一个,同时用后面用的无效;
iomaip的控制符都是setxxxx的形式,dec、oct、hex,endl,setiosflags,resetiosflags
流对象成员函数则是以名词形式:cout。xxxx,setf,unsetf
理解:
控制符应该是一些预定义的对象,在用重载的<<输出的时候,这个预定义的对象利用cout调用cout流成员对cout的属性进行了一些修改,本质上是一样,就是封装和重载;
char a[10]; cin>>a;其实完成了2件事,1是从键盘获取输入到流,2是部分流提取到a
(4)持久化:ofstream fout(“xxx”);fout。write(char*, size);
2、模板(函数模板、类模板)template <typename T>
技巧点:返回引用的函数调用可以充当左值;一般的函数调用不能充当左值;
STL(standard template library)
1 概述:C++刚开始没有STL,后来由HP公司开发并作为编译器的一部分作为C++标准
组成部分:
(1)模板类(容器):
(2)模板函数(通用算法):
(3)迭代器,iterator,智能指针:容器和通用算法之间的桥梁,将二者结合到一起;
迭代器的本质就是指针,可以模拟指针的功能;
概念的理解:
(1)容器+通用算法+迭代器 构成的一整套体系 = STL标准模板库
(2)所有通用算法都是针对iterator的;
(3)区间:用两个迭代器表示的一段范围叫做区间,[begin,end),end指向的不是区间的 一部分;
(4)迭代器的实现原理:内部类+指针,所有STL容器都有一个内部iterator类;
2 容器的分类:
(1)序列式容器(顺序型容器):基于链表实现
vector(向量,动态数组):
list(双向链表):
deque(双端队列):
(2)适配器:stack栈、queue队列、priority queue优先队列
(3)关联式容器():基于二叉查找树,查找速度非常快
set
multiset:
map:
multimap:
3 通用算法
(1)foreach遍历算法;
(2)find(vector::iterator begin,vector::iterator end,T t)区间内查找算法;
4、实例
vector的构建:
(1)指定大小构建;vector<double> v1;
(2)使用一个vector的子区间构建;vector<double> v2(5);// 指定容量为5个元素
(3)拷贝构建;vector<double> v3(v1)
(4)迭代器构建:用v1的子区间(前两个元素)构建
vector的大小:sizeof(vector)=12,因为2个iterator+1个容量=12Bytes
vector添加元素:push_back();
vector遍历:
(1)迭代器遍历:
(2)下标遍历:
(3)下标遍历(2):
用at(下标)可以捕获异常,用
try{}
catch(exception& e) cout<<e.what()<<endl;
vector 插入:
insert()
vector删除:
erase()
5、RTTI 运行时类型识别
T t;cout<<typeid().name()<<endl;
Qt C++编程调试记录
1、
给非常成员函数传递了一个常对象this指针,丢弃了const修饰符后传递给this
说明:
(1)非常成员函数的this指针必须是指向一个非常对象
(也就是:非常对象才可以调用非常成员函数,因为非常函数可能试图修改对象)
(2)常成员函数的this指针可以指向非常对象也可以指向常对象,因此更加通用
2、关于对齐:min(4,max(sizeof(member)))
转载于:https://my.oschina.net/u/1772925/blog/375259