C++ primer 5th 学习笔记

void*是一种特殊的指针类型,可用于存放任意对象的地址,但不能直接操作该指针所指的对象。

int i = 42; void* p = &i;
​
cin >> line;//会忽略空白符
getline(cin, line);//可以保留输入时的空白符,遇回车终止
​
string s1 = "world";
string s2 = "hello" + "," + s1;//错误必须确保每个加法运算符的两侧运算对象至少有一个是string
string s2 = s1+ "hello" + ",";//正确,能自动推导
​
ispunct(c) 当c是标点符号时为真//punctuation--标点符号
toupper(c) tolower(c) 转换大小写   
isspace(c) 当c是空白时为真
    
int inf = 1e9;//表示无穷大,1*10^9

范围for语句(遍历给定序列中的每个元素并对序列中的每个值执行某种操作)

for (declaration : expression)

statement

cbegin cend
    auto it = v.cbegin();//it的类型是vector<int>::const_iterator,无论vector对象本身是否是常量,返回值都是const_iterator
​

auto 与 decltype 区别

p105 p63

int i = 42;
decltype((i)) d = i;//d是int&类型,注意declype(())双括号
    
int ia[] = {0,1,2,3,4,5};
auto ia2(ia);//ia2是一个整型指针,指向假的第一个元素
​
decltype(ia) ia3 = {0,1,2,3,4,5};//ia3是一个含有10个整数的数组

下标和指针

int ia = {1,2,3,4,5};
int i = ia[2];//一、ia转换成指向数组首元素的指针
//二、ia[2]得到(ia + 2)所指的元素
同理:
int *p = &a[2];//p指向索引为2的元素
int j = p[1];//p[1]等价于*(p+1),就是ia[3]表示的那个元素
​
​
char a[5] = "hello";//错误"const char [6]" 类型的值不能用于初始化 "char [5]" 类型的实体
//"hello"以空字符'\0'结束实际上有6个元素
char ca[] = {'C','+','+'};//不以空字符结束
cout << strlen(ca) << endl;//严重错误:ca没有以空字符结束
//在使用C风格字符串的函数时:strcmp(p1,p2);strcat(p1,p2);strcpy(p1,p2)均需要注意
​
string s = "hello";
const char* str = s.c_str();//c_str函数返回结果是一个指针,该指针指向一个以空字符结束的字符数组,而这个数组所存的数据恰好与那个string对象的一样。
cout << str[1] << endl;

int * p[] 和 int ( * p)[] 区别

[]优先级高于 * ,所以p先是一个数组,然后数组每个元素的类型是int * , int * p[]是一个(int *)类型的指针数组,p本质上是一个数组;

()优先级高于[],所以p先与 * 结合,是一个指针,指针的类型是一个int[]数组,int ( * p)[]是一个数组指针,p本质上是一个指针。

int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12};
int(* p)[4] = a;
cout << *p[1] << endl;//第二(1+1)行的第一个元素即为5
cout << (*p)[1] << endl;//第一行的第二(1+1)个元素即为2

类型别名

//两种方法
typedef double wages;//wages是double的同义词
using wages = double;

左值与右值

如果表达式的求值结果是左值,decltype作用域该表达式(不是变量)得到一个引用类型

decltype(*p)的结果是int&

p的类型是int*, *p解引用

decltype(&p)//因为取值运算符生成右值,所以结果是int**

vector与矩阵

vector<vector<int>> arr(3);//()
    arr[1].resize(1);//resize必要
    arr[1][0] = 2;

位运算符

P136

~ << >> & | ^//^是异或 

数组转换成指针

在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针。

但是当数组被用作decltype关键字的参数,或者作为取地址符&、sizeof及typeid等运算符的运算对象时,上述转换不会发生。

  • '0'的ASC||值是48,而后依次是'1'到'9'。 这样char型减去48正好就是它对应的int值。

memset函数用法

第三章

string s;//输入hello world
    //cin >> s;//只显示hello
    getline(cin, s);//显示完整hello world
    cout << s;
