【c++学习笔记】

- struck 和 class 的区别
-C++中的 struct 和 class 基本是通用的,唯有几个细节不同:
1:使用 class 时,类中的成员默认都是 private属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。(最本质的区别)
2:class 继承默认是 private 继承,而 struct 继承默认是 public 继承(《C++继承与派生》一章会讲解继承)。
3:class
可以使用模板,而 struct 不能。

  • IO类型的参数只能是引用,因为IO类属于不能被拷贝的类型

  • 构造函数不能被声明成const,当我们创建类的一个const对象的时候,直到构造函数完成初始化过程,对象才真正的取得其“常量”的属性。因此,构造函数在const对象的构造过程中可以向其写值。

  • ** 默认构造函数**如果我们的类没有显式的定义构造函数,那么编译器会为我们隐式的定义一个默认构造函数。按照如下规则初始化类的数据成员:如果存在类内的初始值,用其初始化成员,否则,默认初始化该成员(例如string类型默认初始化为空字符串)。只有当类没有任何构造函数时,编译器才会自动地生成默认构造函数。

  • 封装地含义,有什么用处:封装实现了类的接口和实现的分离,隐藏了类的实现细节,用户只能接触到类的接口。
    优点:
    隐藏类的实现细节;
    让使用者只能通过程序规定的方法来访问数据;
    可以方便的加入存取控制语句,限制不合理操作;
    类自身的安全性提升,只能被访问不能被修改;
    类的细节可以随时改变,不需要修改用户级别的代码;

  • 友元函数的声明只能出现在类的内部,但是类内的位置不限,因为友元函数不是类的成员函数,也不受他所在区域访问控制级别的约束。

  • 友元函数的声明,仅仅制定了访问的权限。而并不是真正意义上的一个函数声明,必须在友元声明之外在专门对函数进行声明。为了使友元函数对类的用户可见,我们通常把友元的声明(真正的声明)与类本身放置在同一个头文件中。

  • 友元函数的优点和缺点
    友元—类允许其他类或者函数访问其非共有成员,只要在本类内,加一条类前或者函数前有friend关键字(最前方)的声明即可。最好在类的开始或结尾集中声明友元。
    优点:可以灵活地实现需要访问若干类的私有或受保护成员才能完成的任务,便于与其他不支持类的语言进行混合编程;通过使用友元函数重载可以更自然第使用C++语言的I/O流库。
    缺点:一个类将对非公有成员的访问权授予其他的函数或类,会破坏该类的封装性,降低该类的可靠性和可维护性。

  • 虽然我们无需在声明和定义的地方同时说明inline,但是这么做是合法的,不过最好只在类外部定义的地方说明inline,这样可以使类更容易理解一些。

  • 可变数据成员 mutable

class Screen{
	public: 
		void some_member() const;
	private:
		mutable int access_ctr;//即使在一个const对象中也能被修改
}void Screen::some_member()const{
++access_ctr; // 
}

尽管some_member是一个const成员函数,但是他仍然能够改变access_ctr的值,因为他是一个可变成员mutable,因此任何成员函数,不管什么样的情况,他都可以改变。

  • 一个类中,只有内置类型和string类型可以依赖于拷贝和赋值的默认版本。只要类中有其他的类型,(例如指针)就不能直接使用类默认拷贝
  • 一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用
inline Screen& Screen::set(int t ,int col,char ch){
	contents[r*width + col] = ch;
	return *this;
}

set成员函数返回值是调用set对象的引用。
返回引用的函数是左值的(可以修改),意味着这些函数返回的对象本身而不是“对象的副本”。如果我们把一系列这样的操作连在一起的话:

myScreen.move(4,0).set('#');

这些操作将在同一个对象上执行,在上面的表达式中,我们首先移动myScreen内的光标,然后设置contents成员。也就是说上面的一个句子等价于

myScreen.move(4,0);
myScreen.set('#');

这两个句子
如果!!我们令move和set返回的是Screen而不是Screen& 则上述语句的行为完全不同。
他们等价于

Screen temp = myScreen.move(4,0); 
temp.set(' #');

假如我们当时定义的返回类型不是引用,则move的返回值将是*this的副本,因此调用set只能改变临时副本,改变不了myScreen的值。

  • const 可以重载。意思就是如果两个函数都一样,一个有const,一个没有,那么他们也可以重载。例如:
