Day 19 - C strings 字串,我好想吃串烧

Outline

  • Characters
  • C strings
  • C string processing functions

字串是由字元组成的。

指标的应用:字串

Characters (字元)

char

  • 利用 one byte (-128 ~ 127) 来储存 英文字、数字、符号等等(我们在第 9 天有列给大家看)。
  • 它的骨子里也是整数。

char 的使用:

  • 如果要使用 character literal,需要用单引号 (single quotation marks)

    int main()
    {
    	char c = '0';
    	cout << static_cast<int>(c) << " ";
    	c = 'A'
    	cout << static_cast<int>(c) << " ";
    	c = '\n'
    	cout << static_cast<int>(c) << " ";
    	return 0;
    }
    

    就跟上面讲得一样,因为 char 其实就是用整数来储存符号数字等等,所以你把它 cast 成整数的时候就会变成一个数字 (也就是 ASCII code)。

  • 可以对 char 做加减:

    	int main()
    {
    	char c = 48; 
    	cout << c << " "; // 0
    	c += 10;
    	cout << c << " "; // :
    	if (c > 50)
    		cout << c << " "; // :
    	return 0;
    }
    
  • 第一个例子,如果当使用者输入 'Y'或是'y' 就去执行,不是的话就不执行(重来)

    int main()
    {
    	int a = 0, b = 0;
    	char c = 0;
    
    	do
    	{
    		cout << "Enter 2 integers: ";
    		cin << a << b;
    		cout << "Add? ";
    		cin << c;	
    	} while (c != 'Y' && c != 'y');
    	cout << a + b << "\n";
    
    	return 0;
    }
    
  • 可以让英文字母的大小写互换

    // ASCII code: 
    // A : 65 -> B : 66 .... Z : 90
    // a : 97 -> b : 98 ....
    
    int main()
    {
    	char c = 0;
    	while(cin >> c)
    	{
    		if (c >= 65 && c <= 90)
    			cout << static_cast<char>(c + 32);
    		else
    			cout << c;
    		cout << "\n"; 
    	}
    	return 0;
    }
    

    但你怎麽可能会随时记得 ASCII code 是甚麽啦,所以比较好的做法是使用 standard library : ,里面就有很多函数可以帮你转换:

    • int islower(int c); : 小写的话: return 0;大写: return nonzero。
    • int isupper(int c); : 大写的话: return 0;小写: return nonzero 。
    • int tolower(int c); : 转换成小写的数字(ASCII code)。
    • int toupper(int c); : 转换成大写的数字(ASCII code)。

    为什麽 return int : 因为在 C 语言的时候就是这样,因此沿用

    • 那我们来印出 ASCII code 吧
    int main()
    {
    	cout << "   0123456789\n";
    	for (int i = 30; i <= 126; i++)
    	{
    		if (i % 10 == 0)
    			cout << setw(2) << i / 10 << " "; //setw(2) 控制栏位有两格
    		if (isprint(i))
    			cout << static_cast<char>(i);
    		else
    			cout << " ";
    		if (i % 10 == 9)
    			cout << endl;
    	}
    	return 0;
    }
    

C strings

C strings - basics

String literal

在C语言里面,有两种 string:

  • C string 中使用的 character array (比较不好用)
  • C++ string 是使用 object (比较难用)

但是 C string 会使用到 pointer 的概念,可以加强我们对於 pointer 的使用经验。

其实我们在很早很早以前,就已经用过 string 了

就是我们在用 cout << "Hello World!"; 的时候,双引号中间这段东西,其实就是 string。

character array 因为也是 array,所以也可以像 array 一样 initialize

```cpp
char s[10] = {0};
char t[10] = {'a', 'b', 'c'};
```

e.g.,

这段程序可以会让你输入,等到输入'#'後或是输入10个字元後就停止

```cpp
const int LEN = 10;
int main()
{
	char s[LEN] = {0};
	int n = 0;
	do 
	{
		cin >> s[n];
		n++;
	}while (s[n - 1] != '#' && n < LEN);
	for (int i = 0; i < n; i++)
		cout << s[i];
	return 0;
}
```

要怎麽 input string?

在C语言中,他们把存在 char array 里面的 operation overloading(就是可以做出更多事,跟function 的 overloading 颇像)。

特别是 cin >> & cout <<