unsigned cnt = 42;//不是常量表达式
int ia[cnt];//报错,数组维度要求常量表达式
constexpr unsigned sz = 42;//常量表达式
//strlen、strcpy、strcat
char str1[] = "hello";
    char str2[] = "world";
    char* str3 = new char[strlen(str1) + strlen(str2)];//new int[10]和new int(10)的区别
    strcpy(str3, str1);//将str2拷贝给str3后,返回str3
    strcat(str3, str2);//将str1附加到str3后,返回str3
    cout << str3 << endl;

第四章11.7

const char* cp = "hello";
​
    char c[6] = { 'h','e','l','l','o','\0'};
    //char c[6] = "hello";//算上默认的'\0'实际6个
    cout << c << endl;//输出hello
​
    int arr[] = { 1,2,3 };
    cout << arr << endl;//输出00B3F81C
​
    cout << *cp << endl;//输出h
    cout << cp << endl;//输出hello

cout << cp;中的cp如果是字符型指针,就被解释为“从这个指针的值(地址)开始,一个字节接一个字节地把其内容按ASCII码对应的字符输出到终端,直到遇到'\0'停止,且不输出'\0'“。

  • · 运算符优先级高于*//p147运算符优先表

    *iter.empty()//错误
        (*iter).empty()//正确

  • strtol函数、itoa函数

如果一台机器上 int 占32位、char 占8位,用的是 Latin-1 字符集,其中字符 'q' 的二进制形式是 01110001,那么表达式 ~'q' << 6 的值是什么?

解:

在位运算符中,运算符 ~ 的优先级高于 <<,因此先对 q 按位求反,因为位运算符的运算对象应该是整数类型,所以字符 q 首先转换为整数类型。char 占8位而 int 占32位,所以字符 q 转换后得到 00000000 00000000 00000000 01110001,按位求反得到 11111111 11111111 11111111 10001110;接着执行移位操作,得到 11111111 11111111 11100011 10000000。

C++规定整数按照其补码形式存储,对上式求补,得到 10000000 00000000 00011100 100000000,即最终结果的二进制形式,转换成十进制形式是-7296。

第六章11.9

  • 允许为一个常量引用绑定非常量的对象、字面值、甚至是个一般表达式

  • 数组不是类类型,因此begin()、end()函数不是成员函数,正确的使用形式是将数组作为它们的参数

int *matrix[10];//10个指针构成的数组
int (*matrix)[10];//指向含有10个整数的数组的指针
​
void print(int matrix[][10],int rowSize){}//实际上形参是指向含有10个整数的数组的指针
​
int main(int argc,char *argv[]){}
int main(int argc,char **argv){}

第一个形参argc表示数组中字符串的数量,第二个形参argv是一个数组,它的元素是指向C风格字符串的指针,argv的第一个元素指向程序的名字或者一个空字符串...P197

cout << argv;//地址
cout << argv[0];//程序名
  • initializer_list<T> lst{a,b,c...};//函数的实参数量未知但是全部实参的类型都相同

//函数返回数组指针
//方式一 typedef
//typedef int arrT[10];
using arrT = int[10];//arrT是一个类型别名,表示的类型是含有10个整数的数组
arrT* func1(int i) {}//无法返回数组,不加*报错,所以将返回类型定义为数组的指针
//方式二 
//int *fuc2(int i)[10]{}//不加括号报错--不允许使用返回数组(返回10个指针的数组)的函数
int(*fuc2(int i))[10]{}//P205
//方式三 尾置返回类型
auto func3(int i)->int(*)[10]{}//不加括号报错--不允许使用返回数组(返回10个指针的数组)的函数
//方式四 decltype
int arr[10];
decltype(arr)* func4(int i) {}//decltype并不负责把数组类型转换成对应的指针,
//所以decltype的结果是个数组,要想表示返回指针还必须在函数声明时加一个*符号
// const_cast<>()用于去掉或赋予const性质,()不能少
string& shorterString(string& s1, string& s2)
{
    auto& r = s1 > s2 ? const_cast<const string&>(s1) : const_cast<const string&>(s2);
    return const_cast<string&>(r);
}

