字符串的原始字面量
原始字符串字面量的定义为:R “xxx(raw string)xxx”
其中,原始字符串必须用括号()括起来,括号的前后可以加其他字符串,所加的字符串会被忽略,并且加的字符串必须在括号两边同时出现。
#include <iostream>
#include <string>
int main()
{
// 一个普通的字符串,'\n'被当作是转义字符,表示一个换行符。
std::string normal_str = "First line.\nSecond line.\nEnd of message.\n";
// 一个raw string,'\'不会被转义处理。因此,"\n"表示两个字符:字符反斜杠 和 字母n。
std::string raw_str = R"(First line.\nSecond line.\nEnd of message.\n)";
std::cout << normal_str << std::endl;
std::cout << raw_str << std::endl;
std::cout << R"foo(Hello, world!)foo" << std::endl;
// raw string可以跨越多行,其中的空白和换行符都属于字符串的一部分。
std::cout <<R"(
Hello,
world!
)" << std::endl;
return 0;
}
在 C++11 之前如果一个字符串分别写到了不同的行里边,需要加连接符,这种方式不仅繁琐,还破坏了表达式的原始含义,如果使用原始字面量就变得简单很多,很强直观,可读性强。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str = R"(<html>
<head>
<title>
海贼王
</title>
</head>
<body>
<p>
我是要成为海贼王的男人!!!
</p>
</body>
</html>)";
cout << str << endl;
return 0;
}
final关键字
C++ 中增加了 final 关键字来限制某个类不能被继承,或者某个虚函数不能被重写,和 Jave 的 final 关键字的功能是类似的。如果使用 final 修饰函数,只能修饰虚函数,并且要把final关键字放到类或者函数的后面
修饰函数
使用 final 修饰函数,只能修饰虚函数,这样就能阻止子类重写父类的这个函数了:
class Base
{
public:
virtual void test()
{
cout << "Base class...";
}
};
class Child : public Base
{
public:
void test() final
{
cout << "Child class...";
}
};
class GrandChild : public Child
{
public:
// 语法错误, 不允许重写
void test()
{
cout << "GrandChild class...";
}
};
修饰类
使用 final 关键字修饰过的类是不允许被继承的,也就是说这个类不能有派生类。
class Base
{
public:
virtual void test()
{
cout << "Base class...";
}
};
class Child final: public Base
{
public:
void test()
{
cout << "Child class...";
}
};
// error, 语法错误
class GrandChild : public Child
{
public:
};
override
override 关键字确保在派生类中声明的重写函数与基类的虚函数有相同的签名,同时也明确表明将会重写基类的虚函数,这样就可以保证重写的虚函数的正确性,也提高了代码的可读性,和 final 一样这个关键字要写到方法的后面。使用方法如下:
class Base
{
public:
virtual void test()
{
cout << "Base class...";
}
};
class Child : public Base
{
public:
void test() override
{
cout << "Child class...";
}
};
class GrandChild : public Child
{
public:
void test() override
{
cout << "Child class...";
}
};
上述代码中第 13 行和第 22 行就是显示指定了要重写父类的 test() 方法,使用了 override 关键字之后,假设在重写过程中因为误操作,写错了函数名或者函数参数或者返回值编译器都会提示语法错误,提高了程序的正确性,降低了出错的概率。(不过个人觉得有点多此一举)。
模板的改进
- 模板的右尖括号
- 模板的别名
- 函数模板的默认模板参数
模板的右尖括号
C++11之前是不允许两个右尖括号出现的,会被认为是右移操作符,所以需要中间加个空格进行分割,避免发生编译错误。
int main() {
std::vector<std::vector<int>> a; // error
std::vector<std::vector<int> > b; // ok
}
模板的别名
C++11引入了using,可以轻松的定义别名,而不是使用繁琐的typedef。
typedef std::vector<std::vector<int>> vvi; // before c++11
using vvi = std::vector<std::vector<int>>; // c++11
template<class T>
struct Alloc { };
template<class T>
using Vec = vector<T, Alloc<T>>; // 类型标识为 vector<T, Alloc<T>>
Vec<int> v; // Vec<int> 同 vector<int, Alloc<int>>
使用using明显简洁并且易读。
typedef void (*func)(int, int); // 啥玩意,看不懂
using func = void (*)(int, int); // 起码比typedef容易看的懂吧
上面的代码使用using起码比typedef容易看的懂一些吧
函数模板的默认模板参数
C++11之前只有类模板支持默认模板参数,函数模板是不支持默认模板参数的,C++11后都支持。
template <typename T, typename U=int>
class A {
T value;
};
template <typename T=int, typename U> // error
class A {
T value;
};
类模板的默认模板参数必须从右往左定义,而函数模板则没有这个限制。
template <typename R, typename U=int>
R func1(U val) {
return val;
}
template <typename R=int, typename U>
R func2(U val) {
return val;
}
int main() {
cout << func1<int, double>(99.9) << endl; // 99
cout << func1<double, double>(99.9) << endl; // 99.9
cout << func1<double>(99.9) << endl; // 99.9
cout << func1<int>(99.9) << endl; // 99
cout << func2<int, double>(99.9) << endl; // 99
cout << func1<double, double>(99.9) << endl; // 99.9
cout << func2<double>(99.9) << endl; // 99.9
cout << func2<int>(99.9) << endl; // 99
return 0;
}
对于函数模板,参数的填充顺序是从左到右的。
同时C++11支持变长参数模板:
template <typename T>
void func(const T& t){
cout << t << '\n';
}
template <typename T, typename ... Args>
void func(const T& t, Args ... args){
cout << t << ',';
func(args...);
}
关于C++11对于模板的改进就讲到这里
类型转换
数值转字符串
使用 to_string() 方法可以非常方便地将各种数值类型转换为字符串类型,这是一个重载函,函数声明位于头文件 中,函数原型如下:
// 头文件 <string>
string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);
示例代码如下:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string pi = "pi is " + to_string(3.1415926);
string love = "love is " + to_string(5.20 + 13.14);
cout << pi << endl;
cout << love << endl;
return 0;
}
字符串转换为数值
由于 C++ 中的数值类型包括整形和浮点型,因此针对于不同的类型提供了不同的函数,通过调用这些函数可以将字符串类型转换为对应的数值类型。
// 定义于头文件 <string>
int stoi( const std::string& str, std::size_t* pos = 0, int base = 10 );
long stol( const std::string& str, std::size_t* pos = 0, int base = 10 );
long long stoll( const std::string& str, std::size_t* pos = 0, int base = 10 );
unsigned long stoul( const std::string& str, std::size_t* pos = 0, int base = 10 );
unsigned long long stoull( const std::string& str, std::size_t* pos = 0, int base = 10 );
float stof( const std::string& str, std::size_t* pos = 0 );
double stod( const std::string& str, std::size_t* pos = 0 );
long double stold( const std::string& str, std::size_t* pos = 0 );
- str:要转换的字符串
- pos:传出参数,记录从哪个字符开始无法继续进行解析,比如: 123abc, 传出的位置为 3
- base:若 base 为 0 ,则自动检测数值进制:若前缀为 0 ,则为八进制,若前缀为 0x 或 0X,则为十六进制,否则为十进制。
实例如下:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1 = "45";
string str2 = "3.14159";
string str3 = "9527 with words";
string str4 = "words and 2";
int myint1 = std::stoi(str1);
float myint2 = std::stof(str2);
int myint3 = std::stoi(str3);
// 错误: 'std::invalid_argument'
// int myint4 = std::stoi(str4);
cout << "std::stoi(\"" << str1 << "\") is " << myint1 << endl;
cout << "std::stof(\"" << str2 << "\") is " << myint2 << endl;
cout << "std::stoi(\"" << str3 << "\") is " << myint3 << endl;
// cout << "std::stoi(\"" << str4 << "\") is " << myint4 << endl;
}