你可以直接 cin 一个 char array,

char str[10];
cin >> str; // if we type "abcde"
cout << str[0]; //a
cout << str[3];  //d 

或是可以直接 cout 一个 char array

int value[5];
cout << value; //an address
char str[10] = {0}; 
cin << str; // 如果我们打 "abcde"
cout << str; // 就会跑出 abcde

但是! 为什麽我们只打了 5 个 char,且 cout 出来也真的是 5 个? 可是我们原本宣告的阵列有 10 项欸,那我们怎麽知道这段阵列後面的是甚麽?

The null character: \0

当我们在 cin >> 的时候,当输入到最後一项,电脑会自动在最後附加上一个 null character(\0 = 斜线零),来表示你的 cin 结束了。

  • null character 的 ASCII code 就是 0。

  • 这表示 null character 本质上也会占一个空间, 因此你如果宣告了长度是 n 的 array,你最多只能存入 (n- 1)个 char。

  • C string 的 initialization ,可以写成这样

    char s[100] = "abc";

    这是因为 = 这个 operator 也被 overloaded 过了。(未来会讲)

    也同样的,在你宣告成 "abc" 的同时,电脑也会自动在最後加上 \0,以表示你宣告的东西就是这麽多。

    	char a[100] = "abcde FGH";
    	cout << a << "\n"; //abcde FGH
    	char b[100] = "abcde\0FGH";
    	cout << b << "\n";
    

    所以当我们做这件事的时候,会发现在 b[] 里面,印完 abcde 就没了,这就是因为电脑认为你印到 e就结束了。

  • 那 C string 的initialization 也可以写成这样:

    char s[100] = {'A', 'g', 'd'};
    

    但是这个时候,null character 却不会被加到最後一项的後面

    这就是这两种 宣告方式的不同了。

    那讲了那麽多,到底甚麽时候会 append null character呢?

    举几个例子好了:

    • char s[10] = "abc" → 会 ✅
    • char s[100] = {'a', 'b', 'c'} → 不会 ❌
    • cin >> s; (直接输入字串) → 会 ✅
    • cin >> s[0]; (一项一项输入) → 不会 ❌

String assignments:

Assignment with double quotations are allowed only for Initialization

" " 的宣告,只能在 initialization 的时候做。

char s[100];
s = "this is a string"; // compilation error

但是如果是个别的做的话,就可以!

char s[100];
s[0] = 'A';
s[10] = 'B';

char c[100] = {0};
cin >> c; // 123456789
cin >> c;// abcde
cout << c << "\n"; // "abcde"
c[5] = '*'; 
cout << c << "\n"; // abcde*789

这个的原因是甚麽呢?

我们可以用图表示:

【Cin 表示图】

第几次 cin or cout c[0] c[1] c[2] c[3] c[4] c[5] c[6] c[7] c[8] c[9]
first 0 0 0 0 0 0 0 0 0 0...
second 1 2 3 4 5 6 7 8 9 \0
third a b c d e \0 7 8 9 \0
fourth a b c d e 7 8 9 \0

因为 在输入 abcde 的时候,它最後面也输入了 \0,因此我们第三个cout 就只有印出 abcde ,但是我们在 assign * 给 s[5] 之後,\0消失了,所以就可以印出後面的东西了。

那如果我们输入超出一个 array 的东西的话,则可能会有 error

char a[5] = {0};
cin >> a; //"123456789"
cout << //"123456789" or an error

所以可以知道 cout << 不会理你这个 array 里面有多少东西,只会一直印,直到碰到 \0。

有个奇怪的例子:

char a1[100];
cin >> a1; // "this is an apple"
cout << a1; // "this"

为什麽只会输出 this 呢?

还记得我们以前 cin 的时候,电脑会根据空白来切开我们输入的不同的数字。

所以同理,输入"this is an apple"的时候,就会只输入 this 了!

那在 C++ 里面,正确的使用是这个样子:

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就是你想输入的数量。

e.g.,

今天我们的阵列里面有 100 项,我们要输入一串字,让这个程序数我们的字中到底有几个 space (空白)