第五章11.15 第六章11.16

unsigned int ffCnt = 0, flCnt = 0, fiCnt = 0;
    char ch, prech = '\0';
    cout << "请输入一段文本:" << endl;
    while (cin >> ch)
    {
        bool bl = true;
        if (prech == 'f')
        {
            switch (ch)
            {
            case 'f': 
                ++ffCnt;
                bl = false;
                break;
            case 'l':
                ++flCnt;
                break;
            case 'i':
                ++fiCnt;
                break;
            }
        }
        if (!bl)
            prech = '\0';
        else
            prech = ch;
    }
    cout << "Number of ff: \t" << ffCnt << '\n'
        << "Number of fl: \t" << flCnt << '\n'
        << "Number of fi: \t" << fiCnt << endl;
//P164/P166
try{
    xxxxxxxxxx;
    throw runtime_error("什么错误");
}catch(runtime_error err){
    //catch到错误后执行的操作
    cout<<err.what();//显示什么错误
}

如果把一个本来应该是常量引用的形参设成了普通引用类型,有可能遇到几个问题:

  • 一是容易给使用者一种误导,即程序允许修改实参的内容;

  • 二是限制了该函数所能接受的实参类型,无法把 const 对象、字面值常量或者需要类型转换的对象传递给普通的引用形参。

int main(int argc,char *argv[])//等同于
    int main(int argc,char **argv)//argument(实参) counter 和 argument value 
第一个形参argc表示数组中字符串的数量,第二个形参argv是一个数组,它的元素是指向C风格字符串的指针
  • 如果函数的实参数量未知但是全部实参的类型都相同,我们可以使用initializer_list类型的形参//P197

string (&func())[10];
func() 表示调用 func 函数无须任何实参,(&func()) 表示函数的返回结果是一个引用,(&func())[10] 表示引用的对象是一个维度为10的数组,string (&func())[10] 表示数组的元素是 string 对象。
  • C++规定一旦某个形参被赋予了默认实参,则它后面的所有形参都必须有默认实参。

class Y;
class X { Y* ptr; };//可以定义指向不完全类型的指针,但是无法创建不完全类型的对象
class Y { X arg; };
  • 给形参提供了默认实参的构造函数也具备默认构造函数的功能

  • 除了静态常量成员之外,其他静态成员不能在类的内部初始化

第七章11.17 第八章 11.18

  • #include <fstream> //file stream

  #include <sstream>
  ostringstream msg;
  msg << "C++ Primer 第五版" << endl;
  istringstream in(msg.str());//strm.str()返回strm所保存的string的拷贝
  //istringstream in(msg);//错误IO对象无拷贝或赋值

第九章 11.19

  • 与 vector 和 deque 不同,list 的迭代器不支持 < 运算,只支持递增、递减、= 以及 != 运算。

原因在于这几种数据结构实现上的不同:vector 和 deque 将元素在内存中连续保存,而 list 则是将元素以链表方式存储,因此前者可以方便地实现迭代器的大小比较(类似指针的大小比较)来体现元素的前后关系。而在 list 中,两个指针的大小关系与它们指向的元素的前后关系并不一定是吻合的,实现 < 运算将会非常困难和低效。

vector<int>(lst.begin(), lst.end()) == v ?//正确
vector<int> v(lst.begin(), lst.end()) == v//错误
  • forward_list是单向的,不能获取一个元素的前驱(prevcurr):++curr;//正确 --curr;//错误

  • listforward_list 与其他容器的一个不同是,迭代器不支持加减运算,究其原因,链表中元素并非在内存中连续存储,因此无法通过地址的加减在元素间远距离移动。因此,应多次调用++来实现与迭代器加法相同的效果。

vector<char> vc = { 'H', 'E', 'L', 'L', 'O' };//从一个vector<char>初始化一个string
string s(vc.data(), vc.size());//data函数

error

P324 练习9.43
    /*不减1会错因为*is1 == *is2尝试对oldVal.end()解引用*/
