C++基础
类中构造函数调用的顺序
1. 调用基类构造函数
2. 对象成员的构造函数
3. 派生类的构造函数
注:基类的构造函数与声明顺序有关,与初始化列表顺序无关
类赋值规则
派生类的对象继承了基类所有的特性,因此派生类对象可以直接给基类对象赋值。
Base b;
Deived d;
b=d;
Base *b;
Deived d;
b=&d;//指向派生类的基类指针
C++的多态性
- 编译时的多态性 (重载,函数与运算符)
- 运行时的多态性(虚函数)
虚函数的使用
- 到运行时才确定所要调用的函数
- 在基类中关键字virtual必须写,在派生类中可写可不写
- 通过指向派生类的基类指针实现
- 构造函数不能是虚函数,析构函数可以是虚函数
虚析构函数
在主函数中指向派生类的基类指针new一个派生类对象的时候,delete直接调用基类的析构函数,不会调用派生类的构造函数。
将基类析构函数声明为虚析构函数即可解决。(基类析构函数为虚函数,派生类函数为析构函数)
纯虚函数
纯虚函数不具备函数的功能,不能被调用,仅用来继承。
形式:virtual void show()=0;
抽象类
定义:包含了纯虚函数的类。
1.只能作为基类使用,不能建立对象
2.一般用来声明指向派生类的指针和引用
.h文件与.cpp关系
如果这两个文件没有inlcude,那这个文件就没有任何关系,包含也没有用
switch用法
- 如果case之后不加break;程序就会一直向后执行到default;
- case成立说明程序从那个case开始执行,但结束则根据break;
C++中string类的用法
数字变字符串
#include <sstream>
#include <string>
int i=100;
stringstream ss;
ss << i;
string result=ss.str();
字符串变数字
string s
int num;
stringstream ss(s);
ss>>num;
注:
1.<<,>>都是对数字进行的操作。
2.利用stringstream作为中介。
"?:"用法
前面是判断条件,如果成立则返回后面第一个数,如果不成立就返回后面第二个数
例如:(a=)(2>1)?(3):(4)
C++11 中for的用法
for(auto p:num) 可以自动遍历数组,vector等所有元素。
vector<>最大值与最小值极其索引
std::vector<double> v{1.0, 2.0, 3.0,4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0};
std::vector<double>::iterator biggest= std::max_element(std::begin(v), std::end(v));
std::cout << "Max element is" << *biggest<< " at position " <<std::distance(std::begin(v), biggest) << std::endl;
auto smallest =std::min_element(std::begin(v), std::end(v));
std::cout << "min element is" << *smallest<< " at position " <<std::distance(std::begin(v), smallest) << std::endl;
注:max_element()/min_element()返回最大/小值的迭代器;
distance()返回两个元素地址(迭代器)之间的差;
命名空间的作用
#include <iostream>
namespace aaa
{
void display();
}
namespace bbb
{
void display();
}
void aaa::display()
{
std::cout<<"Hello"<<std::endl;
}
void bbb::display()
{
std::cout<<"sorry"<<std::endl;
}
int main()
{
aaa::display();
bbb::display();//不同的空间下虽然名字相同调用的却不是同一个函数
return 0;
}
aaa空间里的display()函数可以和bbb空间的display()函数互不干扰,而std空间里的函数是C++自己库函数的命名空间,专业的术语就是指标识符的各种可见范围,
由于人类的单词有限,现在的大型程序开发,尤其是各种库之间,不可能没有重名的,而且大型程序不可能一个人完成,难免会有名字重复的变量或函数,这时就需要命名空间来区分。
输出当前的cpp文件中的行号
__LINE__: 在源代码中插入当前源代码行号;
__FILE__: 在源文件中插入当前源文件名;
__DATE__: 在源文件中插入当前的编译日期
__TIME__: 在源文件中插入当前编译时间;
命名空间namespace
- 如果程序中没有使用关键字namespace,则程序被认为位于一个无名的命名空间中。(大部分程序示例就是这样)
- 命名空间的声明可以是不连续的(也可以在不同文件中),命名空间的实际内容为所有声明的总和。
使用方法:
1. 命名空间名称::成员名称
例如:NS::PI
2. 使用指令usingnamespace xx;声明,声明之后直接使用(不需要"name::")
C++中迭代器与指针
1. 迭代器也可以理解为指针,主要有自增,吸取,相等,不等这四种运算,比指针的功能更强大一些。
2. 指针是C语言里面就有的东西,而迭代器是C++里面才有的,指针用起来灵活,效率高。迭代器功能更丰富一些(不见得是强大一些),c++的stl里面很多算法都是基于迭代器的,一部分算法的参数可以传递指针作为迭代器使用。
C++运算符重载
运算符重载是创建运算符重载函数来实现的,运算符重载函数定义了重载的运算符将要进行的操作。
例:
A operator+(A o1,Ao2)
{
...............
return temp;
}
调用的两种方法:
1.o3=operator+(o1,o2);//即完全当成普通函数使用,函数名为"operator+"
2. o3=o1+o2;
注:实际上编译系统将方法2中的程序解释为方法1的形式。
typedef与#define区别
1) #define是预处理指令,在编译预处理时进行简单的替换,不作正确性检查,不关含义是否正确照样带入,只有在编译已被展开的源程序时才会发现可能的错误并报错。
例如:
#define PI 3.1415926
程序中的:area=PI*r*r 会替换为3.1415926*r*r
如果你把#define语句中的数字9 写成字母g 预处理也照样带入。
2)typedef是在编译时处理的。它在自己的作用域内给一个已经存在的类型一个别名,但是You cannot use the typedef specifier inside a function definition。
循环体内工作量最小化
应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而提高程序的时间效率。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
sum += ind;
back_sum = sum; /* backup sum */
}
语句“back_sum = sum;”完全可以放在for语句之后,如下。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
sum += ind;
}
back_sum = sum; /* backup sum */
在多重循环中,应将最忙的循环放在最内层
减少 CPU 切入循环层的次数。
示例:如下代码效率不高。
for (row = 0; row < 100; row++)
{
for (col = 0; col < 5; col++)
{
sum += a[row][col];
}
}
可以改为如下方式,以提高效率。
for (col = 0; col < 5; col++)
{
for (row = 0; row < 100; row++)
{
sum += a[row][col];
}
}
用乘法或其它方法代替除法
浮点运算除法要占用较多CPU资源。
示例:如下表达式运算可能要占较多CPU资源。
#define PAI 3.1416
radius = circle_length / (2 * PAI);
应如下把浮点除法改为浮点乘法。
#define PAI_RECIPROCAL (1 / 3.1416 ) // 编译器编译时,将生成具体浮点数
radius = circle_length * PAI_RECIPROCAL / 2;
vector容器
List容器
-在任何位置插入/删除时间都是常数。
-不支持随机访问
-只支持双向迭代器,不能使用比较,移动运算符
deque容器
双向队列,可以理解为vector的扩充,deque是在功能上合并了vector和list。
优点:
(1) 随机访问方便,即支持[ ]操作符和vector.at()
(2) 在内部方便的进行插入和删除操作
(3) 可在两端进行push、pop
缺点:
(1) 占用内存多
三个容器使用区别:
1 如果你需要高效的随即存取,而不在乎插入和删除的效率,使用vector
2 如果你需要大量的插入和删除,而不关心随即存取,则应使用list
3 如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque
普通static函数
在函数的返回类型前加上关键字static,函数就被定义成为静态函数。
函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。
好处:
<1> 其他文件中可以定义相同名字的函数,不会发生冲突
<2> 静态函数不能被其他文件所用。
容器适配器
Stack:
-后进先出(可以用vector,list,deque实现)
-只能插入、删除、访问栈顶元素
-三个主要成员函数
n void push()
n void pop()
n T &top()//返回栈顶元素的引用
Queue:
-先进先出
-list,deque实现(默认使用dequeue)
-push发生在队尾;pop(),top()发生在队头
priority_queue:
-vector与deque(默认情况使用vector实现)
-通过堆排序,保证最大元素总在最前面
-pop()删除最大的元素;top返回最大元素的引用
C++11特性
1.统一的初始化方法:
例如:
vector<int>i{1,2,3};
string str{“dadasd”};
2.成员变量可以有默认的初始值: (从JAVA里面学习的)
class B
{
public:
int m=3;
}
3.auto关键字
注意:必须初始化,编译器自动推倒类型
4.decltype关键字:
作用:反推类型
int i;
decltype(i) x;//此时x就是整形变量
5.智能指针(shared_ptr):
- 头文件<memory>
- 是一个类模板
- 不能指向动态分配内存的数组
当没有指针指向new出来的对象的时候,就自动delete掉
例如:
对比1:
shared_ptr<A>sp1(new A(2));
shared_ptr<A>sp2(sp1);//此时由sp1与sp2才能同时托管
对比2:
A *p=new A();
shared_ptr<A>sp1(p);
shared_ptr<A>sp2(p);
//不会同时托管,或者说不会增加托管计数。
tips:new运算符在内存中开辟一块空间的同时,会返回一个地址
6.空指针nullptr:
可以自动转换成bool类型
bool x=nullptr;//x=false
7.基于范围的for循环
例如:
int arry[]={1,2,3,4};
for(int e:arry) …;
for(int & e:arry) …;
vector<A>st(5);
for(auto& it:st);
7.右值引用和move语义
右值:不能取地址的表达式
左值:能取地址的
A& r=A();//错误
A&& r=A();//不需要深拷贝
move()//把左边值变为右值
8.无序容器(哈希表)
#include<unorderd_map>//必须包含此头文件
unorderd_map<string,int>tu;
tu.insert(make_pair(“Scot”,1972));
注:插入和查询的时间复杂度是常数。
9.正则表达式
#include<regex>//必须包含这个
刷新输出缓冲区
cout<<”HI”<<endl;//输出换行符,刷新缓冲区
cout<<”HI”<<ends;//输出空格,刷新缓冲区
cout<<”HI”<<flush;//刷新缓冲区(不添加任何字符)
获取当前的时间
使用示例:
time_t timer;
struct tm *lt;
lt = localtime(&timer);
cout<<"当前时间:"<<lt->tm_hour << "时 " << lt->tm_min << "分 "<< lt->tm_sec << "秒"<<lt->tm_year+1900<<"年"<<lt->tm_mon+1<<"月 "<<lt->tm_mday<<"日";
&与*的多重含义
上面两个符号需要结合上下文理解
- 在变量的声明中
&:表示声明引用
*:表示指针变量
- 在函数中
&:表示取其地址
*:表示取地址对象
const 类型在其他cpp文件中引用
声明时:extern const int a=3;
在其他cpp文件中引用时: extern const int a;
指针与引用的区别
指针是一个独立的对象,存放的某一个对象的地址。
引用相当于取别名,并没有生成新的对象,是原对象的另一个标签。
typedef、using、#define三者区别
typedef与using都只是在局部作用域
#define是预编译命令,整个cpp文件
使用区别:
#define 自定义名 原始名字
using 自定义名 原始名字
typedef 原始名字 自定义名字 //就你最特殊
typedef使用方法:把原来为变量名的地方替换为自定义的类型名
typedef unsigned int * a;--->typedefunsigned int * UN_p;
const与类型别名混合使用的区别
typedef char* pchar;
const pchar p;//不可以直接将别名原型直接带入
上句指的是指向char类型的常量指针(指针内容(地址)不可以变,地址存放的内容可以变)
区别于:
const char *p;
上句指的是指向const char(常量字符)的指针,(指针地址可以变,但不能通过访问地址改变其所指向的内容)
#if等预编译宏指令
判断内容只能用#define宏定义,不能使用int a=1;,因为这是预编译,在编译过程中就结束了。
注:预处理变量无视c++中关于作用域的规则
定义与声明的区别
声明是告诉编译器存在这么一个标识符。
定义则是为程序申请一块内存。
int x; //这是一个定义
extern int x = 10; //这也是一个定义
extern int x; //这是声明(用extern且不赋值才是声明)
因为定义只能一次,而声明可以有很多次,不能把定义放在.h文件中,多次包含会出问题。
以上说的是不能在头文件中定义变量。但是有三个例外。头文件可以定义类、值在编译时就知道的const对象和inline函数。
编译流程
预处理:预处理相当于根据预处理命令组装成新的C程序,不过常以i为扩展名。
编译:将得到的i文件翻译成汇编代码.s文件。
汇编:将汇编文件翻译成机器指令,并打包成可重定位目标程序的o文件。该文件是二进制文件,字节编码是机器指令。
链接:将引用的其他O文件并入到我们程序所在的o文件中,处理得到最终的可执行文件。
c(源文件) → I(经过预处理) → S(经过编译得到汇编代码) → o(经过汇编得到机器指令) → e(经过链接后得到可执行程序)
string对象
是标准库的一部分,在头文件#include<string>中
包含很多成员函数:判断大小写,判断字符,空格,拼接字符串
迭代器
指向某种容器元素的指针(本质上是类的静态成员)
vector<int>::iterator it;
string::iterator it2;
注意:
1.所有的标准库容器都支持迭代器
2.使用迭代器访问类函数(*it).empty();,如果不加这个,就变成(*(it.empty()))
3.循环体中使用迭代器,就不要向容器中添加元素。
4.v.end()//尾后迭代器,指向的一个容器根本就不存在的尾后
5.如果容器为空,begin和end返回的都是尾后迭代器。
数组默认的索引
不一定是无符号型的整数
int a[10];
int *p=&a[3];
p[-1];//正确,可以正常使用,可以理解程*(p-1);
循环内部定义的变量
定义在while循环条件部分与循环体内部的变量每次迭代都经历从创建到销毁的过程。
异常处理
try{ //在此1.运行代码并2.抛出错误}
catch{//在此3.处理错误}
例如:
int fun1(int a)
{
if(a==0) throw 1;//可以抛出各种数据类型
}
try{
fun1(0);
}
catch(int i)
{
if(i==1)cout<<”输入的是0”;
}
函数声明的头文件
函数声明的头文件应在
1.函数定义的cpp中
2.使用函数的cpp中
参数传递
引用传递:引用形参是实参的别名
值传递:实参的值拷贝给形参,形参和实参是两个独立的对象。
访问(修改)函数外部对象
C程序员用指针的值传递;
void fun1(int *p){}
voidm main(){int a=1;fun1(&a); }
C++中推荐用法(引用类型的形参代替指针)
void fun1(int &a){}
voidm main(){int a=1;fun1(a); }
数组形参调用
void print(const int *p);
void print(const int p[]);
void print(const int p[10]);//上面三者等价,本质上就是一个指针
数组的引用
void print( int (&a)[10]);//括号一定要加
main函数的参数传递
当使用argv时,实参从argv[1]开始,argv[0]保存程序名,而非用户输入。
C++中强制转换
static_cast<目标类型>
例如:
const char *p;static_cast<string>(p);//char改成string
数组指针与指针数组的区别
int *ip[4];//指针数组
int (*ip)[4];//指向四个元素的指针
sizeof()
后面可以加类型,也可以加对象
函数返回值
当返回为值的时候,函数会返回一个临时构建的对象。
当返回为引用的时候,函数不会生成对象。
列表初始化返回值
例如:
vector<Point> fun1()
{
Point p1,p2,p3;
return{p1,p2,p3};
}
assert()调试函数
assert()宏是用于保证满足某个特定条件,用法是:assert(表达式); 如果表达式的值为假,整个程序将退出,并输出一条错误信息。如果表达式的值为真则继续执行后面的语句。
注:这个仅仅在debug模式下可以使用
函数指针及其使用
函数指针的声明:
bool (*pf)(int a,int b);//注意括号必须加,否则变为函数的声明了。
使用:
int fun1(int,int);
pf=&fun1;与pf=fun1;//这两者是等价的
pf(1,2);与(*pf)(1,2);//这两者等价
函数指针作为返回值
using pf=int (*)(int ,int);
using f=int (int ,int );//f代表函数
pf f1(int);
f* f1(int);
比较复杂点的用法:
1.int (* f1(int))(int,int);
上面遵循由内向外的顺序
f1有形参列表,所以f1是一个函数
f1前面有*,说明返回的是一个指针
最外面说明函数返回的是一个指向xx函数的函数指针
2.auto f1(int)->int(*)(int,int);
尾置返回类型(便于理解)
3.decltype(fun)* f1(int);
注意,decltype()返回的是函数的类型。要显示加指针
类中成员函数的调用过程
class A{};
A a;
a.initial();
A::initial(&a);//伪代码,可以等价认为。
成员函数执行的时候,隐式传入指向当前对象的this指针。
const成员函数
在成员函数的形参列表后面加const表示不能修改对象的数据成员(准备说是非静态成员)
普通对象,可以调用非const成员函数与const成员函数
const对象,只能调用const成员函数。
返回值:如果const函数返回引用,返回的是一个常量引用。
struct与class定义类的区别
默认的访问权限不一样
友元函数
在类声明中加friend,可以是普通函数,也可以是其他类的成员函数。
构造函数初始化必不可少
类中的常量对象,引用必须在构造函数中初始化(而且必须通过构造函数列表)
聚合类
条件:
1.所有成员都是public
2.没有构造函数(全部由用户初始化)
3.没有类内的初始值
4.没有基类,也没有virtual函数
例:
struct A{int a;int b}; A a={1,2};
隐式类的类型转换
转换构造函数:构造函数值接受一个实参(准确说应该是其他类的实参)
带有explicit 的成员函数
不会进行隐式的转换
例如:
class A {A(int n);};
int a=3;char b=3;
A(a)与A(b)都能正常