C++知识点
1. 符号.和->的区别
A.B,则A为对象或者结构体,点号(.)左边必须为对象实体
A->B,则A为指针,->为成员提取,A->B为提取A中的成员B,A只能指向类,结构体,箭头(->)左边必须为指针
2.const
const对象声明必须进行初始化,且不可被修改
默认情况下,const对象仅在文件内有效
多文件下
//file1.cpp,定义并初始化一个常量,能被其他文件访问
extern const int a=1;
//file1.h,与a是同一个常量
ertern const int a;
对常量的引用可能引用一个非const对象
int i=1;
int &r1=i;
const int &r2=i;//对常量的引用,不能通过r2修改i
r1=0;//编译通过,可以通过r1修改i
r2=0;//编译错误
指向常量的指针
const int p=3;
int *ptr=&p;//编译错误,指针类型必须与所指对象类型一致,const int!=int
const *ptr=&p;//编译通过
常量指针,将指针本身定为常量
int r=0;
int *const c=&r;
//c将一直指向r
顶层const
指针常量:代表指针本身是常量,声明时必须初始化,之后它存储的地址值就不能再改变。声明时const必须放在指针符号后面,即:const 。声明常量指针就是顶层const,
底层const
指向常量的指针:代表 不能改变其指向内容的指针。声明时const可以放在类型名前后都可,拿int类型来说,声明时:const int和int const 是等价的。声明指向常量的指针也就是 底层const
一个指针本身添加const限定符就是顶层const,而指针所指的对象添加const限定符就是底层const
3.constexpr 常量表达式
常量表达式 值不会改变且自编译过程就会得到结果的表达式
const int s=10;
const int s2=s*10;//常量表达式
const int s3=get_size();//非常量表达式,他的变量类型为int
constexpr int s=10;
constexpr int s1=s+1;
constexpr int s2=get_size();//get_size()是一个constexpr函数时为常量表达式,在编译时将结果进行编译
尽管指针引用共可以定义为constexpr,但初始化的值必须为nullptr或0
constexpr定义的对象为顶层const
const int *p=nullptr;//指向整数常量的指针
constexpr int *p=nullptr;//一个指向整数的常量指针
4.类型别名
typedef wage double ;//double wage同义
typedef wage base,*p;//wage base同义。p,double*同义
C++11
using wage=double;
5.auto
auto(C++11)自动推导类型
int i=0,*p=&i;//编译通过,i:int,p:int *
auto i=0,p=3.0;//编译失败,i,p类型不一致
6.decltype
decltype选择并返回操作数的数据类型
decltype(f()) sum=x;
sum的类型为函数的返回类型
如表表达式是一个变量则返回该变量类型
int i=0,*p=&i,&r=i;
decltype(i+1) b;//b为未初始化的int,正确
decltype(*p) c;//c是引用类型,必须初始化,错误
decltype((i)) d;//参数加了(),则为赋值语句左值的特殊表达式,为引用l
7.string
string可变长的字符串序列
string s1;
string s2=s1;
string s3="a";//拷贝初始化
string s4(5,'a');//"aaaaa"//直接初始化
string s5("aaaaa")//直接初始化
操作
os<<a;//写入输出流os,返回os
is>>a;//从is中读出字符串,字符串用空白分割,返回is
getline(cin,s);//从cin中读取一行给s,返回is
s.empty();//空返回true
s.size();//返回字符个数,返回类型:size_type,无符号类型的值,能存下所有的字符串长度
s[n];//返回第n个字符的引用
s1+s2;//返回拼接后的结果
<,<=,>,>= //判断字典序
s1==s2;//长度和字符全部相等成立
s1="hello";
s2=s1+"world"//正确,
s2="hello"+"world"//错误,+支持字符串相加(至少有一个),不支持两个字面量相加
cctype头文件中的函数(处理每个字符)
isalnum(c)//是数字/字母时为真
isalpha(c)//字母
iscntrl(c)//控制字符
isdigit(c)//数字
isgraph(c)//不是空格
islower(c)//小写字母
isprint(c)//可打印字符
ispunct(c)//标点
isspace(c)//空白
issupper(c)//大写字母
isxdigit(c)//16进制数字
tolower(c)//转小写
toupper(c)//转大写
遍历
for(auto i:s){
//i为字符的拷贝,操作不影响原字符串
}
for(auto &i:s){
//i为字符的引用,操作影响原字符串
}
字符串截取
//pos必选参数,开始位置
//n可选参数,取n个字符,没有表示截取到末尾
string substr(int pos=0,n) const;
8.仿函数
仿函数(functor),是通过重载()运算符模拟函数形为的类
- 仿函数是一种类
- 必须重载()
template<typename T> struct comp
{
bool operator()(T in1, T in2) const
{
return (in1>in2);
}
};
//第一种方式
//先初始化一个对象,再调用该对象的方法
comp<int> cp;
cout << cp(6, 3) << endl; //使用对象调用
//第二种方式
//仿函数,会产生一个临时的无名对象,并调用其方法
cout << comp<int>()(1, 2) << endl; //使用仿函数实现
使用仿函数
class comp
{
comp(int a)
{
sec = a;
}
int sec;
bool operator()(int num)
{
return (num < sec);
}
};
vector<int> v{1,2,3,4,5,6,7,8,9};
count_if(v_a.begin(), v_a.end(), comp<int>(5))
bool comp(int a,int b){
return a<b;
}
vector<int> v{1,2,3,4,5,6,7,8,9};
//这里编译错误,只能传入方法,不能传参
count_if(v_a.begin(), v_a.end(), comp(5))
9.右值引用
C++中值可以分为两种,左值和右值,左值指表达式结束后依然存在的持久化对象,右值指表达式结束后便释放。如果可以对表达式取地址便是左值,否则便是右值。
右值:
- 将亡值 非引用返回函数产生的临时变量,运算表达式
- 纯右值 字面量值
int i=0;//i左值,0右值
//函数返回值为右值,接收函数返回值的为左值
左值引用(别名,&)
int i=0;
int &b=i;//左值引用
int &c=1;//编译失败,1不能取地址,不能引用该右值
右值引用(&&)
int &&a=1;//给匿名变量取别名
int b=1;
int &&c=b;//编译失败,右值引用不能引用左值
class Tmp{
public:
int a;
}
Tmp get_a(){
return tmp();
}
Tmp &&t=get_a();//get_a返回临时变量,所以此时为右值引用
get_a执行结束后,临时变量应该会被销毁,但是通过右值引用,可以让它继续存在,实际上指给临时变量取名。
左值引用只能绑定左值
右值引用只能绑定右值
常量左值引用既能绑定左值,又能绑定右值(相当于右值引用,但不能修改)
已命名的右值引用,编译器会认为是个左值
#include <iostream>
using namespace std;
class Copyable {
public:
Copyable(){}
Copyable(const Copyable &o) {
cout << "Copied" << endl;
}
};
Copyable ReturnRvalue() {
return Copyable(); //返回一个临时对象
}
void AcceptVal(Copyable a) {
}
void AcceptRef(const Copyable& a) {//左值引用
}
int main() {
cout << "pass by value: " << endl;
AcceptVal(ReturnRvalue()); // 应该调用两次拷贝构造函数
cout << "pass by reference: " << endl;
AcceptRef(ReturnRvalue()); //应该只调用一次拷贝构造函数
}
实际上编译器中开启了返回值优化,一次拷贝构造函数也没调用,编译时加上-fno-elide-constructors
参数不使用优化
右值引用作用
- 移动语义
vector<MyString> vecStr;
vecStr.reserve(1000); //先分配好1000个空间,不这么做,调用的次数可能远大于1000
for(int i=0;i<1000;i++){
vecStr.push_back(MyString("hello"));
}
这里每次执行push_back都会执行拷贝构造函数,造成资源浪费,每次MyString("hello")
产生的临时变量都会释放,直接使用该临时变量初始化可以节省资源申请释放时间和内存。使用移动语义
可以完成此要求(需要实现移动构造函数和移动赋值构造函数。)
移动构造函数与拷贝构造函数的区别是,拷贝构造的参数是const MyString& str
,是常量左值引用,而移动构造的参数是MyString&& str
,是右值引用,而MyString("hello")
是个临时对象,是个右值,优先进入移动构造函数而不是拷贝构造函数。
移动构造函数的参数是直接将资源拿来使用,并将原来的指针指向nullptr,而不是重新分配空间复制过去
有些左值是函数产生的局部变量,也可以进行移动而不是拷贝,采用std::move
(move
对于拥有如内存、文件句柄等资源的成员的对象有效)将其转换成右值即可,转移了资源控制权
MyString str1("hello"); //调用构造函数
MyString str2("world"); //调用构造函数
MyString str3(str1); //调用拷贝构造函数
MyString str4(std::move(str1)); // 调用移动构造函数、此时str1的资源给了str4,但str1没有立刻析构,当它离开自己作用域才会析构,但str1的内容已经失效了
通用引用(universal references)
右值引用和模板结合,既可以传递左值又可以传递右值,会发生自动类型推断,参数被左值初始化是左值引用,被右值初始化是右值引用。
template<typename T>
void f( T&& param){//这里类型必须是位置的才是通用引用,如果类型确定,则是右值引用
}
f(10);//右值引用
int x=10;
f(x);//左值引用
引用类型叠加会变为左值引用T& &&
=>T&
- 完美转发
通过一个函数将参数继续转交给另一个函数进行处理,原参数可能是右值,可能是左值,如果还能继续保持参数的原有特征,那么它就是完美的
c++中提供了一个std::forward()
模板函数解决这个问题
void myforward(int&& i){
cout << "myforward(int&&):" << i << endl;
process(std::forward<int>(i));//这里只能转发右值,不能转发左值,不是完美转发
}
myforward(2); // process(int&&):2
借助universal references
通用引用类型和std::forward()
模板函数共同实现完美转发
// 这里利用了universal references,如果写T&,就不支持传入右值,而写T&&,既能支持左值,又能支持右值
template<typename T>
void perfectForward(T && t) {
RunCode(forward<T> (t));
}
vector中emplace
函数就是通过该方法实现的,减少了拷贝构造函数的使用
swap函数需要三次拷贝
使用移动语义可实现高性能交换函数
template <typename T>
void swap(T& a, T& b)
{
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
移动语义可以减少无谓的内存拷贝,要想实现移动语义,需要实现移动构造函数和移动赋值函数。
std::move()
将一个左值转换成一个右值,强制使用移动拷贝和赋值函数,这个函数本身并没有对这个左值什么特殊操作。
std::forward()
和universal references
通用引用共同实现完美转发。
用empalce_back()
替换push_back()
增加性能。
移动语义可以减少无谓的内存拷贝,要想实现移动语义,需要实现移动构造函数和移动赋值函数。
std::move()
将一个左值转换成一个右值,强制使用移动拷贝和赋值函数,这个函数本身并没有对这个左值什么特殊操作。
std::forward()
和universal references
通用引用共同实现完美转发。
用empalce_back()
替换push_back()
增加性能。