字面量
字面量(Literal)是计算机编程中用于表达源代码中一个固定值的表示法。通俗地说,字面量就是"所见即所得"的常量,直接在代码中书写数字、字符串或布尔值等,无需通过变量名引用。
这些变量赋值语句等号右侧的表达式 5、7、5.6、7.3f、655ul 都是字面量。字面量都有类型,默认是int、double等,也可以指定类型,比如f代表单精度浮点数,ul代表无符号长整型。
cpp
const int a = 5;
constexpr int c = 7;
double d = 5.6;
float f = 7.3f; //单精度,如果默认则是双精度
unsigned long l = 655ul; //无符号长整型
如果想要定义比如5kb = 5*1024这样的表达,则可以自定义字面量。自定义字面量使用类似运算符重载的方式,通过前缀 operator "" (双引号)+ 字面量名称定义,自定义字面量名称最好以"_"开头从而与其他默认字面量区别。
另外,自定义字面量要求特定的参数类型,这里可以使用unsigned long long。
cpp
#include <iostream>
int operator "" _kb(unsigned long long value) {
return value * 1024;
}
int main() {
int a = 10;
const int c = 7;
constexpr float pi = 3.1415926f;
unsigned int u = 1e10;
int size = 5_kb;
std::cout << size << std::endl; //5120
}
再定义一个微(比如微米,微秒)为单位的字面量:
这里使用指定的long double作为参数类型。另外用自定义字面量给变量赋值时,"=" 右边为常量表达式,可以使用constexpr修饰函数。
cpp
constexpr double operator "" _micro(long double value) {
return value * 1e-6;
}
int main() {
double m = 6.8_micro;
std::cout << m << std::endl; //6.8e-06
}
字符串字面量
字符在c++中为char类型,字符字面量用 ' ' 单引号包围一个字符,以ascii码规定其内容,一个英文字符通常占用1字节。
字符串字面量用 " " 双引号包围一个字符序列。在使用其他字面量时并不会占用额外的空间,这时字面量往往被视为一个立即数,在编码时会被直接编写到指令中;而字符串字面量在使用时,会被存放到只读区域。也就是说,其他字面量的数据存储在指令中,存在于代码段的内存中;字符串字面量则会占用数据段内存。
字符串的长度不确定,会使用 '\0' 终止符表示结束,并多占用一个字节。字符串会存储在只读区一片连续的内存中,需要使用指针来寻找。可以通过字符数组存储,或者使用char指针,推荐使用const char*,因为字符串存储于只读区域,修改会引发报错。
cpp
int main() {
char c[] = "hello";
std::cout << c << std::endl; //hello
char c2[] = { 'h', 'e', 'l', 'l', 'o','\0'};
std::cout << c2 << std::endl; //hello
const char* s = "world";
std::cout << s << std::endl; //world
}
字符串由于使用指针保存,数据在处理时有很多不方便,比如比较大小时如果使用变量名会变成比较地址大小;数组之间赋值操作也不合法,指针赋值只进行了浅复制,只是令指针指向同一片内存。
c++提供了string类来封装针对字符串的各类操作,在这里封装一个字符串类来演示对字符串的各类操作。
可以尝试实现一个包含了无参数构造,普通构造,拷贝构造,析构,拷贝赋值,比较运算符重载和获取字符串内容的函数来熟悉字符串字面量和字符串操作。
cpp
class String {
public:
//构造与析构函数
String() : data_(nullptr), size_(0), capacity_(0) {}
String(const char* s)
: size_(std::strlen(s)),
capacity_(size_ + 1), //字符串内容的长度加上一个字符的空间,包含字符串结尾的'\0'字符
data_(nullptr)
{
data_ = new char[capacity_]; //分配堆内存
memcpy(data_, s, capacity_); //将字符串内容复制到堆内存中,包含字符串结尾的'\0'字符
}
~String() {
delete[] data_;
}
//拷贝构造函数
String(const String& s)
: size_(s.size_),
capacity_(s.capacity_)
{
if (s.data_ != nullptr) {
delete[] data_;
data_ = nullptr;
}
data_ = new char[capacity_];
memcpy(data_, s.data_, capacity_);
}
//拷贝赋值运算符重载
String & operator =(const String& s) {
if (this != &s) { //检查自赋值
//先拷贝数据到新的内存区域,再释放旧的内存区域。
char* newdata = new char[s.capacity_];
memcpy(newdata, s.data_, s.capacity_);
delete[] data_;
data_ = newdata;
size_ = s.size_;
capacity_ = s.capacity_;
}
return *this;
}
//比较运算符重载
bool operator ==(const String& s) const {
if (size_ != s.size_)
return false;
return std::memcmp(data_, s.data_, size_) == 0; //memcmp函数返回值为0表示两块内存区域的内容相同,
// 返回值小于0表示第一块内存区域的内容小于第二块内存区域的内容,
// 返回值大于0表示第一块内存区域的内容大于第二块内存区域的内容。
}
//获取字符串内容
const char * get_string() const {
return data_;
}
private:
char* data_;
size_t size_;
size_t capacity_;
};
void StringDemo() {
String s1("hello");
String s2 = s1; //调用拷贝构造函数
String s3;
s3 = s1; //调用拷贝赋值运算符重载
std::cout << s1.get_string() << std::endl; //hello
std::cout << s2.get_string() << std::endl; //hello
std::cout << s3.get_string() << std::endl; //hello
}