C++ 基础语法

121 阅读5分钟

1、引用

引用(reference) 为对象起了另外一个名字,通过将声明符写成 &d 的形式来定义引用类型,其中 d 是声明的变量名:

int ival = 1024;
int &refVal = ival;  // 引用类型必须被初始化

因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。

为引用赋值,实际上是把值赋给了与引用绑定的对象。 获取引用的值,实际上是获取了与引用绑定的对象的值。

2、指针

指针(pointer) 是指向另外一种类型的复合类型。

  1. 指针本身就是一个对象,允许对指针赋值和拷贝,可以先后指向几个不同的对象。
  2. 指针无须在定义时赋初值。
int ival = 42;
int *p = &ival;

指针值

  1. 指向一个对象
  2. 指向紧邻对象所占空间的下一个位置
  3. 空指针,意味着指针没有指向任何对象
  4. 无效指针,也就是上述情况之外的其它值

3、const

默认状态下,const 对象仅在文件内有效。

  1. const 的引用: 对常量的引用(reference to const),对常量的引用不能被用作修改它所绑定的对象
const int ci = 1024;
const int &r1 = ci; // 正确:引用及其对应的对象都是常量
r1 = 42;            // 错误:r1 是对常量的引用
int &r2 = ci;       // 错误:试图让一个非常量引用指向一个常量对象
  1. 指向常量的指针(pointer to const): 不能改变其所指对象的值。
const double pi = 3.14;    // pi 是一个常量,它的值不能改变
double *ptr = π         // 错误:ptr 是一个普通指针
const double *cptr = π  // 正确:cptr 可以指向一个双精度常量
*cptr = 42;                // 错误:不能改 *cptr 赋值
  1. 常量指针(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

C/C++ 中的static关键字

7、template

  1. 函数模板(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;
}
  1. 类模板
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;

};

知无涯之C++ typename的起源与用法

8、namespace

命名空间(namespace)为防止名字冲突提供了更加可控的机制

namespace cplusplus_primer {
	class Sales_data { };
	Sales_data operator+(const Sales_data&, const Sales_data&);
	class Query {}
} // 命名空间结束后无须分号

9、参数传递

  1. 传值参数

    当初始化一个非引用类型的变量时,初始值被拷贝给变量,此时对变量的改动不会影响初始值。

  2. 传引用参数

    通过使用引用形参,允许函数改变一个或多个实参的值。

    void reset(int &i) {
    	i = 0;
    }
    
    int j = 42;
    reset(j);  // j 采用传引用方式,它的值被改变
    cout << "j = " << j << endl; // 输出 j = 0
    
  3. 使用引用避免拷贝

    拷贝大的类型对象或者容器对象比较低效,有些类型就不支持拷贝操作,函数只能通过引用形参访问该类型的对象

    bool isShorter(const string &s1, const string &s2) {
    	return s1.size() < s2.size();
    }
    

10、返回类型和 return 语句

  1. 不要返回局部对象的引用或指针

    函数完成后,它所占用的存储空间也随之被释放。因此,函数终止意味着局部变量的引用将指向不再有效的内存局域。

    const string &manip()
    {
    	string ret;
    	if(!ret.empty())
    		return ret;      // 错误:返回局部对象的引用!
    	else
    		return "Empty";  // 错误:"Empty"是一个局部临时辆
    }
    

11、内联函数和 constexpr 函数

  1. 内联函数可避免函数调用的开销

    inline const string &
    shorterString(const string &s1, const string &s2)
    {
    	return s1.size() <= s2.size() ? s1 : s2;
    }
    
  2. 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。