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),是通过重载()运算符模拟函数形为的类

  1. 仿函数是一种类
  2. 必须重载()
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::movemove对于拥有如内存、文件句柄等资源的成员的对象有效)将其转换成右值即可,转移了资源控制权

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()增加性能。

转载:[c++11]我理解的右值引用、移动语义和完美转发

posted @ 2021-10-22 13:18  流光之中  阅读(25)  评论(0编辑  收藏  举报