1、引用
引用(reference) 为对象起了另外一个名字,通过将声明符写成 &d 的形式来定义引用类型,其中 d 是声明的变量名:
int ival = 1024;
int &refVal = ival; // 引用类型必须被初始化
因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。
为引用赋值,实际上是把值赋给了与引用绑定的对象。 获取引用的值,实际上是获取了与引用绑定的对象的值。
2、指针
指针(pointer) 是指向另外一种类型的复合类型。
- 指针本身就是一个对象,允许对指针赋值和拷贝,可以先后指向几个不同的对象。
- 指针无须在定义时赋初值。
int ival = 42;
int *p = &ival;
指针值
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针,意味着指针没有指向任何对象
- 无效指针,也就是上述情况之外的其它值
3、const
默认状态下,const 对象仅在文件内有效。
- const 的引用: 对常量的引用(reference to const),对常量的引用不能被用作修改它所绑定的对象
const int ci = 1024;
const int &r1 = ci; // 正确:引用及其对应的对象都是常量
r1 = 42; // 错误:r1 是对常量的引用
int &r2 = ci; // 错误:试图让一个非常量引用指向一个常量对象
- 指向常量的指针(pointer to const): 不能改变其所指对象的值。
const double pi = 3.14; // pi 是一个常量,它的值不能改变
double *ptr = π // 错误:ptr 是一个普通指针
const double *cptr = π // 正确:cptr 可以指向一个双精度常量
*cptr = 42; // 错误:不能改 *cptr 赋值
- 常量指针(const pointer): 必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。
int errNumb = 0;
int *const curErr = &errNumb; // curErr 将一直指向 errNumb
const double pi = 3.14159;
const double *const pip = π // pip 是一个指向常量对象的常量指针
要想弄清这些声明的含义最行之有效的办法就是从右向左阅读,如离 curErr 最近的符号是 const,意味着 curErr 本身是一个常量对象,对象的类型由声明符的其余部分确定。声明符中的下一个符号是 * ,意思是 curErr 是一个常量指针。最后,该声明语句的基本数据类型部分确定了常量指针指向的是一个 int 对象。
可以简记:左定值,右定向,const修饰不变量
- 如果const位于 * 的左侧,const 修饰指针指向的内容,则内容为不可变量,简称左定值;
- 如果const位于 * 的右侧,const 修饰指针,则指针为不可变量,简称右定向;
4、friend 友元
类可以允许其它类或者函数访问它的非公有成员,方法是令其它类或者函数成为它的友元(friend)。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend:
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
// 程序的主函数
int main( )
{
Box box;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth( box );
return 0;
}
5、拷贝构造、拷贝复制、析构函数
#ifndef HJ_C___STRING_H
#define HJ_C___STRING_H
class String {
public:
explicit String(const char *cstr = nullptr);
String(const String &str);
String &operator=(const String &str);
char *get_c_str() const { return m_data; }
~String();
private:
char *m_data;
};
#include <cstring>
// 拷贝构造
inline String::String(const char *cstr) {
if (cstr) {
m_data = new char[strlen(cstr) + 1];
strcpy(m_data, cstr);
} else {
m_data = new char[1];
*m_data = '\0';
}
}
// 拷贝构造
inline String::String(const String &str) {
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
}
// 拷贝复制
inline String &String::operator=(const String &str) {
if (this == &str) return *this;
delete[] m_data;
m_data = new char[strlen(str.m_data) + 1];
strcpy(m_data, str.m_data);
return *this;
}
// 析构函数
inline String::~String() {
delete[] m_data;
}
#include <iostream>
using namespace std;
ostream &operator<<(ostream &os, const String &str) {
return os << str.get_c_str();
}
#endif //HJ_C___STRING_H
6、static
7、template
- 函数模板(function template)
template<typename T>
int compare(const T &l, const T &r) {
if (l < r) return -1;
if (l > r) return 1;
return 0;
}
- 类模板
template<typename T>
class Blob {
public:
typedef T value_type;
typedef typename std::vector<T>::size_type size_type;
Blob();
Blob(std::initializer_list<T> il);
// Blob 中元素数目
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
private:
std::shared_ptr<std::vector<T>> data;
void check(size_type i, const std::string &msg) const;
};
8、namespace
命名空间(namespace)为防止名字冲突提供了更加可控的机制。
namespace cplusplus_primer {
class Sales_data { };
Sales_data operator+(const Sales_data&, const Sales_data&);
class Query {}
} // 命名空间结束后无须分号
9、参数传递
-
传值参数
当初始化一个非引用类型的变量时,初始值被拷贝给变量,此时对变量的改动不会影响初始值。
-
传引用参数
通过使用引用形参,允许函数改变一个或多个实参的值。
void reset(int &i) { i = 0; } int j = 42; reset(j); // j 采用传引用方式,它的值被改变 cout << "j = " << j << endl; // 输出 j = 0 -
使用引用避免拷贝
拷贝大的类型对象或者容器对象比较低效,有些类型就不支持拷贝操作,函数只能通过引用形参访问该类型的对象
bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); }
10、返回类型和 return 语句
-
不要返回局部对象的引用或指针
函数完成后,它所占用的存储空间也随之被释放。因此,函数终止意味着局部变量的引用将指向不再有效的内存局域。
const string &manip() { string ret; if(!ret.empty()) return ret; // 错误:返回局部对象的引用! else return "Empty"; // 错误:"Empty"是一个局部临时辆 }
11、内联函数和 constexpr 函数
-
内联函数可避免函数调用的开销
inline const string & shorterString(const string &s1, const string &s2) { return s1.size() <= s2.size() ? s1 : s2; } -
constexpr 函数是指能用于常量表达式的函数。
- 函数的返回类型及所有形参的类型都是字面值类型
- 函数体中必须有且只有一条 return 语句
constexpr int new_sz() { return 42; } constexpr int foo = new_sz(); // 正确:foo 是一个常量表达式
12、函数指针
函数指针指向的是函数而非对象。和其它指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。
// 比较两个 string 对象的长度
bool lengthCompare(const string&, const string &);
该函数类型是 bool(const string&, const string&),
bool (*pf)(const string &, const string &); // 未初始化
pf 前面有个 * ,因此 pf 是指针,右侧是形参列表,表示 pf 指向的是函数,再观察左侧,发现函数的返回类型是 bool。