Introduction

按照天问之路学习路线所做的博客记录,后续TinySTL源码和所做笔记也慢慢贴上来以督促自己学习(。-ω-)zzz

C++ Keyword const

函数末尾加const的作用

声明一个成员函数的时候用const关键字是用来说明这个函数是 "只读(read-only)"函数,也就是说明这个函数不会修改任何数据成员(object)。 为了声明一个const成员函数, 把const关键字放在函数括号的后面。声明和定义的时候都应该放const关键字。

任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。

同样,如果把不改变数据成员的函数都加上const关键字进行标识,显然,可提高程序的可读性,一看就知道这个函数的大概功能。

C++ Pointer

const T* & T* const

如果 * 在const之后,则const就修饰的是 *p ,表示指向的内容是const的,而 p 可以改变;

如果 * 在const之前,则const修饰的只是 p ,表示指针是const的,而内容可以改变。

指针数组&数组指针

指针数组

int *pt[3];// == int *(pt[3])	

对于该语句来说,由于C++运算符的优先级中,小于[],所以pt先和*[]结合成为数组,然后再和int 结合形成数组的元素类型为int 类型,最后称该数组为指针数组,该数组的元素是指针

Example

#include<iostream>
#define endl '\n'
using namespace std;

int main()
{
	int arr[2][4] = {{1,2,3,4},{5,6,7,8}};
	int *pt[2];
	for (size_t i = 0; i < 2; ++i)
	{
		pt[i] = arr[i];
	}
    cout << *pt + 1 << endl;//输出arr[0][1]的地址
	cout << *pt[0] + 1 << endl;//输出arr[0][1]的值
//这里如果要输出数组首元素以后的元素只能通过与立即数相加的方式
	return 0;
}

数组指针

int (* pt)[3]; 

对于该语句来说,优先级顺序是*小于[],[]等于(),考虑到结合顺序是从左向右,先是()里的*与pt结合成一个指针,然后(* pt)与[]结合为一个数组,最后称该指针pt为数组指针,该指针指向一个数组

Example

#include<iostream>
#define endl '\n'
using namespace std;

int main()
{
	int arr[2][4] = {{1,2,3,4},{5,6,7,8}};
	int (*ptr)[4] = arr;
	
	cout << arr << endl;
	cout << ptr << endl;
	cout << ptr + 1 << endl;
	cout << *(*ptr + 1) << endl;
	cout << **(ptr + 1) << endl;//这里是二维数组的知识

	return 0;
}

C++ 从函数返回指针

C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为static变量

C++ Reference

关于引用的说明

  1. 引用必须在声明时将其初始化,不能先声明后赋值
  2. 引用更接近const指针,必须在创建时进行初始化,一旦引用和某个变量关联起来,该引用就会一直指向该变量
int num = 1;
int &ptr = num;// <==> int * const ptr = &num;

C++ 把引用作为返回值

通过使用引用来替代指针,会使 C++ 程序更容易阅读和维护

C++ 函数可以返回一个引用,方式与返回一个指针类似

  • 当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边

  • 当返回一个引用时,要注意被引用的对象不能超出作用域,所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用

C++ Class

template<class T>
void function(T v1,T v2)
{
    T v1 = T();
}

//For int, it would be:
//int tSum = int();

这是调用其构造函数来初始化该变量

使用初始化列表来初始化字段

假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,只需要在不同的字段使用逗号进行分隔

C++ 初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序

namespace C
{
    double X,Y,Z;
    void C( double a, double b, double c): X(a), Y(b), Z(c)
	{	//若a b c没有赋值,可先在定义时赋初值	double a = 1.5
        //给X Y Z赋值也可用常量赋值	X(1.1),Y(1.2),Z(1.3)
  		....
	}
}

析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行

析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数

析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源

友元函数friend

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员

尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数

友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员

如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend

可以直接调用友元函数,不需要通过对象或指针

内联函数inline

引入内联函数的目的是为了解决程序中函数调用的效率问题

程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的i节省。所以内联函数一般都是1-5行的小函数。

在使用内联函数时要留神:

  • 在内联函数内不允许使用循环语句和开关语句
  • 内联函数的定义必须出现在内联函数第一次调用之前
  • 类结构中所在的类说明内部定义的函数是内联函数

C++ Overload Operator

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的

与其他函数一样,重载运算符有一个返回类型和一个参数列表

ReturnType operator+(const &valuable)

Overloading the << Operator for Your Own Classes

C++ 能够使用流提取运算符 >> 和流插入运算符 << 来输入和输出内置的数据类型

您可以重载流提取运算符和流插入运算符来操作对象等用户自定义的数据类型

习惯上人们是使用 cin>>cout<< 的,得使用友元函数来重载运算符,这样我们就能不用创建对象而直接调用函数

如果使用成员函数来重载会出现 d1<<cout; 这种不自然的代码