char a[100] = "abcde FGH";
	while (cin.getline(a, 100))
	{
		int i = 0;
		int spaceCount = 0;
		
		while (a[i] != '\0')
		{
		// 如果多个space只算一个的话
		//if (a[i] == ' ' && a[i - 1] == ' ')  
		//{
		//	i++;
		//	continue;
		//}
			if (a[i] == ' ')
				spaceCount ++;
			i++;
		}
		cout << spaceCount << "\n";	
	}

String array

如果你今天需要储存很多个 string 的话,这时候就必须用一个二位阵列来储存这麽多个 char array。

char name[4][10] = {"John", "Mikasa", "Eren", "Armin"};
cout << name << "\n"; // an address
cout << name[1] << "\n";  // Mikasa 
cin >> name[2]; // Obama
cout << name[2][0] << "\n"; // O

可以想像成:

第几个 array [][0] [][1] [][2] [][3] [][4] [][5] [][6] [][7] [][8] [][9]
0 J o h n 0 ...
1 M i k a s a 0 ...
2 E r e n 0 ...
3 A r m i n 0 ...

C strings as character pointers

pointer 是可以指向一个 array 的,所以可以利用 character pointer 来使用 C string:

char a[100] = "12345";
char* p = s;
cout << p << "\n";
cin >> p; // or s
cout << s; // or p
  • 因为 pointer 只是指向一个地址,所以我们必须要先宣告一个 array(配置记忆体),才能使用 pointer。

  • 但是如果你 initialize pointer,就可以使用了! (但是这段空间只能读不能做更改或是其他的宣告)

    char* p = "12345";
    cout << p + 2 << "\n"; // 345
    

Passing a string to a function?

要怎麽把 string 传入 function 里面? 其实很简单,把你的 parameter 改成 pointer 或是 char array 就好了!

#include<iostream>
#include<cstring>
using namespace std;
void reverse(char p[]);
void print(char* p);

int main()
{
	char s[100] = "12345";
	reverse(&s[1]);
	print(s);
	return 0;
}

void reverse(char p[])
{
	int n = strlen(p); // string 的长度
	char* temp = new char[n];
	for (int i = 0; i < n; i++)
		temp[i] = p[n - i - 1];
	for (int i = 0; i < n; i++)
		p[i] = temp[i];
	delete [] temp; // 把空间释放(避免 memory leak)
}

void print(char* p)
{
	cout << p << "\n";
}

我们想要写一个程序,可以让我们传入的 string 可以反转过来。

而且我们传入的时候不一定要传入 哪个位置,所以当然我们也可以传 &s[1] (也就是第二项 2),这样的话,结果就会显示 15432 ,那如果改成 &s[2], 就会变成 12543 这样!

小提醒: 你也可以用指标来宣告一段 "string",例如:

char* ptr = "12345";

这个时候,记忆体的操作就是把一块空间空出来给你 放 "12345",但是你其实不知道在哪里,接着再把 ptr 指在 "1" 上面。如此这般你就可以用 ptr 来读取这段 string 了。

Main function arguments

如果你在 sublime 或是 visual studio 上面打 int ,他会给你选择,如果你选择的画,他就会跑出:

int main(int argc, char* argv[])
{
	
}

你可能会发现 main function 里面竟然有 argument,真的太奇葩了吧。

我原本根本搞不懂这个在干嘛,所以就一直不敢用他。

在C++ 的 main function 里面,只能传入这两个 argument

  • 第一个 int argc: 这个意思就是你会传多少 argument (argc = argument count)
  • 後面的 char* argv[] 就是你传入的字串指标(指向一条字串),argc 就是计算会有多少个字串会传入
    • argv[0] 是 executable file 的名字
    • argv[i] 就是第 i 个传入main中的 string

那我们来写一个跟这些有关的程序:

int main(int argc, char* argv[])
{
	for (int i = 0; i < argc; i++)
	{
		cout << argv[i] << "\n";
	}	
	return 0
}

但是写完如果你按编译,他其实不会有任何的反应,所以你必须要打开终端机(win + R → cmd),然後按 cd(change direction) 找到你在的那个档案资料夹(我的话是 Desktop),最後选择 Untitled1.exe(我的档案名称) ,并在後面加上你想加入的string: 结果会这样显示:

C:\Users\user\Desktop>Untitled1.exe 1 2 3 4 t y u hgh gg
Untitled1.exe
1
2
3
4
t
y
u
hgh
gg

C:\Users\user\Desktop>