while ((*is1 == *is2) && (is2 != oldVal.end() - 1) && (is1 != s1.end()))
        {
            ++is1;
            ++is2;  
        }
​
//不管insert还是erase,都是对闭开区间[)的元素进行处理
string test = "hello";
string exam = "world";
test.erase(test.begin(), --test.end());//除开o
test.insert(test.begin(), exam.begin(), --exam.end());//不包含d
//find函数的应用,string::npos
int p = 0;
    while ((p = s.find(oldVal, p)) != string::npos)
    {
        s.replace(p, oldVal.size(), newVal);
        p += newVal.size();
    }

第十章11.20 第十一章11.21

  • 谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。

标准库定义了名为partition的算法,它接受一个谓词,对容器内容进行划分,使得谓词为true的值会排在容器的前半部分,而使谓词为false的值会排在后半部分。
  • lambda 可以理解为是一个未命名的内联函数 {}

  • 值捕获(=)想修改变量的话,可以在参数列表后加mutable进行修改。引用捕获(&)也必须是非const的变量才可以进行修改

接受一个可调用对象的算法:sort、stable_sort、find_if、for_each、partition、count_if

  • bind要用到占位符的话要using namespace std::placeholders;

unique_copy(words.begin(), words.end(), inserter(lst,lst.begin()));
unique_copy(words.begin(), words.end(), back_inserter(lst));
unique_copy(words.begin(), words.end(), front_inserter(lst));
ifstream ifs("单词.txt");//ifstream
istream_iterator<string> in_iter(ifs), eof;//istream_iterator
vector<string> vs;
copy(in_iter, eof, back_inserter(vs));//back_inserter
ostream_iterator<string> out_iter(cout, " ");//ostream_iterator
for (auto s : vs)
    *out_iter++ = s;

对于一个绑定到流的迭代器,一旦关联的流遇到文件尾或遇到IO错误,迭代器的值就与尾后迭代器相等。空的istream_iterator可以当做尾后迭代器。

  • 递减排序除了使用谓词外,还可以使用反向迭代器sort(vec.rbegin(),vec.rend());

  • 通用版本的sort要求随机访问迭代器,因此不能用于list和forward_list,因为这两个类型分别提供双向迭代器和前向迭代器。

list版本的unique函数作用等同于vector的unique加erase

C++getline函数需要包含istream和string头文件。
C++中有两个getline函数,一个是在string头文件中,定义的是一个全局的函数,函数声明是:
istream& getline ( istream& is, string& str, char delim );
istream& getline ( istream& is, string& str );
另一个则是istream的成员函数,函数声明是:
istream& getline (char* s, streamsize n );
istream& getline (char* s, streamsize n, char delim );
注意第二个getline是将读取的字符串存储在char数组中而不可以将该参数声明为string类型,因为C++编译器无法执行此默认转换。
  • 要想声明一个函数指针,只需要用指针代替函数名即可

//pair的三种创建方法
vec.push_back(make_pair(str, i));
vec.push_back({ str, i });
vec.push_back(pair<string, int>(str, i)); 
  • 对于不包含重复关键词的容器,添加单一元素的insert和emplace版本返回一个pair,告诉我们插入操作是否成功。pair的first成员是一个迭代器,指向具有给定关键字的元素;second成员是一个bool值,指出元素是插入成功还是已经存在于容器中

  • 对一个map使用下标操作,其行为与数组或vector上的下标操作很不相同:使用一个不在容器中的关键词作为下标,会添加一个具有此关键字的元素到map中

第十二章11.23

  • 一个返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针

shared_ptr<int> p1 = new int(1024);//wrong
shared_ptr<int> p2(new int(1024));//correct 
  • 传递给delete的指针必须指向动态分配的内存或者是一个空指针

  • strlen()返回字符串长度,但是不包括字符串末尾的空字符,所以+1

allocator 将内存分配和对象构造分离开来

allocate->construct->destory(p)->deallocate(p,n)

uninitialized_copy() 作用于未初始化的空间

第十三章11.24

