字面值
编译期创建、全局生命周期、只读的常量。不可寻址。
- 字面值的定位是“值的直接表示”,不可修改。
- 编译器会根据字面量的形式推断其类型。
- 包括基础类型字面量(
int型,double型,char型,const char[]型,bool型)。
- 包括基础类型字面量(
- UDL, User-Defined Literals,用户自定义字面量。是C++的字面值的一个子集。
- 由程序员手动定义(通过
operator"" 后缀函数),带_开头的自定义后缀是UDLd的标志性特征。 5是内置整数字面量,5_KM2M是基于5扩展的用户自定义字面量(UDL).
- 由程序员手动定义(通过
为什么 "abc" 不是 std::string 类型?
因为字面型常量的类型只能是auto倒推的,而倒推只能推导基本类型。std::string型是C++标准库的封装类。
为什么 'a' 不是 const char 类型?
- 首先认识到字面值常量尽管底层被设计为
char型,但不能作为左值。- 之所以不是
const char是底层兼容设计。
- 之所以不是
- 而且
const是用来形容变量/对象属性 的,而字面值常量是值本身。- (字面量本身没有存储位置(或仅作为立即数存在),
const修饰它毫无意义。)
- (字面量本身没有存储位置(或仅作为立即数存在),
为什么 "abc" 是 const char [] 类型?
为避免`"abc"[0] = 'x'` 这类代码会编译通过。
字面值运算符
UDL就一定会涉及到字面值运算符。
字面值运算符是由operator"" _函数名定义的特殊函数,是有关字面量的自定义扩展。
具体格式为:
-
`constexpr T operator"" _函数名( T' 入参名) { return 返回值;}` (`constexpr`可以没有;`T` 和 `T'`分别表示入参和返回值的数据类型,不必相同。)
实例:
// 本代码用 字面值运算符实现“千米到米的单位换算”
constexpr long long operator "" _KM2m (long long kilometer){
retuen kilometer*1000;
}
int main(){
auto distance = 5_KM2m;
}
-
将 字面值运算符 理解为 特殊的函数定义 是正确的。
constexpr long long operator"" _KM2m (long long kilometer){ return kilometer*1000; }
constexpr的作用是 优先在编译期处理,去除代码逻辑照样正确,但只能在运行期计算结果。- 这个函数的入参是
long long kilometer- 即入参的变量标识符为
kilometer, 变量类型为long long型。 - (之所以是
long long型是为了保证数值范围足够大,避免int溢出。)
- 即入参的变量标识符为
- 这个函数的函数名是
_KM2m- 强制要求:后缀必须以下划线_开头
operator""之后紧跟_KiloMeter,表示对这个KiloMeter字面值进行运算符自定义;operator""就代表在进行 自定义字面量运算符 的操作。
constexpr表示函数属性:编译期常量函数(普通函数也可以加constexpr)long long表示函数返回值类型。
-
字面值运算符 的特殊之处:
- 函数名特殊:字面值运算符必须是
operator"" _函数名 - 调用方式特殊:编译器解析
数字_后缀时自动调用,如5_KM2M→ 自动调operator"" _KM2M(5); - 针对不同字面量(整数 / 浮点 / 字符串),参数类型有固定规则
- (比如整数字面量只能用
long long/unsigned long long/const char*)。
- (比如整数字面量只能用
auto是编译期推导,推导完成后类型就固定。- 此处函数返回值已确定是
long long型, - 因此这个
auto完全可以直接改写为long long型不影响代码功能。
- 此处函数返回值已确定是
- 函数名特殊:字面值运算符必须是
char型字面值运算符
- 处理单个char型字面量的自定义扩展。
//将单个字符的小写输入转成大写输出。
constexpr char operator"" _lower2upper (char c) {
return (c >= 'a' && c <= 'z') ? (c - 32) : c;
}
int main() {
char ch1 = 'a'_lower2upper;
char ch1 = 'b'_lower2upper;
return 0;
}
- 处理字符串字面量
const char []的自定义扩展。
//带长度参数
#include <cstddef>
#include <string>
// 用 std::size_t len 让编译器自动获取并传递 字符串字面值的长度
std::string operator"" _str(const char* str, std::size_t len) {
return std::string (str, len);
// 将const char [] 字符串字面值 转化为 std::string 返回。
}
int main() {
auto s1 = "hello"_str;
//这个地方用auto属于类型在编译期可明确推导,用auto无性能损耗。
//减小维护成本也是好代码的标识。
return 0;
}
//——————————————————————————————————————————————————————————————————
//——————————————————————————————————————————————————————————————————
//不带长度参数,不推荐。
//不推荐的原因在于不知道字符串长度就必须在运行期逐个遍历。
//且,C 风格字符串以 `'\0'` 作为结束标志,`std::string(str)` 会把 `'\0'` 当作 “字符串结尾”,导致无法构造包含 `'\0'` 的完整字符串;而 `std::string(str, len)` 不受 `'\0'` 影响,能正确处理这类场景。
#include <cstring>
#include <string>
std::string operator"" _str(const char *str) {
return std::string(str, std::strlen(str));
// std::strlen(str) 手动计算会影响性能,属于运行期逐字符遍历的额外开销;
}
int main() {
auto s1 = "world"_str;
return 0;
}
字面值运算符的总结
long long 型 UDL 仅处理单个整数,而 char 型 UDL 分 “单个字符” 和 “字符串” 两种场景,字符串 UDL 支持带长度参数的特殊形式。 std::string(str, len) 是 UDL 函数中构造 std::string 的最优选择,兼顾高效和安全。
C++字面值包括UDL和原生字面值
原生字面值相比较UDL是内置,即默认存在的无需定义的。
比如,5_KM2M中5就是内置字面值,_KM2M是UDL.即5_KM2M是基于内置型字面值5扩展的UDL。