Screen &display(...){return *this;}  //返回的是非常量引用
const Screen &display(...){return *this;} // 返回的是常量引用

Screen myA(5,3);
const Screen myB(5,4);
myA.display(); 调用第一个函数,非常量版本
myB.display(); 调用第二个函数,常量版本

  • 友元可以是非成员函数其他类其他类的成员函数。友元不具有传递性,也就是说如果A是B的友元,B是C 的友元,那么A并不能理所当然的具有C的访问特权。
  • 尽管重载函数的名字相同,但他们仍然是不同的函数,因此如果一个类想把一组重载函数声明成他的友元,他需要对这组函数中的每一个分别声明。
  • 注意!友元函数的声明只是说明他是我的朋友,影响访问权限。除此之外没有任何作用,他不是普通意义上的声明。需要友普通的声明才能使用该函数
  • 编译器在类中和不是类中,寻找与所用名字最匹配的声明的过程不同:
    一般规则为:
    1.在名字所在块中寻找声明语句,只考虑在名字使用之前出现的声明
    2.如果没找到,继续查找外层作用域。
    3.如果最终没有匹配到,则程序报错。
    在定义在类内部的成员函数来说,类的定义分两步:
    1.编译成员的声明
    2编译器处理完类中全部声明后,才会处理成员函数的定义
    所以,在类中,即便是public在private的上面,也可以在public函数中调用private的成员变量,因为类中的声明以及全部处理完了。
  • 如果函数的成员变量中有 const 或者引用时,只能用构造函数初始值列表去初始化他们,而不能在构造函数中赋值。

例如:

class ConstRef{
	int i;
	const int ci;
	int &ri;
};
//下面是错误的
ConstRef::ConstRef(int ii ){
	//赋值
	i = ii;	//正确
	ci = ii; // 错误,不能给const赋值
	ri = i; // 错误,ri没有被初始化
}
//下面是正确的
ConstRef::ConstRef(int ii )i(ii),ci(ii),ri(i){ }

建议养成使用构造函数初始值的习惯(后者)

  • 成员初始化顺序与他们在类中出现的顺序一致,第一个成员先被初始化,然后第二个,以此类推。**与构造函数初始值列表的前后位置关系无关!**最好令构造函数初始值的顺序与成员声明的顺序保持一致,如果可能的话,尽量避免使用某些成员初始化其他成员。

  • 什么是默认构造函数?
    默认构造函数是可以不用实参进行调用的构造函数,它包括了以下两种情况:

没有带明显形参的构造函数
提供了默认实参的构造函数。
类设计者可以自己写一个默认构造函数。编译器帮我们写的默认构造函数,称为“合成的默认构造函数”。

强调“没有带明显形参”的原因是,编译器总是会为我们的构造函数形参表插入一个隐含的this指针,所以”本质上”是没有不带形参的构造函数的,只有不带明显形参的构造函数,它就是默认构造函数。

默认构造函数什么时候被调用?
如果定义一个对象时没有提供初始化式,就使用默认构造函数。

例如:

