Day 23 - 字串又来了,我还是没吃到串烧

Outline

可以把这三个东西理解成 class 的更多运用。

C++ Strings : 比之前教过的 c string 更模组化。

File I /O : 因为现在写的程序越来越大,所以可能需要用这种方式。

Header files : 因为我们写的 class 会越来越多,我们就需要利用 header files 来管理这些 classes。

C++ Strings

之前我们学到的 C string 会与 C++ string 不太一样:

  • C string : 基本上就是一个 character array,会在最後加上 '\0'。(define 在 <cstring>里面)
  • C++ string : 是一个 class 叫做 string,并且是 defined 在 <string> 里面,因此可以做到模组化的效果。(因此比较好用),他内含:
    • A member variable, a pointer pointing to a dynamic character array
    • many member functions
    • many overloaded operators

Declaration

宣告

string myStr;//空字串                             
string yourStr = "your string";//传入一个阵列
string herStr(yourStr);// copy contructor (已经是 deep copy)

透过这些方式可以让我们宣告 string object(注意 每个都是)

这些函式是:

  • string::string()
  • string::string(const char* s);
  • string::string(const string& str);
    • string 是定义在<string> 里面的一个 class
    • sting 不是 C++ keyword
    • myStr 是一个 object
    • 因为 encapsulation 的缘故,让 C++ string 可以不再担心 '\0'

函式

string myStr;
string yourStr = "your string";
cout << myStr.length() << endl; // 0
cout << yourStr.size() << endl; //11
  • size_t string:: length() const;
  • size_t string::size() const;
  • size_t string::max_size() const; (可以让我们知道我们最多可以使用多大的字串)
    • 我的电脑里是: 2147483647
    • 反正就是很大

指派

string myString = "my string";
string yourstring = myString; // 宣告的时候用的是 structure
string herString;
herString = yourString = "a new string"; // assignment 是使用 operator overloading

或是也可以用 C string 的方式做到 C++ string 里面:

char hisString[100] = "oh yaaaa";
myString = hisString;

串接(concatenation)和标数(indexing)

string myStr = "my string";
string yourStr = myStr;
string herStr;
herStr = myStr + yourStr; // "my string my string" (会自动加上一个 space)

可以直接用 + 把两个字串接起来(也是因为被 overloaded 过了)

这个 + 就像是 C 里面的这个函式 strcat()

所以一样的可以用 C string 的方式配上加号,一样可以把字串接起来:

string s = "123";
char c[100] = "456";
string t = s + c; // 123456
string u = s + "789" + t; // 123789123456

取出一个字串的某的 character 就用 [ ]

string myStr = "hello there";
char a = myStr[0]; // h

string input: getline()

string myStr; 
cin >> myStr; // this is a book
cout << myStr; // this? why
cout << myStr[0];// t

为什麽我们myStr 印出来会是this?

这是因为C++在判定cin 的时候,space 会被当作一个delimiter

Review

char a[100];
cin.getline(a, 100); // Hi, this is John Cena
cout << a << "\n"; // Hi, this is John Cena

cin.getline() 是一个函数,且在切换字元方面,它是依照换行来区别不同的东西。(a, 100) → a 就是要传入的阵列、100就是你想输入的数量。

但是我们现在却不能使用getline(),这是因为我们第一个传入的是一个字元阵列,也就是所谓的C string,但我们现在呼叫的却是一个C++ string( object)。

这次我们要用的,是一个define在<string>里面的global function。

istream& getline(istream& is, string& str);
istream& getline(istream& is, string& str, char delimiter);

istream = input string

istream& = input stream 的 reference

使用:

string str;
getline(cin, str);
getline(cin, str, '#');  

可以想像 cin 就是一个 object,这一个function 需传入两个 object

而如果加入第三个则就是每个字串会以 '#' (以上面为例) 做分割。

string str;
getline(cin, str); // hello there

Substrings

string string::substr(size_t pos = 0, size_t len = npos) const;

pos : 从哪里,len : 多长

string::npos 这是一个 static member,也就是指 size_t最大值

也就是说,若你你二个参数不填,他就会从第一个 pos 切到这个字串结束。

使用 :

string s = "asdfgijjj";
cout << s.substr(2, 3) << endl; // "dfg";
cout << s.substr(2) << endl; // "dfgijjj" 

