|
1. 字符串
1.1 标准库的字符串类
C++标准库提供的string 类型 支持:
- 字符串连接
- 字符串的大小比较
- 子串查找和提取
- 字符串的插入和替换
#include <iostream>
#include <string> //没有.h后缀,说明将要使用标准库的字符串类
using namespace std; //使用标准命名空间
void string_sort(string a[], int len) //字符串排序
{//选择排序
for(int i=0; i<len; i++)
{
for(int j=i; j<len; j++)
{
if( a > a[j] ) //选择排序:选出最小的数据排前面
//标准库中重载了< > 的操作符: 支持比较字符
{
swap(a, a[j]);
}
}
}
}
string string_add(string a[], int len)
// 合并字符串列表中的参数
{
string ret = &#34;&#34;;
for(int i=0; i<len; i++)
{
ret += a + &#34;; &#34;;//标准库中重载了+,支持字符串拼接
}
return ret;
}
int main()
{
string sa[7] =
{
&#34;Hello World&#34;,
&#34;D.T.Software&#34;,
&#34;C#&#34;,
&#34;Java&#34;,
&#34;C++&#34;,
&#34;Python&#34;,
&#34;TypeScript&#34;
};
string_sort(sa, 7);
for(int i=0; i<7; i++)
{
cout << sa << endl;
/*
- 标准库对 左移 和 右移进行了操作符的重载
- a << b 把b赋值给a
*/
}
cout << endl; //C++标准库默认endl是常量&#39;\n&#39;
cout << string_add(sa, 7) << endl;
return 0;
}
/*
C#
C++
D.T.Software
Hello World
Java
Python
TypeScript
C#; C++; D.T.Software; Hello World; Java; Python; TypeScript;
*/
1.2 字符串数字转换
- 标准库中提供了相关的类对字符串和数字进行转换
- 字符串流类(sstream) 用于 string转换
使用方法 string -> 数字
istringstream iss(&#34;123.45&#34;)
double num;
iss >> num;
数字 -> string
ostringstream oss;
oss << 543.21;
string s = oss.str();
}
bool to_number(const string& s, int& n){
istringstream iss(s);
return iss>>n;
}
string to_string(int n){ //只能将int类型的字符串转为string,double类型需要再写一个重载方法
ostringstream oss;
oss << n;
return oss.str();
}
- 但是这样的话,换一个数据类型据需要再写一个重载方法,建议用宏
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
/*
- 得到一个字符串输入流的临时对象
- 将临时对象里的内容传输到n里面
*/
#define TO_NUMBER(s, n) (istringstream(s) >> n)
/*
- 得到一个字符串输出流的临时对象(作用只有一行语句)
- 将n传入流中,然后调用这个流的str()
- 最后再做一个类型转换
*/
#define TO_STRING(n) (((ostringstream&)(ostringstream() << n)).str())
int main()
{
double n = 0;
if( TO_NUMBER(&#34;234.567&#34;, n) )
{
cout << n << endl;
}
string s = TO_STRING(12345);
cout << s << endl;
return 0;
}
1.3 重载操作符:字符串循环右移
abcdefg 循环 右移 3位后 efgabcd
#include <iostream>
#include <string>
using namespace std;
string operator >> (const string& s, unsigned int n) //直接重载右移操作符
{
string ret = &#34;&#34;;
unsigned int pos = 0;
n = n % s.length(); //取余
pos = s.length() - n;
ret = s.substr(pos); //把pos剩余的部分作为子串给提取出来
ret += s.substr(0, pos);
return ret;
}
int main()
{
string s = &#34;abcdefg&#34;;
string r = (s >> 3);
cout << r << endl;//cout是标准库默认打到显示器上替代printf,还有cin是键盘输入
return 0;
}
1.4 字符串类的兼容性质
- string 类兼容了C, 可以按照数组的方式遍历string对象
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s = &#34;a1b2c3d4e&#34;;
int n = 0;
for(int i = 0; i<s.length(); i++)
{
if( isdigit(s) )
{
n++;
}
}
cout << n << endl;
return 0;
}
思考: 类的对象怎么支持数组的下标访问
- 数组访问符是 C/C++ 中的内置操作符
- 数组访问符的原生意义是数组访问和指针运算
//a[n] <======> *(a + n) <======> *(n + a) <======> n[a]
#include <iostream>
#include <string>
using namespace std;
int main()
{
int a[5] = {0};
for(int i=0; i<5; i++)
{
a = i;
}
for(int i=0; i<5; i++)
{
cout << *(a + i) << endl; // cout << a << endl;
}
cout << endl;
for(int i=0; i<5; i++)
{
i[a] = i + 10; // a = i + 10; ===> *(i+a) ===>*(a+i) ===> a
}
for(int i=0; i<5; i++)
{
cout << *(i + a) << endl; // cout << a << endl;
}
return 0;
}
1.5 重载数组访问操作符
重载数组访问操作符能让对象模拟数组的行为
- 只能通过类的成员函数重载
- 重载函数能且仅能使用一个参数
- 可以定义不同参数的多个重载函数
#include <iostream>
#include <string>
using namespace std;
class Test
{
int a[5];
public:
int& operator [] (int i) //数组访问操作符重载函数能且仅能使用一个参数
{
return a;
}
//以字符串作为下标访问一个数组
int& operator [] (const string& s) //不同参数的数组访问操作符重载函数
{
if( s == &#34;1st&#34; )
{
return a[0];
}
else if( s == &#34;2nd&#34; )
{
return a[1];
}
else if( s == &#34;3rd&#34; )
{
return a[2];
}
else if( s == &#34;4th&#34; )
{
return a[3];
}
else if( s == &#34;5th&#34; )
{
return a[4];
}
return a[0];
}
int length()
{
return 5;
}
};
int main()
{
Test t;
for(int i=0; i<t.length(); i++)
{
t = i;
}
for(int i=0; i<t.length(); i++)
{
cout << t << endl;
}
cout << t[&#34;5th&#34;] << endl;
cout << t[&#34;4th&#34;] << endl;
cout << t[&#34;3rd&#34;] << endl;
cout << t[&#34;2nd&#34;] << endl;
cout << t[&#34;1st&#34;] << endl;
return 0;
}
让对象模拟数组的行为
#include &#34;IntArray.h&#34;
IntArray::IntArray(int len)
{
m_length = len;
}
bool IntArray::construct()
{
bool ret = true;
m_pointer = new int[m_length];
if( m_pointer )
{
for(int i=0; i<m_length; i++)
{
m_pointer = 0;
}
}
else
{
ret = false;
}
return ret;
}
IntArray* IntArray::NewInstance(int length)
{
IntArray* ret = new IntArray(length);
if( !(ret && ret->construct()) )
{
delete ret;
ret = 0;
}
return ret;
}
int IntArray::length()
{
return m_length;
}
bool IntArray::get(int index, int& value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
bool IntArray::set(int index, int value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
m_pointer[index] = value;
}
return ret;
}
int& IntArray::operator [] (int index)
{
return m_pointer[index];
}
IntArray& IntArray::self()
{
return *this;
}
IntArray::~IntArray()
{
delete[]m_pointer;
}
#include <iostream>
#include <string>
#include &#34;IntArray.h&#34;
using namespace std;
int main()
{
IntArray* a = IntArray::NewInstance(5);
if( a != NULL )
{
IntArray& array = a->self();
cout << &#34;array.length() = &#34; << array.length() << endl;
array[0] = 1;
for(int i=0; i<array.length(); i++)
{
cout << array << endl;
}
}
delete a;
return 0;
}
1.6 字符串赋值Tips
- 在需要进行深拷贝的时候必须重载赋值操作符
- 赋值操作符和拷贝构造函数有同等重要的意义
- string 类通过一个数据空间保存字符数据
- string 类通过一个成员变量保存当前字符串的长度,C++开发时应避免C中的编程思维
2. 重载操作符
2.1 智能指针
通过一个对象来模拟指针的行为,这个对象叫智能指针
问题
- 需要一个特殊的指针,在指针生命周期结束时主动释放堆内存
- 一片堆空间最多只能由一个指针标识
- 杜绝指针运算和指针比较
智能指针的使用规则
- 只能用来指向堆空间中的对象或者变量,不能指向栈空间
#include <iostream>
#include <string>
using namespace std;
class Test
{
int i;
public:
Test(int i)
{
cout << &#34;Test(int i)&#34; << endl;
this->i = i;
}
int value()
{
return i;
}
~Test()
{
cout << &#34;~Test()&#34; << endl; //从左往右连续传送
}
};
class Pointer //创建智能指针对象
{
Test* mp;//创建智能指针指向Test
public:
Pointer(Test* p = NULL)
//用堆空间上的一个内存地址进行初始化:构造函数的参数为Test类型的指针,初始值NULL
{
mp = p;
}
/*
- 重载指针特征操作符(-> 和 *)
- 只能通过类的成员函数重载
- 只能定义一个重载函数
*/
Pointer(const Pointer& obj)
{
/*
一片堆空间最多只能由一个指针标识
初始化对象将自己管理的堆空间交给当前对象,然后置NULL
*/
mp = obj.mp;
const_cast<Pointer&>(obj).mp = NULL;
//由于传参时加上了const,所以是只读对象,先用const_cast去掉只读属性
}
Pointer& operator = (const Pointer& obj)
{
if( this != &obj )
{
delete mp;
mp = obj.mp;
const_cast<Pointer&>(obj).mp = NULL;
}
return *this;
}
Test* operator -> ()
{
return mp;
}
Test& operator * ()
{
return *mp;
}
bool isNull()//判断当前的只能指针是否为空
{
return (mp == NULL);
}
~Pointer()
{
delete mp; //在析构函数中释放成员变量mp指向的堆空间
}
};
int main()
{
Pointer p1 = new Test(0); //定义只能指针
cout << p1->value() << endl;
Pointer p2 = p1; //定义第二个指针指针,赋值给p2
cout << p1.isNull() << endl; //p1 的堆空间传给 p2 后,p1被置为NULL,然后析构函数
cout << p2->value() << endl;
return 0;
}
/*
Test(int i)
0
1
0
~Test()
*/
2.2 逻辑操作符的重载
不建议逻辑操作符重载,因为会使短路规则失效
#include <iostream>
#include <string>
using namespace std;
class Test
{
int mValue;
public:
Test(int v)
{
mValue = v;
}
int value() const
{
return mValue;
}
};
bool operator && (const Test& l, const Test& r)
{
return l.value() && r.value();
}
bool operator || (const Test& l, const Test& r)
{
return l.value() || r.value();
}
Test func(Test i)
{
cout << &#34;Test func(Test i) : i.value() = &#34; << i.value() << endl;
return i;
}
int main()
{
Test t0(0);
Test t1(1);
if( func(t0) && func(t1) )
{
cout << &#34;Result is true!&#34; << endl;
}
else
{
cout << &#34;Result is false!&#34; << endl;
}
cout << endl;
if( func(1) || func(0) )
{
cout << &#34;Result is true!&#34; << endl;
}
else
{
cout << &#34;Result is false!&#34; << endl;
}
return 0;
}
2.3 逗号操作符
逗号符号构成逗号表达式:
- 将多个子表达式连为一个表达式
- 最后一个子表达式的值为逗号表达式的值
- 前N-1个子表达式可以没有返回值
- 逗号表达式按照从左往右的顺序计算每个子表达式的值
#include <iostream>
#include <string>
using namespace std;
void func(int i)
{
cout << &#34;func() : i = &#34; << i << endl;
}
int main()
{
int a[3][3] = {
(0, 1, 2), //其实这里是个逗号表达式, 这个二维数组初始化的值成了(2,5,8), 应该用{}
(3, 4, 5),
(6, 7, 8)
};
int i = 0;
int j = 0;
while( i < 5 )
func(i),
i++;
for(i=0; i<3; i++)
{
for(j=0; j<3; j++)
{
cout << a[j] << endl;
}
}
(i, j) = 6; //逗号表达式等价于 j = 6
cout << &#34;i = &#34; << i << endl;
cout << &#34;j = &#34; << j << endl;
return 0;
}
重载后的逗号表达式:可能变成了从右边往左运算
- 进入函数体前必须完成所有参数的计算
- 函数的参数计算次序是不固定的
- 重载后无法严格从左往右计算表达式
所以工程开发中不要重载逗号操作符
2.4 重载前置后后置操作符
C++ 的 前置和后置都是一样的,没什么区别
- 现代编译器产品会对代码进行优化,使得最终的二进制程序更加高效,但优化后的二进制程序对前置和后置都一样(汇编代码一样)
#include <iostream>
#include <string>
using namespace std;
int main()
{
int i = 0;
i++;
++i;
return 0;
}
重载++操作符
- 全局函数和成员函数均可重载
- 重载前置++ 操作符不需要额外的参数
- 重载后置 ++ 操作符需要一个int类型的占位参数
重载后 前置 后置的效率
- 前置++的效率比后置高,因为不需要在栈上生成一个对象占用空间
- 就意味着不需要调用构造函数和析构函数
#include <iostream>
#include <string>
using namespace std;
class Test
{
int mValue;
public:
Test(int i)
{
mValue = i;
}
int value()
{
return mValue;
}
Test& operator ++ () //前置的++操作符不需要额外的参数
{
/*
- 前置++的效率比后置高,因为不需要在栈上生成一个对象占用空间
- 就意味着不需要调用构造函数和析构函数
*/
++mValue; //先自增1,然后返回自身,即返回this指针指向的当前对象
return *this;
}
Test operator ++ (int) //后置的++操作要带一个占位值 int类型
{
Test ret(mValue); //调用构造函数
mValue++; //调用析构函数
return ret;
}
};
int main()
{
Test t(0);
//现在是调用了不同重载函数
t++; //因为要生成一个返回值对象,所以效率比前置++低
++t; //效率更高
return 0;
}
3. 类型转换
3.1 隐式类型转换
- 标准数据之间会进行隐士类型安全转换,即小类型在和大类型计算时会隐士转换到大类型
- 小类型向大类型转换是安全的,不会发生截断
char -> int -> unsigned int -> long -> unsigned long -> float -> double
|
|
short
#include <iostream>
#include <string>
using namespace std;
int main()
{
short s = &#39;a&#39;;
unsigned int ui = 1000;
int i = -2000;
double d = i; //
cout << &#34;d = &#34; << d << endl;
cout << &#34;ui = &#34; << ui << endl;
cout << &#34;ui + i = &#34; << ui + i << endl;
if( (ui + i) > 0 )
/*
unsigned int 类型的ui遇到int类型的变量,int类型的i会发生隐士类型转换,导致计算错误
所以这里成了两个unsigned int相加
*/
{
cout << &#34;Positive&#34; << endl;
}
else
{
cout << &#34;Negative&#34; << endl;
}
cout << &#34;sizeof(s + &#39;b&#39;) = &#34; << sizeof(s + &#39;b&#39;) << endl;
return 0;
}
转换构造函数
- 有且仅有一个参数
- 参数是基本类型
- 参数是其他类类型
3.2 explicit 显示类型转换
有时隐式类型转换不一定时需要的,当编译器默认去调用转换构造函数,比如基本数据类型转成类对象,这样可能就会产生Bug
explicit 关键字
- 工程中通过explicit 关键字杜绝编译器的转换尝试
- 转换方式
- static_cast<ClassName>(value);
- ClassName(value);
- (ClassName)value;
- 转换构造函数被 explicit 修饰时只能进行显示转换
#include <iostream>
#include <string>
using namespace std;
class Test
{
int mValue;
public:
Test()
{
mValue = 0;
}
explicit Test(int i) //给转换构造函数加上explicit关键字,便只能手动显示转换
{
mValue = i;
}
Test operator + (const Test& p)
{
Test ret(mValue + p.mValue);
return ret;
}
int value()
{
return mValue;
}
};
int main()
{
Test t;
t = static_cast<Test>(5); // t = Test(5);
Test r;
r = t + static_cast<Test>(10); // r = t + Test(10);
cout << r.value() << endl;
return 0;
}
3.3 类型转换函数
类型转换函数
operator Type(){
Type ret;
....
return ret;
}
比如 Test t(1);
int i = t;
如发现 Test 类中定义了operator int() 就可以不报错而进行转换
- 与转换构造函数具有同等地位
- 使得编译器有能力将对象转化为其他类型
- 编译器能够隐式的使用类型转换函数
#include <iostream>
#include <string>
using namespace std;
class Test
{
int mValue;
public:
Test(int i = 0)
{
mValue = i;
}
int value()
{
return mValue;
}
operator int () //定义类型转换函数,通过operator 关键字
{
return mValue;
}
};
int main()
{
Test t(100);
int i = t; // int i = t.int()
cout << &#34;t.value() = &#34; << t.value() << endl;
cout << &#34;i = &#34; << i << endl;
return 0;
}
与转换构造函数冲突
- 无法抑制隐式类型转换函数的调用,就可能与转换构造函数发生冲突,编译器不知道用是用转换构造函数还是类型转换函数
- 转换构造函数前声明explicit: 不让编译器主动调用
#include <iostream>
#include <string>
using namespace std;
class Test;
class Value
{
public:
Value()
{
}
//类型转换函数可能与转换构造函数冲突 \
在转换构造函数前声明explicit: 不让编译器主动调用,\
避免和Test中的类型转换函数冲突
explicit Value(Test& t)
{
}
};
class Test
{
int mValue;
public:
Test(int i = 0)
{
mValue = i;
}
int value()
{
return mValue;
}
operator Value() //类型转换函数
{
Value ret;
cout << &#34;operator Value()&#34; << endl;
return ret;
}
};
int main()
{
Test t(100);
Value v = t; //t.operatoe Value()
return 0;
}
- 实际工程以 Type toType() 公有成员代替类型转换函数,如QT平台
#include <QDebug>
#include <QString>
int main()
{
QString str = &#34;&#34;;
int i = 0;
double d = 0;
short s = 0;
str = &#34;-255&#34;;
//qt里定义的,类类型的转换
i = str.toInt();
d = str.toDouble();
s = str.toShort();
qDebug() << &#34;i = &#34; << i << endl;
qDebug() << &#34;d = &#34; << d << endl;
qDebug() << &#34;s = &#34; << s << endl;
return 0;
} |
|