class A
{
public:
    A(bool _isTrue= true, int _num=10){ isTrue = isTrue; num = _num; }; //默认构造函数
    bool isTrue;
    int num;

};
int main()
{
    A a; //调用类A的默认

也就是说,我在给某个类创造一个对象的时候,如果可以不用加参数,那调用的就是默认构造函数

  • 挖个坑,隐式的类类型转换,没看懂什么意思 c++prime263页

  • **字面值:**是一个不能改变的值,如数字、字符、字符串等。单引号内的是字符字面值,双引号内的是字符串字面值。
    字面值类型(literal type):算数类型、引用和指针等。
    常量表达式(const experssion):是指(1)值不会改变 并且
    (2)在编译过程就能得到计算结果的表达式。字面量属于常量表达式,用常量表达式初始化的const对象也是常量表达式

    一个对象(或表达式)是不是常量表达式由它的数据类型初始值共同决定。


const int a =1;		//常量表达式
cosnt int b=a+1;	//常量表达式
int c=2;		//初始值是字面值常量,但c数据类型是普通int。
const int d=fun();	//fun()值要在运行时得到,d不是字面值常量。
  • constexpr变量。C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的是否是一个常量表达式。
    声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。这里也不太明白啥意思,以后查一下。

  • constexpr函数是只能用于常量表达式的函数,遵循以下约定:
    1 函数的返回类型,以及,所有的形参的类型,都得是字面值。
    2函数体中有且只有一个return语句。

  • 类的静态成员static
    1.可以使public也可以是private。可以是常量、引用、指针、类、等类型。
    2.类的静态成员存在与任何对象之外,不包含this指针,不能声明成const,不能在其内部使用this指针。
    3 虽然不属于某个对象, 但是仍然可以用该类的对象、指针、引用来访问。即每个对象都能访问到该静态成员。
    4 在类内外都能定义,不能重复static关键词,该关键词只能在类的内部。
    5 静态成员不是由类的构造函数初始话,必须在类的外部定义和初始化。
    6 静态成员也能够访问类的私有成员。
    7 还有一些看不懂的。271
    8 静态成员可以是不完全类型

class Bar{
	public....
	private:
		static Bar mem1; //正确,静态成员可以是不完全类型
		Bar *mem2;      // 正确,指针成员可以是不完全类型
		Bar mem3		// 错误,数据成员必须是完全类型
};

9 静态成员和普通成员的另外一个区别就是,我们可以使用静态成员作为默认实参。

class Screen{
	public:
		//bkgroud表示在一个类中稍后定义的静态成员
		Screen& clear(char =  bkground)
	private:
		static const char bkground;
};

第八章 IO库

  • getline函数,从一个给定的istream读取一行数据,存入一个给定的string对象中
  • IO对象无拷贝或赋值,所以不能将形参返回类型设置为流类型,进行IO操作的函数通常以引用的方式传递和返回流。读写一个IO对象会该变其状态,因此,传递和返回引用不能是const
    - 条件状态(condition state)
    IO库定义了一个与机器无关的iostate类型,包含四个值
    badbit表示系统级错误,
    failbit被置位表示可修复错误,
    到达文件结束位置,eofbit和failbit都会置位,
    goodbit的值为0,表示流没有错误。
    标准库定义了一组函数来查询这些标志位的状态
    s.eof() 若流s的eofbit置位,则返回true。
    s.fail() 若流s的failbit或者badbit置位,则返回true。
    s.bad() 若流s的badbit置位,则返回true。
    s.good() 若流s处于有效状态,则返回true。
    使用good或者fail是确定流的总体状态正确的方法。!fail()就等价于将流当作条件使用的代码。
  • 管理条件状态
    s.rdstate() 返回流当前的条件状态。
    s.clear() 将流中所有条件状态位复位,将流的状态设置为有效。
    s.clear(flags)根据给定的flag标志位,将流s中对应条件状态为复位 。
auto old_state = cin.rdstate();  //记住cin的当前状态
cin.clear();					//使cin有效
process_input(cin);				//使用cin
cin.setstate(old_state);		//将cin置为原有状态
  • 输出流缓冲区:每个输出流都管理一个缓冲区,用来保存程序读写的数据,例如执行下列代码:
    os<< “请打印一个值:”;
    这个文本串可能立刻被打印出来,也可能被操作系统保存在缓冲区中,随后在打印。有了缓冲基质,操作系统可以将程序的多个输出操作组合成单一的系统级(设备)写操作,由于设备写操作很费时间,所以将多个输出操作组合成一个设备写操作会带来很大性能提升。

  • 缓冲刷新(即,数据真正写到输出文件或者设备)的原因由很多包括:
    1 程序正常结束
    2 缓冲区满了
    3 使用操作符如:endl换行并刷新缓冲区,flush 刷新但不输出额外字符, ends向缓冲区插入一个空字符,在刷新。
    4 unitbuf操纵符,他告诉流,在接下来以后所有的写操作以后都会进行flush操作。cout << unitbuf; 与他相反,cout<<nounitbuf 回到正常缓冲方式。
    5 一个输出流可能会被关联到另一个流。任何试图从输入流读取数据的操作,都会先刷新相关的输出流,例如cout 和cin 关联在一起,所以
    c>>ival;会导致cout的缓冲区被刷新。
    注意! 如果程序异常终止的时候,输出缓冲区使不会被刷新的。当一个程序崩溃以后,他输出的数据很可能停留在输出缓冲区中等待打印。

  • 文件输入输出
    头文件#include 中,有三个类型支持文件IO
    ofstream 向一个给定文件写数据
    ifstream 从一个给定文件读数据
    fstream 可以读写给定文件
    他们都继承自iostream类型,且fstream中还增加了新的操作

ifstream in(ifile);    // 构造一个ifstream并打开给定文件
ofstream out;			//输出文件流未关联到任何文件

这段代码给定了一个输入流in ,他被初始化为从文件读取数据,文件名由string类型的ifile指定。第二条语句给定了一个输出流out,未与任何文件关联。
因为继承机制,如果一个函数接受参数为iostream类型的引用或指针,那么可以传递给他一个ifstream& 参数,ofstream&也是一样的(父类的指针和引用可以指向或绑定子类)

ifstream in(ifile);    // 构造一个ifstream并打开给定文件
ofstream out;			//输出文件流未关联到任何文件
out.open(ifile + ".copy")  //打开指定文件

如果open失败,failbit会被置位,所以==if(out)==检查是否open成功是一个好的习惯。如果要打开另一个文件,要先将已打开的文件关闭

in.close()   //关闭文件
in.open(ifile + "2");打开另一个文件
  • 文件模式
    每一个流都有一个关联的文件模式
    in 以读的方式打开
    out 以写的方式打开
    app 每次写操作前均定位到文件末尾
    ate 打开文件后立即定位到文件末尾
    trunc 截断
    binary 以二进制方式IO
    ifstream 默认in,ofstream 默认out,fstream 默认in和out
    ostream以out模式打开文件会丢失已有数据,只有同时指定app,才不会清空
ofstream out("file1");//隐含out 和截断
ofstream out2("file1",ios::out);//隐含截断
ofstream out3("file1,ios::out  |  ios::app")
//为保留文件内容,我们必须显示指定app模式
ofstream app("file2,ios::app")//隐含out
ofstream app2("file2,ios::app | ios::out")

每次用open打开文件时都会确定文件模式,可能是显示,也可能是隐式。没指定就是默认

ofstream out; // 未指定文件打开模式
out.open("file");//隐式设置为out和截断
out.close();//关闭out,以便将其用于其他文件
out.open("file2",ios::app); //out 和截断trunc
out.close()
  • string 流
    #include < sstream > 头文件,定义了三个类型,支持内存IO
istringtream从string读取数据
ostringstream向string写入数据
stringstream既可以从string读数据,也可以向string写数据

与fstream类型相似,他们也是继承iostream头文件中的类型
istringstream
题目:编写程序,将来自一个文件中的保存在一个vector中,然后使用一个istringstream从vector中读取数据。每次读取一个单词

#include<iostream>
#include<fstream>
#include<sstream>
#include<string>
#include<vector>
using namespace std;

int main(int argc, char**argv)
{
/*两种文件流皆有open和close函数,之后视情况打开读或者写模式*/
string infile = "1.txt";//代表文件名
vector<string> vec;//声明一个vector
ifstream in(infile);//ifstream定义了一个输入流in(文件流),它被初始化从文件中读取数据 

if (in)//检查文件的读取是否成功,养成良好的习惯!
{
    string buf;
    while (getline(in,buf))//输入一行
    {
        vec.push_back(buf);
    }
}
else
{
    cerr<<"cannot open this file: "<<infile<<endl;
}
for (int i = 0;i < vec.size();++i)
{
    istringstream iss(vec[i]);//将istringstream与vec[i]相绑定
    //一次读取vec[i]中的一个元素。注意每个vec[i]中存的都是文件中的一行
    string word;
    while(iss >> word)// 空格为间隔,一次读取一个单词
        cout<<word<<endl;
}

return 0;

总结:
1

iostream处理控制台IO
fstream处理命名文件IO
stringstream完成内存stringIO

其中fstream,和stringstream 都继承自iostream。

2输入类都继承自istream,输出类都继承自ostream。因此可以在istream对象上执行的操作
在ifstream和istringstream对象上都能执行。继承自ostream的输出类也有类似的情况。
3 每个IO对象维护一组 条件状态

第九章 顺序容器

  • string 和 vector 将元素保存在连续的内存空间中。

  • list 和 forward_list两个容器,支持任何位置快速添加删除。不支持元素随机访问:只能遍历整个容器。

  • deque 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。

  • forward_list 和array是新c++标准增加的类型。与内置数组相比,array是一种更安全,更容易地数组类型,与内置数组类似,array对象大小固定。不支持添加和删除元素。forward_list 没有size操作,其他都有。

  • 容器之间的拷贝
    在这里插入图片描述

  • 顺序容器会提供一个带有大小相关的构造函数!(关联容器并不支持)
    在这里插入图片描述
    array要有固定大小,其初始化,拷贝如下所示
    在这里插入图片描述

  • 对于array类型来说,{}可以用来初始化,但是不可以用来赋值

  • 用swap()函数对两个array会真正交换他们的元素,但是不会对其他容器的任何元素进行拷贝等操作。

  • 容器大小比较
    在这里插入图片描述

  • push_back() 除了array和forward_list(单项列表)之外,每个顺序容器包括string类型都支持该函数。

  • *insert(p,“hello”) 把“hello”插入到*p之前

  • **insert()返回的是一个指向iterator。**下面的代码是将word一致插入到iter前,相当于调用push_front:
    在这里插入图片描述

  • emplace操作
    == emplace_front==,emplace ,emplace_back 对应push_front,insert和push_back。只不过后者用在各种类型上。前者是在自己用在容器里装的是自己设计的类中。
    如下:
    在这里插入图片描述
    - 访问成员函数(front back at 下标)返回的是引用
    在这里插入图片描述
    - string, vector , deque , array 可以使用下标去访问,也可以使用at()
    在这里插入图片描述

  • forward_list单项列表如何删除
    需要有两个指针,一个指向被删除的元素,另外一个指向被删除的元素的前一个元素。代码如下:
    在这里插入图片描述

  • 注意!不要保存end返回的迭代器(我经常犯这个错误)
    因为当我们删除或者添加vector或string的元素后,或者在deque中首元素之外任何位置添加删除元素后,原来的end返回的迭代器总会无效!
    在这里插入图片描述
    正确做法如下所示:
    在这里插入图片描述
    - vector是如何增长的?
    假定容器中,元素是连续存储的,且容器的大小是可变的,考虑向vector或string中添加元素会发生什么:如果没有空间容纳新元素,容器不可能简单地将它添加到内存地其他位置,因为元素必须连续存储。容器必须分配新的内存空间来保存已有元素和新元素,将已有元素从旧位置移动到新空间中,然后添加新的元素,释放旧的存储空间。如果我们每添加一个新的元素就执行一次这个操作,那太慢了!
    所以,当不得不获取新的内存空间时,vector和string 地实现会分配比新的空间需求更大地内存空间。容器预留这些空间作为备用。

  • 管理容器容量的成员函数
    在这里插入图片描述

  • 理解capacity和size
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 知识点:vector中的元素在内存中是连续存放的,所以需要capacity这个东西~
    list没有capacity是因为其存储元素的内存是不连续的,不需要capacity
    array没有是因为其大小是固定的

  • 容器适配器
    除了顺序容器之外,标准库定义了三个顺序容器适配器stack,queue,priority_queue 本质上,适配器是一个机制,使某种事物的行为看起来像另外一个事物。
    例如stack适配器接受一个顺序容器,使其操作看起来像一个stack一样。
    定义一个适配器:
    每个适配器有两个构造函数:
    1 默认构造函数,创造一个空对象。
    2 接受一个容器的构造函数,拷贝该容器来初始化适配器。
    例如:假如deq是一个deque,我们可以用deq来初始化一个stack如下图所示:

 stack<int> stk(deq);//从deq拷贝元素到stk

默认情况下,stack和queue使基于deque实现的。priority_queue使基于vector实现的。
但是也可以改变:如下
我们可以在创建一个适配器时候,将一个,命名的顺序容器,作为第二个参数类型,来重载默认容器的类型

//在vector上实现空栈
stack<string , vector<string>> str_stk;
//str_stk2在vector上实现,初始化时候保存svec的拷贝
stack<string , vector<string>> str_stk2(svec);
适配器(功能)能构建在什么容器上
stack【push_back,pop_back,back】除array和forward_list之外的其他容器
queue【back,push_back,front,push_front】list,deque(默认)
priority_queue【back,push_back,front,push_front,随机访问】vector.deque
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值