寻找~~威力~~ String

可以使用 find()

size_t find(const string& str, size_t pos = 0) const; // 传 c++ string
size_t find(const char* s, size_t pos = 0) const; // c string
size_t find(char c, size_t pos = 0)const; // a character

pos 也是从哪里开始找; 前面的 parameter 就是你想要寻找谁。

而他回传的是 你想要的那个东西的 beginning index,若没找到则是 string::npos

string s = "asdfgh";
if (s.find("fgh") != string::npos)
	cout << s.find("fgh"); // 3

比较

以前我们可能需要使用 strcmp()

但 C++ 里面就可以直接使用 >, ≥, <, ≤, ==, != 来 compare。

怎麽查 ?

如果你想做 string concatenation,去google 或是 找一下 <string> 里面的函式。

还有更多

insert(), replace(), erase(); 好用好用推推推!!

string& insert(size_t pos, const string& str);
string& replace(size_t pos, size_t len, const string& str);
string& erase(size_t pos = 0, size_t len = npos);
int main()
{
  cout << "01234567890123456789\n";
  string myStr = "Today is not my day.";
  cout << myStr << endl;
  myStr.insert(9, "totally "); // Today is totally not my day. 
  cout << myStr << endl;
  myStr.replace(17, 3, "NOT"); // Today is totally NOT my day. 
  cout << myStr << endl;
  myStr.erase(17, 4); // Today is totally my day. 
  cout << myStr << endl;
  return 0;
}

其实看一遍就大概会他们怎麽用了!!

C++ string 的转换

  • C++ → C string c_str()
  • C++ → number stoi(), stof(), stod()
  • number → C++ to_string()

C++string for mandarin characters

就像是英文字母一样,中文字也会使用编码来纪录,而在大部分的环境里会利用两种系统

  • Big-5
  • UTF-8

他们都是用 2 bytes 来存取中文的字元

int main()
{
	string s = "大家好";
	cout << s << endl; // 大家好
	char c[100] = "喔耶";
	cout << c << endl;  //喔耶

	cout << s[1] << endl; // j
	cout << c + 2 << endl; // 耶
}

在先前介绍的 function 用在中文字元也是行得通的 !

但是要注意的是,要把 elements 当作一个个分开的 char variables

像是如果我们要反转一个字串

对於英文字串就可以这样写

int main()
{
	string s = "12345";
	int n = s.length(); //5
	string t = s;
	for (int i = 0; i < n; i++)
		t[n - i - 1] = s[i]; // good
	cout << t << endl; //54321
	return 0;
}

但是如果我们同样用这样的方式去做中文字就会变成

int main()
{
	string s = "大家好";
	int n = s.length(); //6
	string t = s;
	for (int i = 0; i < n; i++)
		t[n - i - 1] = s[i]; // nono
	cout << t << endl; // n地屐
	return 0;
}

这是因为中文字元是由两个 byte 做编码,大概会长这样:

xxk xxu xxo xix xxo xox

编码是我乱写的。总之如果我们两个编码倒过来就会变得我们不知道是甚麽的中文字元

那麽要怎麽样写?

所以我们就必须要两个两个一组把他们传过去

int main()
{
	string s = "大家好";
	int n = s.length(); //6
	string t = s;
	for (int i = 0; i < n - 1; i = i + 2)
	{
		t[n - i - 2] = s[i];
		t[n - i - 1] = s[i + 1];
	} //nice
	cout << t << endl; 
	return 0;
}

File I /O (focus on plain text file)

我们可以直接用程序汇入档案或是汇出档案,像是游戏的成绩、纪录(在程序中也可以更改这个 file)

The von Neumann architecture:

在我们以前写的程序,只会包含上面那三个,但是现在可以加入下面这一个 storage 了。

要使用 storage,我们就一定要跟我们的硬碟去沟通了。

一个 plain text file 会包含

  • 存入 characters
  • 没有 format (MS word document)
  • 没有颜色 (bitmap file)

这些字元是如何储存的?

  • 每个字元会有自己的位置
  • 每个 file 会有一个 position pointer 指向 你现在要读/写的地点
  • 可以透过这个pointer 来控制 reading or writing

写档案

第一个 character 会存在 position 0。

每当写一个 character 的时候,position pointer 会移到下一个位置,且原本存在position 0 的字元会被取代。