当调用push或insert成员函数时,我们将元素类型的对象传递给它们,这些对象被拷贝到容器中。当我们调用一个emplace成员函数时,则是将参数传递给元素类型的构造函数。emplace成员使用这些参数在容器管理的内存空间中直接构造元素。

  • rhs--right hand side; lhs--left hand side

一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表达的是对象的值

第十四章

P493 当我们把运算符定义成成员函数时,它的左侧运算对象必须是运算符所属类的一个对象

//weak_ptr
w.use_count() 与w共享对象的shared——ptr的数量
w.expired()   若w.use_count()为0,返回true,否则返回false
w.lock()      如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr;

定义了A类的时候,需要定义一个B类来完善A的功能,A类当中有 返回类型为B的函数 ,定义A之前,先声明了B,且B作为A的友元,需要访问A的私有成员。

因为B的定义在A的后面,所以在具体定义fun1这个函数时,B只有声明而没有具体的定义。导致编译的时候会报错。将fun1放在B的定义之后定义就可以了,而只在A当中先声明。

注:类没有定义之前但是有声明的时候,可以声明指向它的指针。而不允许对未定义的类型的引用。


HasPtr& HasPtr::operator=(const HasPtr& hp)
{
    string* p = new string(*hp.ps);//防范自赋值
    delete ps;//释放旧内存
    ps = p;
    //ps = new string(*hp.ps);
    i = hp.i;
    return *this;
}

内置类型的指针在离开作用域时,本身会被销毁,但是其指向的内存空间什么都不会发生,必须以显式的delete进行释放空间

  • 对于那些与重排元素顺序的算法(如sort、unique)一起使用的类,定义swap是非常重要的。这类算法在需要交换两个元素时会调用swap。

  • 新标准库move函数可以显式地将一个左值转换到对应的右值引用类型//使用move的代码应该使用std::move,避免潜在的名字冲突


C++语言中有几种可调用的对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类

function<int(int,int)>,在这里我们声明了一个function类型,它可以表示接受两个int,返回一个int的可调用对象


输入输出运算符必须是非成员函数//拷贝构造函数的参数为什么必须是const引用?---不要套娃!

如果类定义了调用运算符,则该类的对象称为函数对象。因为可以调用这种对象,所以我们说这些对象的“行为像函数一样”。调用对象实际上是在运行重载的调用运算符。

  • 标准库<algorithm>中的transform函数的作用是:将某操作应用于指定范围的每个元素

类型转换运算符既没有显式的返回类型,也没有形参,而且必须定义成类的成员函数。类型转换运算符通常不应该改变待转换对象的内容,因此,类型转换运算符一般被定义成const成员。

operator type() const

  • 向bool的类型转换通常用在条件部分,因此operator bool一般定义成explicit的

第十五章11.29

struct(public)和class(private)唯一的差别就是默认成员访问说明符及默认派生访问说明符

  • 不存在从基类向派生类的隐式类型转换...//dynamic_cast/static_cast

  • 如果一个类需要析构函数,那么它也同样需要拷贝和赋值操作//P447

  • 通常情况下,using声明语句只是令某个名字在当前作用域可见

用来组织一个容器中元素的操作的类型也是该容器类型的一部分,自定义的操作类型必须在尖括号中紧跟着元素类型给出//P379

multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn)
//decltype(compareIsbn)* 函数指针类型
multiset的upper_bound函数:返回一个迭代器,该迭代器指向所有与iter关键字相等的元素中最后一个元素的下一位置
multiset的count成员:统计在multiset中有多少元素的键值相同
​
  • 纯虚函数(pure virtual)在函数体的位置(即在声明语句的分号之前)书写=0

#include <algorithm>
set_intersection(right.begin(), right.end(), left.begin(), left.end(), inserter(*ret_lines, ret_lines->begin()));//set_intersection、inserter

与其他下标运算符不同的是,如果关键字并不在map中,会为它创建一个元素并插入到map中,关联值将进行值初始化。由于下标运算符可能插入一个新元素,我们只可以对非const的map使用下标操作。