就可以看到第一个印出来的是 执行档的名称,接着後面的就是我传进去的东西

但实际上我们不太会用到(可能要写作业程序的时候会用到)。

C string processing functions

使用 c string 很方便的函数,这些函数都存在 <cstring>这个library 里面,且有很多是 pointer-based 的 function。另外<cstdlib>里面也有蛮多好用的函数。要查的话可以用 http://www.cplusplus.com/ 里面可以查到很多东西。

  1. String length query

    strlen():

    • 功能:可以计算你的string 有多长。
    • 呼叫: unsigned int strlen(const char* str); // 这个函式不会影响你传入的 str
    • 回传: 一个 unsigned integer (计算从 str(第一个的地址)到第一个'\0' 中间会有几个字元)
    - - - - - - - - - - -
    - - - 1 2 3 4 5 0 - -
    - - - ↑str - - - - - - -

    就像是这样

    E.g.

    char* p = new char[100];
    cin >> p;
    cout << strlen(p);
    p[3] = '\0';
    cout << strlen(p + 1) << "\n";  
    delete [] p;
    

    结果会 cout 2,原因是这样的:

    如果我们原本输入12345,就会自动在後面加上\0

    1 2 3 4 5 \0

    但我们之後把 p[3] 改成 '\0' 之後,就会变成:

    1 2 3 \0 5 \0

    所以当 strlen() 在侦测的时候就会发现 从 p+1 跑到 \0 只有两个,所以就会显示 2 了!

    简单说,strlen()就是把你觉得是 string 的东西丢进去就可以数他的长度。所以像上面,若你把一个指标丢给它,它也会帮你数。

    • sizeof() 跟 strlen() 的区别

      char* p = "12345";
      	cout << strlen(p) << "\n"; // 
      	char a[100] = "1234567890";
      	cout << strlen(a) << "\n"; //   
      	cout << sizeof(a) << "\n"; // 
      	cout << sizeof(a + 2) << "\n"; // 8 -> 因为sizeof 侦测的是大小 a+2 会被认为是一个指标
      
    • 应用:

      还记得上面写的找空格的程序吗?其实有了 strlen(),我们就可以把 while 改成 for loop:

      char a[100] = "abcde FGH";
      	while (cin.getline(a, 100))
      	{
      		int i = 0;
      		int spaceCount = 0;
      		for(int i = 0; i < strlen(a); i++)
      		{
      			if (a[i] == ' '
      				spacecount++;
      		}
      		cout << spaceCount << "\n";	
      	}
      

      Likewise.

  2. searching in a string

    strchr()

    • 功能:可以在你的 string 中寻找某一个 character
    • 呼叫: char* strchr(char* str, int character) (给我一个 character,找看看有没有存在 str 里面,有的话就回传他的位置,再加上心号,就会变成一串字串)
    • 回传: 那一个 character 所在的位置,如果不存在就回传 nullptr。
    • 这就是一个 pointer-based 的函式!
    char a[100] = "1234567890";
    char* p = strchr(a, '8');
    if (strchr(a, 'a') == nullptr)
    	cout << "!!!\n";
    cout << strchr(a, '4') << "\n";
    cout << strchr(a, '4') - a;
    

    结果会显示:

    !!!

    4567890 : 这是因为函式回传 4 的地址,再加上*就会变成它後面的一串字串了。

    3 :因为 4 与 1 差了三格

    如果想要验证的话可以用下面这段:

    char* ptr = "1234";
    cout << ptr << "\n";//1234
    cout << ptr + 1 ; //234
    

    cout 一个指向string的指标的话,就会 cout 它後面剩下的 string。

    • 应用:打string里的空白都换成底线。
    char a[100] = "This is a book.";
    	char* p = strchr(a, ' '); //指向第一个space
    	while(p != nullptr)
    	{
    		*p = '_'; //把space 换成underline
    			p = strchr(p, ' '); // 由上一个space当作起点找下一个
    	}
    	cout << a;
    
  3. searching for a substring:

    strstr():

    • 功能:可以在你的 string 中寻找某一个 string
    • 呼叫: char* strshr(char* str1, const char* str2) 传入两个 string
    • 回传: 那一个 string 所在的位置,如果不存在就回传 nullptr。
    • 这也是一个 pointer-based 的函式!
    • 应用:我们直接找到字串里面的所有 is 换成 IS
    char a[100] = "this is a book";
    	char* p = strstr(a, "is");
    	while(p != nullptr)
    	{
    		*p = 'I'; // 并不是 p = "IS" 这样会变成 IS\0 而且 p 会完全被改变
    		*(p + 1) = 'S';
    		p = strstr(p, "is");
    	}
    	cout << a;
    
  4. String-number conversion

    简单说就是把 string 中的数字转换成 真的数字。

    在cstdlib里面有两个:

    • int atoi(const char* str); array to integer

    • double atof(const char* str); array to float

      • 这两个函式的使用前提:string 里面都要是数字相关
      char a[100] = "1234";
      cout << atoi(a) * 2 << endl;
      char b[100] = "-12.34";
      cout << atof(b) * 2 << endl;
      

      结果会跑出:

      2468
      -24.68

    • char* itoa(int value, char*str, int base); integer to array

      e.g.

      char a[100] = {0};
      itoa(123, a, 2);
      cout << a << endl;
      itoa(123, a, 10)
      cout << a[2] << " " << a << endl;
      
  5. String comparisons

    • 功能:可以把 character 拿来排序,就是比较每个字元的 ASCII code

    • 呼叫:

      • int strcmp(const char* str1, const char* str2);
      • int strcmp(const char* str1, const char* str2, unsigned int num);
    • 回传:

      • 若两个 string 是一样的→回传 0 == 0
      • 若 str1 在 str2 前面→ 回传一个负数 <0
      • 若 str1 在 str2 後面→ 回传一个正数 >0
    • 使用:

      char a[100] = "the";
      char b[100] = "they";
      char c[100] = "them";
      cout << strcmp(a, b) << endl;
      cout << strcmp(b, c) << " " << strcmp(b, c, 2); //後面的2就是比较前两个 char
      
  6. String copy

    • 功能:可以直接取代一串 substring(也就是改了很多个 char)

    • 呼叫:char* strcpy(char* dest, const char* source, unsigned int num); (把 source 的东西 copy 到 destination); 最後的 num 则是选source前面 前 num 个 char 传入 dest

    • 回传:char*(dest 的 address)

    • 使用:

      //instance1
      char a[100] = "watermelon";
      char b[100] = "orange";
      cout << a << "\n";
      strcpy(a, b)
      cout << a << "\n";
      
    • 需要注意: copy 完之後,只是把原本存在上面的东西取代,但如果後来的字比较短 → 原本字串後面的东西就会留下。像是上面的 copy 之後就会变成

      orange\0lon\0

    • 接着我们想要借刚刚的程序,来换"is"

      //instance2
      char a[100] = "this is an apple";
      char* p = strstr(a, "is");
      while(p != nullptr)
      {
      	strcpy(p, "IS");
      	p = strstr(p, "is")
      }
      cout << a;
      

      这个时候就只会跑出 thIS,原因是因为我们做 copy 的时候,IS後面加了 \0,所以cout只会读到 \0 就结束,如果你去 cout << a[5]; ,就会发现它会跑出 剩下的 is an apple

  7. String Concatenation

    • 功能:把两个字串串在一起(从\0开始把字串接上)

    • 呼叫:char* strcat(char* dest, const char* source, unsigned int num); (把 source 的东西 copy 到 destination);最後的 num 则是选source前面 前 num 个 char 传入 dest

    • 回传:char*(dest 的 address)

    • 使用:

      //instance1
      char a[100] = "watermelon";
      char b[100] = "orange";
      cout << a << "\n";
      strcat(a, b)
      cout << a << "\n";
      
      -
      w a t e r m e l o n 0
      o r a n g e 0
      - - - - - - - - - - - - - - - - - -
      w a t e r m e l o n 0 o r a n g e 0

      用图解释就非常的清楚了!

    注意! :要准备足够的空间,你 copy 或是 concatenation 的时候很可能会 踩到人家的地盘,也很可能会发生 run time error。因此要注意你的 destination要是一个阵列,不要只是一个 pointer 指向的空间。

Case Study:

  1. sorting names alphabetically

    • e.g.

      Before: John, Mikasa, Eren, Armin

      After: Armin, Eren, John, Mikasa

    • strategy:

      • using bubble sort:

        75469 → 57469 → 54769 → 54679

        这样可以确认最右边是最大 再去排左边 两个两个比较→换→比较→换

      • comparison: using strcmp()

      • swapping: using strcpy()

      const int CNT = 4;
      const int LEN = 10;
      
      void swapName(char* n1, char* n2)
      {
      	char temp[LEN] = {0};
      	strcpy(temp, n1);
      	strcpy(n1, n2);
      	strcpy(n2, temp);
      }
      
      int main()
      {
      	char name[CNT][LEN] = {"John", "Mikasa", "Eren", "Armin"};
      
      	for (int i = 0; i < CNT; ++i)
      		for (int j = 0; j < CNT - i - 1; ++j)
      			if (strcmp (name[j], name[j + 1]) > 0) // 大於0代表 str1 在 str2 後面
      				swapName(name[j], name[j + 1]);
      
      	for (int i = 0; i < CNT; ++i)
      	{
      		cout << name[i] << " ";
      	}
      	return 0;
      }
      

      improvement :

      可以 swap pointers

      const int CNT = 4;
      const int LEN = 10;
      
      void swapPtr(char*& p1, char*& p2) //*&对指标参数做 call by reference
      {
      	char* temp = p1;
      	p1 = p2;
      	p2 = temp;
      }
      
      int main()
      {
      	char name[CNT][LEN] = {"John", "Mikasa", "Eren", "Armin"};
      	char* ptr[CNT] = {name[0], name[1], name[2], name[3]};
      	// 用指标阵列储存 name 阵列的位置
      	for (int i = 0; i < CNT; ++i)
      		for (int j = 0; j < CNT - i - 1; ++j)
      			if (strcmp (ptr[j], ptr[j + 1]) > 0) // 大於0代表 str1 在 str2 後面
      				swapPtr(ptr[j], ptr[j + 1]);
      
      	for (int i = 0; i < CNT; ++i)
      	{
      		cout << ptr[i] << " ";
      	}
      	return 0;
      }
      
  2. Splitting a string into substrings

    区隔东西的人: delimiters

    被区隔开来的人:token

    input: www.im.ntu.edu.tw/~lckung/courses/PD16

    output: www im ntu edu tw ~lckung courses PD16

    当然可以一个一个来切,但是比较好的方式是:

    strtok():

    • 功能:
    • 宣告: char* strtok(char* str, const char* delimiters);
    const int CNT = 100;
    const int WORD_LEN = 50;
    const int SEN_LEN = 1000;
    
    int main()
    {
    	char url[SEN_LEN];
    	char delim[] = ".//";
    	char word[CNT][WORD_LEN] = {0};
    	int wordCnt = 0;
    	cin >> url;
    
    	char* start = strtok (url, delim);
    	while(start != nullptr)
    	{
    		strcpy(word[wordCnt], start);
    		wordCnt++;
    		start = strtok(nullptr, delim); // 传nullptr ->电脑会找刚刚纪录的那个起点,再继续找
    	}
    
    	for (int i = 0; i < wordCnt; ++i)
    		cout << word[i] << " ";
    	return 0;
    }
    

My Opinion

pointer 延伸出来的应用真的好多....

而且应用上往往要图像化才清楚..

希望我可以把它用的更熟一点 ?

Better and better.


<<:  Day 17- 依 NEWS 前台页面分析拆解後,逐步建立後台功能 (下) - 画面内容互动 - ASP.NET Web Forms C#

>>:  EP 23: SQLite DB in Android and iOS for TopStore App

不同API层级的专案

手边有老机器的智慧型手机使用者应该不在少数,如果从早期HTC风光年代开始就换用智慧型手机的话,我想,...

【Day10】会襄在DOM上面的Ref (•ิ_•ิ)?

Ref 其实就是 Reference(参考)的意思,也就是传值和传址里面的址 (参考位址) 我们的R...

成为工具人应有的工具包-04 VaultPasswordView

VaultPasswordView 今日来认识 VaultPasswordView ! 他是一个在 ...

[Day10]字符函数

字符函数,又分为大小写转换函数及字符处理函数。 大小写转换函数: 字符处理函数: 下篇会从日期单列函...

[常见的自然语言处理技术] Bag-of-Words Model:简单直观的统计语言模型

前言 当我们要使用机器学习演算法来解决自然语言的问题,我们首先必须将文字进行量化( quantifi...