File Stream

就像我们以前可以在 console 中印出或是输入时,我们使用 cout << , cin >>,而他们使用的 library 是 <iostream>

我们要在 console 里面改动 file 中的资料,就会使用 ifstreamofstream 的 object 或是 function,而这两个 class 是被 define 在 <fstream>里面。

Output file stream:

ofstream file object;
file object.open(file name); //打开档案
//.....
file object.close(); // 关掉档案

file name可以是 C or C++ string

int main()
{
	ofstream myFile;
	myFile.open("temp.txt");
	myFile << "1 abc\n &%^ " << 123.45;
	myFile.close();

	return 0;
}

execute 之後就会发现在程序的同一个资料夹就会出现一个 txt 档,里面写着

1 abc
&%^ 123.45

<< 这个 operator 被做了 overloaded,会 return ofstream&,来连续做 output stream。

different options:

open mode:

ofstream file object;
file object.open(file name, option); //打开档案
//.....
file object.close(); // 关掉档案
  • ios::out (default) : 从 0 开始,会覆盖已存在的资料。
  • ios::app : 从档案最後开始,不会更改资料
  • ios::ate : 从档案最後开始,会把整个资料更改

其中 ios 是一个 class,out, app, ate 三个是 static variable

其他 function:

  • put(char c) : 把 char c 写入 file 里面

例子:

#include<iostream>
#include<cstdlib>
#include<fstream>
using namespace std;

int main()
{
	ofstream scoreFile("temp.txt", ios::out);
	char name[20] = { 0 };
	int score = 0;
	char notFin = 0;
	bool con = true;

	if (!scoreFile) // 如果 scoreFile 没有被读取
		exit(1); // 强制 terminate
	while (con)
	{
		cin >> name >> score;
		scoreFile << name << " " << score << "\n";
		cout << "Continue (Y/N)";
		cin >> notFin;
		con = ((notFin == 'Y') ? true : false);
	}
	scoreFile.close();
	return 0;
}

Input file stream:

ifstream file object;
file object.open(file name); //打开档案
//.....
file object.close(); // 关掉档案

option 只有 ios::int,没有其他的。

就像 output 一样,我们可以用 if(!myFile) 来确认档案有没有正确地打开,确认後再去其他事情。

状况一:

如果input data 的资料型态是非常的整齐,就使用 >>

像是下面这样,每个姓名成绩中间都隔一个空白

Jeff 100
Charlie 95
Jack 88
Emily 99
Leo 53

可以求 平均、排序、....等等

#include<iostream>
#include<cstdlib>
#include<fstream>
using namespace std;

int main()
{
	ifstream inFile("score.txt");
	
	if (inFile)
	{
		string name;
		int score = 0;
		int sumScore = 0;
		int scoreCount = 0;

		while (inFile >> name >> score)
		{
			sumScore += score;
			scoreCount++;
		}
		if (scoreCount != 0)
			cout << static_cast<double>(sumScore) / scoreCount;
		else
			cout << "no grade!";
	}
	inFile.close();		
	return 0;
}

这样就可以从我们的资料里面找到他们的成绩,再取平均了!

>> 会在两个空白/ tab/ \n 之间读取资料。

状况二:

这个情况就是 file 里面的资料没有格式化,或不是非常的完美,例如资料有缺失(即没办法预测下一向 data type 是甚麽)

Jeff 100
Charlie 95
Jack
Emily 99
Leo 53

这时候我们就不能使用 >> 来读取档案,在这种情况,要把 data 当作 character 来看,并手动地找到我们要的 type,这个过程会被称为 parsing。

这时候就可以使用在 ifstream 之中的函式(member function)

  • get() : read a character and return it
while (!inFile.eof()) // eof = end of line
{
	char c = inFile.get();
  cout << c;
}
  • getline() : read multiple character into a character array
  • getline(name, 20, ' ') 第三个 parameter 是 delimiter
while (!inFile.eof()) // eof = end of line
{
	char name[20] = 
	inFile.getline(name, 20); //name是要存入的矩阵; 20 代表取 20 个字
  cout << name << endl;
}

如果加上第三个参数的话,它代表的意思是读到那就停(position pointer 停在 delimiter 的下一个字元)

  • ignore() 可以把指标指向下一个字元