派生类需要使用基类的构造函数来初始化它的基类部分—每个类控制自己的初始化过程,派生类的构造函数同样是通过构造函数初始化列表将实参传递给基类的构造函数,再由基类的构造函数,完成其基类部分的初始化

派生类的声明只能包含类名,不能包含它的派生列表:一条声明语句的目的是令程序知晓某个名字的存在以及该名字表示一个怎样的实体,派生列表以及其定义的相关细节必须与类的主体一起出现

  • 动态绑定只有当我们通过指针或引用调用虚函数时才会发生

  • 含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类,不能创建抽象基类的对象

  • 派生类的成员和友元只能访问派生类对象中的基类部分的受保护成员;对于普通的基类对象中的成员不具有特殊的访问权限。P543

static bool compare(const shared_ptr<Quote>& lhs, const shared_ptr<Quote>& rhs)
{
    return lhs->isbn() < rhs->isbn();
}
//multiset保存多个报价,按照compare成员排序
multiset<shared_ptr<Quote>, decltype(compare)*> items{compare};

upper_bound multiset.count

第十六章12.2

strm.str() 返回strm所保存的string的拷贝//stringstream特有的操作

  • 如果一个函数参数是指向模板类型参数的右值引用(如T&&),它对应的实参的const属性和左值/右值属性将得到保持

定义在类内部的成员函数是自动inline的

//类内声明 类外定义 练习16.15
template<unsigned h,unsigned w>
friend ostream& operator<<(ostream& os, const Screen<h,w>& c);
friend istream& operator>>< /*H, W*/ >(istream& is, Screen& c)  ;//一定要有<>
https://www.zhihu.com/question/44130347?sort=created
空<>也表示希望使用默认实参/类型
template<> 指出我们正在定义一个特例化版本

void *是一种无类型指针,任何类型指针都可以转为void *

function<void(T*)> deleter;//接受T*,返回void//P512

C++ 类成员访问运算符 -> 重载 运算符 -> 必须是一个成员函数。如果使用了 -> 运算符,返回类型必须是指针或者是类的对象。

p->m 可以被解释为 (p.operator->())->m

T& operator  *() const { return *ptr; }
T* operator ->() const { return &this->operator *(); }
//练习16.40 尾置返回类型与类型转换
template <typename It>
//解引用运算符返回一个左值,因此通过decltype推断的类型为beg表示的元素的类型的引用
auto fcn1(It beg, It end) -> decltype(*beg)
{
    return *beg;
}
template <typename It>
//标准类型转换模板 remove_reference
auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
​
template <typename It>
//+0的操作起到的是生成临时变量实现脱引用。所以要求序列中的元素必须能+0,生成临时变量
auto fcn3(It beg, It end) -> decltype(*beg + 0)

转发 某些函数需要将一个或多个实参连同类型不变地转发给其他函数。在此情况下,我们需要保持被转发实参的所有性质,包括实参类型是否是const的以及实参是左值还是右值。// T&& forward<T>()

  • 特例化的本质就是实例化一个模板,而非重载它。因此,特例化不影响函数匹配。

int strcmp(const char* str1,const char* str2);
如果返回值 < 0,则表示 str1 小于 str2。
如果返回值 > 0,则表示 str2 小于 str1。
如果返回值 = 0,则表示 str1 等于 str2。

第十七章12.5

equal_range函数#include <algorithm>
此函数接受一个关键字,返回一个迭代器pair。若关键字存在,则第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置。
auto found = equal_range(it->cbegin(), it->cend(), book, compareIsbn);
默认情况下,equal_range使用<运算符来比较元素。//因为用的是二分查找
accumulate函数#include <numeric>
accumulate函数接受三个参数,前两个指出了需要求和的元素的范围,第三个参数是和的初值。
第三个参数的类型决定了函数中使用 哪个加法运算符 以及返回值的类型
  • 在字符前放置一个反斜线 \ \ 来去掉其特殊含义

  • \ d{n}表示一个n个数字的序列

#include <iomanip> manipulate--操纵

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值