class ClassName
{
	public:
    friend ostream &operator<<(ostream &os, const ClassName& instance)
      { 
         //os << "user-defined format";
         return os;            
      } 
};

C++ Namespace

把namespace看作一个特殊库,若要调用其中的函数或变量,需要在前面加上命名空间的名称

namespace spacename{
    int code;
    //code
}
std::cout << spacename::code << endl;

使用using指令后,调用命名空间内的变量或函数即可不用加上命名空间名称,你也可以指定命名空间中的特定项目,这样调用该特定项目时不用加上名称,但使用该空间内的其他项目时仍需加上名称

命名空间可以嵌套使用

若a为全局变量,使用格式可为

using namespace std;
std::cout << ::a << endl;

C++ Template

如果觉得自己对模板不太懂,就看看下面

推荐基础先看一,然后二三,三是写得很全的

C++ 模板详解

An Idiot’s Guide to C++ Templates - Part 1

CppTemplateTutorial

C++ Class Template Explicit Specialization

Explicit (full) template specialization

Allows customizing the template code for a given set of template arguments

  • Explicit specialization may be declared in any scope where its primary template may be defined (which may be different from the scope where the primary template is defined; such as with out-of-class specialization of a member template) . Explicit specialization has to appear after the non-specialized template declaration
  • Specialization must be declared before the first use that would cause implicit instantiation, in every translation unit where such use occurs
  • A template specialization that was declared but not defined can be used just like any other incomplete type(e.g. pointers and references to it may be used)

重点了解Members of specializations

When defining a member of an explicitly specialized class template outside the body of the class, the syntax template<> is not used, except if it’s a member of an explicitly specialized member class template, which is specialized as a class template, because otherwise, the syntax would require such definition to begin with template<parameters> required by the nested template

template<typename T>
struct A
{
    struct B {};      // member class 
 
    template<class U> // member class template
    struct C {};
};
 
template<> // specialization
struct A<int> 
{
    void f(int); // member function of a specialization
};
// template<> not used for a member of a specialization
void A<int>::f(int) { /* ... */ }
 
template<> // specialization of a member class
struct A<char>::B
{
    void f();
};
// template<> not used for a member of a specialized member class either
void A<char>::B::f() { /* ... */ }
 
template<> // specialization of a member class template
template<class U>
struct A<char>::C
{
    void f();
};
 
// template<> is used when defining a member of an explicitly
// specialized member class template specialized as a class template
template<>
template<class U>
void A<char>::C<U>::f() { /* ... */ }

About C++ header file

Include Guards

#ifndef XXX 防止重复包含

细说C++头文件

Header files (C++)

#ifndef XXXXXXXXXXX_H_
#define XXXXXXXXXXX_H_
// header file content
#endif

如果未宏定义,先定义,并编译程序段;如果已进行宏定义,则该段被忽略

这样的写法来保护防止重复。这个宏就是用ifndef来判断有没有定义过XXXXXXXXXXX_H_,如果没有就定义XXXXXXXXXXX_H_,然后再接具体内容,否则发现定义过的话,相当于本编译单元其它地方已经拷贝过这个XXXXXXXXXX.h的内容,那就可以直接跳过,没必要再编译了。

#pragma once

在想要保护的文件开头写入

#pragma once

关于两者区别

#pragma指令与#ifndef指令

#ifndef方式是C/C++语言的标准支持,也是比较常用的方式,#ifndef的方式依赖于自定义的宏名(例中的_CODE_BLOCK)不能冲突,它不光可以保证同一份文件不会被包含两次,也能够保证不同文件完全相同的内容不会被包含两次。但,同样的,如果自定义的宏名不小心“重名”了,两份不同的文件使用同一个宏名进行#ifndef,那么会导致编译器找不到声明的情况(被编译器判定为重定义而屏蔽了)。

此外,由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,#ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式(Visual Studio 2017新建头文件会自带#pragma once指令)。

#pragma once一般由编译器提供保证:同一个文件不会被包含多次。这里所说的”同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。无法对一个头文件中的一段代码作#pragma once声明,而只能针对文件。此方式不会出现宏名碰撞引发的奇怪问题,大型项目的编译速度也因此提供了一些。缺点是如果某个头文件有多份拷贝,此方法不能保证它们不被重复包含。在C/C++中,#pragma once是一个非标准但是被广泛支持的方式。

#pragma once方式产生于#ifndef之后。#ifndef方式受C/C++语言标准的支持,不受编译器的任何限制;而#pragma once方式有些编译器不支持(较老编译器不支持,如GCC 3.4版本之前不支持#pragmaonce),兼容性不够好。#ifndef可以针对一个文件中的部分代码,而#pragma once只能针对整个文件。相对而言,#ifndef更加灵活,兼容性好,#pragma once操作简单,效率高。

Q.E.D.


怀着一颗虔诚谦虚的心学习