但是如果我们每次都要使用 char array 这样会非常的麻烦,因此我们也可以使用 C++ string。

但是这边要注意的是,我们之後要使用的 getline() 会是定义在<string>底下的 global function:

header:

istream& getline(isream& is, string& str, char delim);

使用

while (!inFile.eof()) // eof = end of line
{
	string name;
	getline(inFile, name, ' '); // 直接使用 getline() 就可以
	cout << name << endl;
}

更新档案

如果今天有一段资料像是这样

Jeff 100
Charlie 95
Jack 87
Emily 99
Leo 53

我们要把 Jack 改成 Jackson 怎麽办?

  • 可以先用 seekp() 找到我们想要的文字 : J
  • 在利用 copy-and-paste 把新的文字贴上去(因为 plain text files 是 sequential-access files 资料是连续的)

实作

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

int main()
{
	ifstream inFile("test.txt");
	ofstream outFile("test1.txt");
	string name; 
	int score;

	if (inFile && outFile)
	{
		while (inFile >> name >> score)
		{
			if (name == "Jack")
				name = "Jackson";
			outFile << name << " " << score << endl;
		}
	}
	inFile.close();
	outFile.close();

	return 0;
}

>> vs. getline()

- date type delimiter
>> 会把传入的转成容器的type 在第一个不是容器 type 时停止(pt停在下一个字元)
getline() 全部转成 string 会取到delimiter而已(pt停在下个字元)

Header files

Libraries

<iostream><fstream><cmath><cctype><string>这些 library已经被我们用了行之有月了,那这些 library 要怎麽 define? 我们自己也可以 define 吗?

当然是可以的 !

一个 library 包含了一个 header file(.h)与 一个或数个 source file(s) (.cpp)

  • header file 里面包含了 declarations
  • source file 里面包含了definition

例子

#include <iostream>
using namespace std; 
int myMax(int a[], int len);
int main() 
{
  int a[LEN] = {7, 2, 5, 8, 9};
  cout << myMax(a, 5)
  return 0;
}
int myMax(int a[], int len)
{
  int max = a[0];
  for(int i = 1; i < len; i++)
  {
    if(a[i] > max)
      max = a[i];
  }
  return max;
}

我们想要把这个程序定义在一个 library 里面,可以这样写

这个header file就是放 function 或是 variable 的宣告。

myMax.h

const int LEN = 5; 
int myMax (int [], int);
void print(int);

这个档案就是放 function 的定义(类似使用说明)

myMax.cpp

#include <iostream>
using namespace std; 

int myMax(int a[], int len)
{
  int max = a[0];
  for(int i = 1; i < len; i++)
  {
    if(a[i] > max)
      max = a[i];
  }
  return max;
}
void print(int i)
{
  cout << i; // cout undefined!
}

这边就是放我们一般使用的编译 main program

main.cpp

#include <iostream>
#include "myMax.h" //要传入我们自定义的 header file
using namespace std;

int main() 
{
  int a[LEN] = {7, 2, 5, 8, 9};
  print(myMax(a, LEN));
  return 0;
}

要注意的是,每一个 source file 都要记得 include 它里面所需的 library

心得

想到剩下五天就觉得好兴奋!!!!!!

加油


<<:  [Day 20] 两段式训练比两段式左转更安全 (迁移学习技巧)

>>:  JavaScript入门 Day30 _addEventListener监听器

D14: 工程师太师了: 第7.5话

工程师太师了: 第7.5话 杂记: 注解是程序语言中用来解释程序码中的部分,可增加程序的可读性、可维...

Day 1 Odoo是什麽呢? 云端ERP?

为什麽要使用云端服务? 《2020亚太地区中小企业数位化成熟度研究》报告指出,亚太区近70%的中小企...

我们的基因体时代-AI, Data和生物资讯 Day04- 深度学习在基因体学的建模架构01

上一篇我们的基因体时代-AI, Data和生物资讯 Day03- 基因医学的数据问题介绍了基因医学中...

Unity - VR - Step运用

制作 VR 虚拟实境游戏控制玩家在 3D 空间移动,实际上是一个重要的问题,使用键盘滑鼠缺乏真实体验...

Day 30:完赛与 PVE 小经验

前言 铁人赛最後一日,直到此我们已经学习了许多知识、也了解了不少 PVE 的操作!最後一天就来聊聊之...