#include <fstream>
#include <iostream>
#include "/usr/local/mysql/include/mysql/mysql.h"
const char mysqlServer[20] = "10.88.15.114";
//const char mysqlServer[20] = "10.88.15.114";
const char user[20]="web";
const char password[20]="sinatest";
const char database[20]="enterprise";
unsigned int port=3306;
using namespace std;
int main()
{
MYSQL myData;
MYSQL_RES *res;
MYSQL_FIELD *fd;
MYSQL_ROW row;
int i,j,rowCount = 0,colCount = 0;
string query;
mysql_init( &myData );
if(!mysql_real_connect( &myData, mysqlServer, user, password, database,port,NULL,0))
{
printf("connect mysql error!\n");
}
cout <<"the mysql is ok!\n";
query = "select email from enterprisemail_info where enterpriseid=100334 ";
if( mysql_query(&myData, query.c_str()) != 0 )
{
printf("query error!\n");
cout << query;
return 0;
}else{
cout << "mysql query run ok!\n";
}
res = mysql_store_result( &myData );
rowCount = (int) mysql_num_rows( res );
colCount = (int) mysql_num_fields( res );
//cout << colCount<<"\t"<<rowCount<<"\n";
for(i = 0; i < rowCount; i++)
{
//第一种方法取出@前面的用户名
row = mysql_fetch_row( res );
char buffer[1024],buffer2[1024];
strcpy(buffer,row[0]);
char* p = strstr(buffer,"@");
*p=0;
cout <<"strstr="<<buffer<<"\t";
//第二种方法取出@前面的用户名
strcpy(buffer2,row[0]);
char* p2 = strstr(buffer2,"@");
int points= p2-buffer2;
string buffer3=buffer2;
cout<<"substr="<<buffer3.substr(0, points)<<endl;
}
cout <<"\n";
}
分离url里的get参数的键值~
[root@test geturi]# gcc geturi.c
[root@test geturi]# ./a.out
para1=val1
para2=val2
para3=val3
________________________________________________________________
头文件:#include <string.h>
strchr() 用来查找某字符在字符串中首次出现的位置,其原型为:
char * strchr (const char *str, int c);
【参数】str 为要查找的字符串,c 为要查找的字符。
strchr() 将会找出 str 字符串中第一次出现的字符 c 的地址,然后将该地址返回。
摘自:http://c.biancheng.net/cpp/html/161.html
strchr与strstr函数,strchr函数的语法格式怎么用?它的作用与strstr函数有什么区别?
在C语言中 strchr 和 strstr函数都被包含在<string.h>头文件中,也就是要调用它们时要在程序前面包含<string.h>头文件,也就是写这个语句:#include<string.h>
strchr函数原型:char * strchr(char * str, int ch); 功能就是找出在字符串str中第一次出项字符ch的位置,找到就返回该字符位置的指针(也就是返回该字符在字符串中的地址的位置),找不到就返回空指针(就是 null)。
strstr 函数原型: char * strstr(char * str1,char * str2);功能就是找出在字符串str1中第一次出项字符串str2的位置(也就是说字符串sr1中要包含有字符串str2),找到就返回该字符串位置的指针(也就是返回字符串str2在字符串str1中的地址的位置),找不到就返回空指针(就是 null)。
它们一个是求一个字符在字符串中得位置,另一个是求一个字符串在另一个字符串中的位置。
这些在C语言书最后面中都有的,你要学会去多看看书,要会自己解决问题。学编程是要有耐心的,学久了就会懂了。
来自:http://zhidao.baidu.com/link?url=sANNu-OqOB2bmhvJuBEOC1n7S8oeuSOoBcx47GsH0UcScIr3uMFXiZsQNtTu1MNkJGnHuTtCjrBrNdDEcLy2eq
#include <iostream>
#include "/usr/local/mysql/include/mysql/mysql.h"
const char mysqlServer[20] = "10.88.15.114";
//const char mysqlServer[20] = "10.88.15.114";
const char user[20]="web";
const char password[20]="sinatest";
const char database[20]="enterprise";
unsigned int port=3306;
using namespace std;
int main()
{
MYSQL myData;
MYSQL_RES *res;
MYSQL_FIELD *fd;
MYSQL_ROW row;
int i,j,rowCount = 0,colCount = 0;
string query;
mysql_init( &myData );
if(!mysql_real_connect( &myData, mysqlServer, user, password, database,port,NULL,0))
{
printf("connect mysql error!\n");
}
cout <<"the mysql is ok!\n";
query = "select email from enterprisemail_info where enterpriseid=100334 ";
if( mysql_query(&myData, query.c_str()) != 0 )
{
printf("query error!\n");
cout << query;
return 0;
}else{
cout << "mysql query run ok!\n";
}
res = mysql_store_result( &myData );
rowCount = (int) mysql_num_rows( res );
colCount = (int) mysql_num_fields( res );
//cout << colCount<<"\t"<<rowCount<<"\n";
for(i = 0; i < rowCount; i++)
{
//第一种方法取出@前面的用户名
row = mysql_fetch_row( res );
char buffer[1024],buffer2[1024];
strcpy(buffer,row[0]);
char* p = strstr(buffer,"@");
*p=0;
cout <<"strstr="<<buffer<<"\t";
//第二种方法取出@前面的用户名
strcpy(buffer2,row[0]);
char* p2 = strstr(buffer2,"@");
int points= p2-buffer2;
string buffer3=buffer2;
cout<<"substr="<<buffer3.substr(0, points)<<endl;
}
cout <<"\n";
}
分离url里的get参数的键值~
[root@test geturi]# gcc geturi.c
[root@test geturi]# ./a.out
para1=val1
para2=val2
para3=val3
________________________________________________________________
头文件:#include <string.h>
strchr() 用来查找某字符在字符串中首次出现的位置,其原型为:
char * strchr (const char *str, int c);
【参数】str 为要查找的字符串,c 为要查找的字符。
strchr() 将会找出 str 字符串中第一次出现的字符 c 的地址,然后将该地址返回。
摘自:http://c.biancheng.net/cpp/html/161.html
strchr与strstr函数,strchr函数的语法格式怎么用?它的作用与strstr函数有什么区别?
在C语言中 strchr 和 strstr函数都被包含在<string.h>头文件中,也就是要调用它们时要在程序前面包含<string.h>头文件,也就是写这个语句:#include<string.h>
strchr函数原型:char * strchr(char * str, int ch); 功能就是找出在字符串str中第一次出项字符ch的位置,找到就返回该字符位置的指针(也就是返回该字符在字符串中的地址的位置),找不到就返回空指针(就是 null)。
strstr 函数原型: char * strstr(char * str1,char * str2);功能就是找出在字符串str1中第一次出项字符串str2的位置(也就是说字符串sr1中要包含有字符串str2),找到就返回该字符串位置的指针(也就是返回字符串str2在字符串str1中的地址的位置),找不到就返回空指针(就是 null)。
它们一个是求一个字符在字符串中得位置,另一个是求一个字符串在另一个字符串中的位置。
这些在C语言书最后面中都有的,你要学会去多看看书,要会自己解决问题。学编程是要有耐心的,学久了就会懂了。
来自:http://zhidao.baidu.com/link?url=sANNu-OqOB2bmhvJuBEOC1n7S8oeuSOoBcx47GsH0UcScIr3uMFXiZsQNtTu1MNkJGnHuTtCjrBrNdDEcLy2eq
char s1[4]="12",*ptr=s1;,ptr指向数组s1的 ,而*(ptr+1)的值是?
char s1[4]="12";
上式等价于 char s1[4]={'1','2','\0','\0'};
*ptr=s1;
ptr指向s1的首地址,即&s1[0],也可表示为&ptr[0]
*(ptr+1) 即为*(&ptr[1]),为*(&s1[1]), 值为2
由于数组名代表的就是数组的首元素的地址,作为char型指针的ptr被初始化为s1,所以ptr就指向了数组s1的首元素1,ptr的值就是& s1[0],即数组s1首元素s1[0]的地址。那么ptr+1就指向了数组s1的下一个元素,也就是第二个元素2,ptr+1的值就是&s1 [1],即数组s1的第二个元素的地址,*(ptr+1)对这个地址反引用,就取出这个地址里面的值,就是2
楼主要多看看你们的课本上关于指针的操作,取地址,反引用,数组名这些部分。
char s1[4]="12";
上式等价于 char s1[4]={'1','2','\0','\0'};
*ptr=s1;
ptr指向s1的首地址,即&s1[0],也可表示为&ptr[0]
*(ptr+1) 即为*(&ptr[1]),为*(&s1[1]), 值为2
由于数组名代表的就是数组的首元素的地址,作为char型指针的ptr被初始化为s1,所以ptr就指向了数组s1的首元素1,ptr的值就是& s1[0],即数组s1首元素s1[0]的地址。那么ptr+1就指向了数组s1的下一个元素,也就是第二个元素2,ptr+1的值就是&s1 [1],即数组s1的第二个元素的地址,*(ptr+1)对这个地址反引用,就取出这个地址里面的值,就是2
楼主要多看看你们的课本上关于指针的操作,取地址,反引用,数组名这些部分。
C++:
[codes=html]
char buffer[1024],buffer2[1024];
strcpy(buffer,row[0]);
char* p = strstr(buffer,"@");
*p=0;
cout <<buffer<<"\t";
[/codes]
Js实现前两位为**:
[code]<script langrage="javascript">
var str='372647693';
str = "**"+str.substr(2);
alert(str);
</script>[/code]
输出:**2647693
Js实现后两位为**:
[code]<script langrage="javascript">
var str='372647693';
var len=str.length;
str =str.substr(0,len-2)+ "**";
alert(str);
</script>[/code]输出:3726476**
如果是整形,需要转换为string:
[code]<script langrage="javascript">
var str=372647693;
str='**'+str.toString().substr(2);
alert(str);
</script>[/code]
如果是string,需要转换为整形:
fileNum = parseInt(fileNum);
如果是string,需要转换为浮点形:
fileNum = parseFloat(fileNum);
解决JS浮点数(小数)计算加减乘除的BUG:
http://www.cnblogs.com/binjoo/archive/2012/09/10/2679151.html
[codes=html]
char buffer[1024],buffer2[1024];
strcpy(buffer,row[0]);
char* p = strstr(buffer,"@");
*p=0;
cout <<buffer<<"\t";
[/codes]
Js实现前两位为**:
[code]<script langrage="javascript">
var str='372647693';
str = "**"+str.substr(2);
alert(str);
</script>[/code]
输出:**2647693
Js实现后两位为**:
[code]<script langrage="javascript">
var str='372647693';
var len=str.length;
str =str.substr(0,len-2)+ "**";
alert(str);
</script>[/code]输出:3726476**
如果是整形,需要转换为string:
[code]<script langrage="javascript">
var str=372647693;
str='**'+str.toString().substr(2);
alert(str);
</script>[/code]
如果是string,需要转换为整形:
fileNum = parseInt(fileNum);
如果是string,需要转换为浮点形:
fileNum = parseFloat(fileNum);
解决JS浮点数(小数)计算加减乘除的BUG:
http://www.cnblogs.com/binjoo/archive/2012/09/10/2679151.html
cp /usr/local/share/vim/vim80/vimrc_example.vim ~/.vimrc #from:http://www.cnblogs.com/unsea/archive/2012/11/17/2774267.html
1、装好 vim 后,拷贝 vimrc_example.vim 到 ~/ 下,改名为 .vimrc,并做一些适当的修改,最重要的是关闭 vi 兼容模式(set nocompatible),色彩就自己按喜好来吧。
2、ls -G 可显示彩色,不喜欢这个配色的话可自定一个环境变量(好像是LSCOLORS),看看man 就明白了
我配置的是
setenv LSCOLORS ExGxFxdxCxegedabagExEx
3. 一些配置
” 在行前显示行号.关闭显示请用”set nonu”
set nu
” 启用语法高亮
syntax on
” 根据文件类型启用缩进
filetype indent on
set autoindent
” 查找时忽略大小些
set ic
” 查找文本高亮
set hls
” Wrap text instead of being on one line
set lbr
” 更改缺省配色方案为delek
colorscheme delek
1、装好 vim 后,拷贝 vimrc_example.vim 到 ~/ 下,改名为 .vimrc,并做一些适当的修改,最重要的是关闭 vi 兼容模式(set nocompatible),色彩就自己按喜好来吧。
2、ls -G 可显示彩色,不喜欢这个配色的话可自定一个环境变量(好像是LSCOLORS),看看man 就明白了
我配置的是
setenv LSCOLORS ExGxFxdxCxegedabagExEx
3. 一些配置
” 在行前显示行号.关闭显示请用”set nonu”
set nu
” 启用语法高亮
syntax on
” 根据文件类型启用缩进
filetype indent on
set autoindent
” 查找时忽略大小些
set ic
” 查找文本高亮
set hls
” Wrap text instead of being on one line
set lbr
” 更改缺省配色方案为delek
colorscheme delek
#include<string>
回答者:tolemi_longing - 兵卒 一级 1-16 15:04
好像是在string.h中。
回答者:张_银 - 江湖新秀 四级 1-16 15:11
没有这个函数,不过在类string中有这个函数
#include <string>
#include <iostream>
using namespace std;
int main()
{
string ss("hello");
cout<<ss.substr(0, 2)<<endl;
}
回答者:tolemi_longing - 兵卒 一级 1-16 15:04
好像是在string.h中。
回答者:张_银 - 江湖新秀 四级 1-16 15:11
没有这个函数,不过在类string中有这个函数
#include <string>
#include <iostream>
using namespace std;
int main()
{
string ss("hello");
cout<<ss.substr(0, 2)<<endl;
}
原帖:[url=http://www.hur.cn/program/cc/cc15/200703/120754.html]http://www.hur.cn/program/cc/cc15/200703/120754.html[/url]
文件 I/O 在C++中比烤蛋糕简单多了。 在这篇文章里,我会详细解释ASCII和二进制文件的输入输出的每个细节,值得注意的是,所有这些都是用C++完成的。
一、ASCII 输出
为了使用下面的方法, 你必须包含头文件<fstream.h>(译者注:在标准C++中,已经使用<fstream>取代<fstream.h>,所有的C++标准头文件都是无后缀的。)。这是 <iostream.h>的一个扩展集, 提供有缓冲的文件输入输出操作. 事实上, <iostream.h> 已经被<fstream.h>包含了, 所以你不必包含所有这两个文件, 如果你想显式包含他们,那随便你。我们从文件操作类的设计开始, 我会讲解如何进行ASCII I/O操作。 如果你猜是"fstream," 恭喜你答对了! 但这篇文章介绍的方法,我们分别使用"ifstream"?和 "ofstream" 来作输入输出。
如果你用过标准控制台流"cin"?和 "cout," 那现在的事情对你来说很简单。 我们现在开始讲输出部分,首先声明一个类对象。ofstream fout;
这就可以了,不过你要打开一个文件的话, 必须像这样调用ofstream::open()。
fout.open("output.txt");
你也可以把文件名作为构造参数来打开一个文件.
ofstream fout("output.txt");
这是我们使用的方法, 因为这样创建和打开一个文件看起来更简单. 顺便说一句, 如果你要打开的文件不存在,它会为你创建一个, 所以不用担心文件创建的问题. 现在就输出到文件,看起来和"cout"的操作很像。 对不了解控制台输出"cout"的人, 这里有个例子。
int num = 150;
char name[] = "John Doe";
fout << "Here is a number: " << num << "\n";
fout << "Now here is a string: " << name << "\n";
现在保存文件,你必须关闭文件,或者回写文件缓冲. 文件关闭之后就不能再操作了, 所以只有在你不再操作这个文件的时候才调用它,它会自动保存文件。 回写缓冲区会在保持文件打开的情况下保存文件, 所以只要有必要就使用它。回写看起来像另一次输出, 然后调用方法关闭。像这样:
fout << flush; fout.close();
现在你用文本编辑器打开文件,内容看起来是这样:
Here is a number: 150 Now here is a string: John Doe
很简单吧! 现在继续文件输入, 需要一点技巧, 所以先确认你已经明白了流操作,对 "<<" 和">>" 比较熟悉了, 因为你接下来还要用到他们。继续…
二、ASCII 输入
输入和"cin" 流很像. 和刚刚讨论的输出流很像, 但你要考虑几件事情。在我们开始复杂的内容之前, 先看一个文本:
12 GameDev 15.45 L This is really awesome!
为了打开这个文件,你必须创建一个in-stream对象,?像这样。
ifstream fin("input.txt");
现在读入前四行. 你还记得怎么用"<<" 操作符往流里插入变量和符号吧?好,?在 "<<" (插入)?操作符之后,是">>" (提取) 操作符. 使用方法是一样的. 看这个代码片段.
int number;
float real;
char letter, word[8];
fin >> number; fin >> word; fin >> real; fin >> letter;
也可以把这四行读取文件的代码写为更简单的一行。
fin >> number >> word >> real >> letter;
它是如何运作的呢? 文件的每个空白之后, ">>" 操作符会停止读取内容, 直到遇到另一个>>操作符. 因为我们读取的每一行都被换行符分割开(是空白字符), ">>" 操作符只把这一行的内容读入变量。这就是这个代码也能正常工作的原因。但是,可别忘了文件的最后一行。
This is really awesome!
如果你想把整行读入一个char数组, 我们没办法用">>"?操作符,因为每个单词之间的空格(空白字符)会中止文件的读取。为了验证:
char sentence[101]; fin >> sentence;
我们想包含整个句子, "This is really awesome!" 但是因为空白, 现在它只包含了"This". 很明显, 肯定有读取整行的方法, 它就是getline()。这就是我们要做的。
fin.getline(sentence, 100);
这是函数参数. 第一个参数显然是用来接受的char数组. 第二个参数是在遇到换行符之前,数组允许接受的最大元素数量. 现在我们得到了想要的结果:“This is really awesome!”。
你应该已经知道如何读取和写入ASCII文件了。但我们还不能罢休,因为二进制文件还在等着我们。
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
ofstream fout("output.txt");
int num = 150;
char name[] = "John Doe";
fout << "Here is a number: " << num << "\n";
fout << "Now here is a string: " << name << "\n";
}
文件 I/O 在C++中比烤蛋糕简单多了。 在这篇文章里,我会详细解释ASCII和二进制文件的输入输出的每个细节,值得注意的是,所有这些都是用C++完成的。
一、ASCII 输出
为了使用下面的方法, 你必须包含头文件<fstream.h>(译者注:在标准C++中,已经使用<fstream>取代<fstream.h>,所有的C++标准头文件都是无后缀的。)。这是 <iostream.h>的一个扩展集, 提供有缓冲的文件输入输出操作. 事实上, <iostream.h> 已经被<fstream.h>包含了, 所以你不必包含所有这两个文件, 如果你想显式包含他们,那随便你。我们从文件操作类的设计开始, 我会讲解如何进行ASCII I/O操作。 如果你猜是"fstream," 恭喜你答对了! 但这篇文章介绍的方法,我们分别使用"ifstream"?和 "ofstream" 来作输入输出。
如果你用过标准控制台流"cin"?和 "cout," 那现在的事情对你来说很简单。 我们现在开始讲输出部分,首先声明一个类对象。ofstream fout;
这就可以了,不过你要打开一个文件的话, 必须像这样调用ofstream::open()。
fout.open("output.txt");
你也可以把文件名作为构造参数来打开一个文件.
ofstream fout("output.txt");
这是我们使用的方法, 因为这样创建和打开一个文件看起来更简单. 顺便说一句, 如果你要打开的文件不存在,它会为你创建一个, 所以不用担心文件创建的问题. 现在就输出到文件,看起来和"cout"的操作很像。 对不了解控制台输出"cout"的人, 这里有个例子。
int num = 150;
char name[] = "John Doe";
fout << "Here is a number: " << num << "\n";
fout << "Now here is a string: " << name << "\n";
现在保存文件,你必须关闭文件,或者回写文件缓冲. 文件关闭之后就不能再操作了, 所以只有在你不再操作这个文件的时候才调用它,它会自动保存文件。 回写缓冲区会在保持文件打开的情况下保存文件, 所以只要有必要就使用它。回写看起来像另一次输出, 然后调用方法关闭。像这样:
fout << flush; fout.close();
现在你用文本编辑器打开文件,内容看起来是这样:
Here is a number: 150 Now here is a string: John Doe
很简单吧! 现在继续文件输入, 需要一点技巧, 所以先确认你已经明白了流操作,对 "<<" 和">>" 比较熟悉了, 因为你接下来还要用到他们。继续…
二、ASCII 输入
输入和"cin" 流很像. 和刚刚讨论的输出流很像, 但你要考虑几件事情。在我们开始复杂的内容之前, 先看一个文本:
12 GameDev 15.45 L This is really awesome!
为了打开这个文件,你必须创建一个in-stream对象,?像这样。
ifstream fin("input.txt");
现在读入前四行. 你还记得怎么用"<<" 操作符往流里插入变量和符号吧?好,?在 "<<" (插入)?操作符之后,是">>" (提取) 操作符. 使用方法是一样的. 看这个代码片段.
int number;
float real;
char letter, word[8];
fin >> number; fin >> word; fin >> real; fin >> letter;
也可以把这四行读取文件的代码写为更简单的一行。
fin >> number >> word >> real >> letter;
它是如何运作的呢? 文件的每个空白之后, ">>" 操作符会停止读取内容, 直到遇到另一个>>操作符. 因为我们读取的每一行都被换行符分割开(是空白字符), ">>" 操作符只把这一行的内容读入变量。这就是这个代码也能正常工作的原因。但是,可别忘了文件的最后一行。
This is really awesome!
如果你想把整行读入一个char数组, 我们没办法用">>"?操作符,因为每个单词之间的空格(空白字符)会中止文件的读取。为了验证:
char sentence[101]; fin >> sentence;
我们想包含整个句子, "This is really awesome!" 但是因为空白, 现在它只包含了"This". 很明显, 肯定有读取整行的方法, 它就是getline()。这就是我们要做的。
fin.getline(sentence, 100);
这是函数参数. 第一个参数显然是用来接受的char数组. 第二个参数是在遇到换行符之前,数组允许接受的最大元素数量. 现在我们得到了想要的结果:“This is really awesome!”。
你应该已经知道如何读取和写入ASCII文件了。但我们还不能罢休,因为二进制文件还在等着我们。
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main()
{
ofstream fout("output.txt");
int num = 150;
char name[] = "John Doe";
fout << "Here is a number: " << num << "\n";
fout << "Now here is a string: " << name << "\n";
}
http://www.cftea.com/c/648.asp
曾经看到一篇文章,说删除数组中某一元素的步骤应该这样:
1、delete
2、元素前移
3、重设数组length
其实使用数组方法splice可以一步完成。
arrayObject.splice(start, deleteCount, [item1[, item2[, . . . [,itemN]]]])
start 要删除的开始位置
deleteCount 要删除的个数
item1, item2, . . ., itemN 如果指定此值,可以在删除的位置插入元素。如果插入的是数组,则只取第一个元素
数组对象仅有一个属性,这个属性就是length,它表示数组的长度,即其中元素的个数。因为数组的索引总是由0开始,所以一个数组的上下限分别是:0和length-1。和其他大多数语言不同的是,JavaScript数组的length属性是可变的,这一点需要特别注意。当length属性被设置得更大时,整个数组的状态事实上不会发生变化,仅仅是length属性变大;当length属性被设置得比原来小时,则原先数组中索引大于或等于 length的元素的值全部被丢失。下面是演示改变length属性的例子:
var arr=[12,23,5,3,25,98,76,54,56,76];
//定义了一个包含10个数字的数组
alert(arr.length); //显示数组的长度10
arr.length=12; //增大数组的长度
alert(arr.length); //显示数组的长度已经变为12
alert(arr[8]); //显示第9个元素的值,为56
arr.length=5; //将数组的长度减少到5,索引等于或超过5的元素被丢弃
alert(arr[8]); //显示第9个元素已经变为"undefined"
arr.length=10; //将数组长度恢复为10
alert(arr[8]); //虽然长度被恢复为10,但第9个元素却无法收回,显示"undefined"
由上面的代码我们可以清楚的看到length属性的性质。但length对象不仅可以显式的设置,它也有可能被隐式修改。JavaScript中可以使用一个未声明过的变量,同样,也可以使用一个未定义的数组元素(指索引超过或等于length的元素),这时,length属性的值将被设置为所使用元素索引的值加1。例如下面的代码:
var arr=[12,23,5,3,25,98,76,54,56,76];
alert(arr.length);
arr[15]=34;
alert(arr.length);
代码中同样是先定义了一个包含10个数字的数组,通过alert语句可以看出其长度为10。随后使用了索引为15的元素,将其赋值为15,即arr [15]=34,这时再用alert语句输出数组的长度,得到的是16。无论如何,对于习惯于强类型编程的开发人员来说,这是一个很令人惊讶的特性。事实上,使用new Array()形式创建的数组,其初始长度就是为0,正是对其中未定义元素的操作,才使数组的长度发生变化。
由上面的介绍可以看到,length属性是如此的神奇,利用它可以方便的增加或者减少数组的容量。因此对length属性的深入了解,有助于在开发过程中灵活运用。
曾经看到一篇文章,说删除数组中某一元素的步骤应该这样:
1、delete
2、元素前移
3、重设数组length
其实使用数组方法splice可以一步完成。
arrayObject.splice(start, deleteCount, [item1[, item2[, . . . [,itemN]]]])
start 要删除的开始位置
deleteCount 要删除的个数
item1, item2, . . ., itemN 如果指定此值,可以在删除的位置插入元素。如果插入的是数组,则只取第一个元素
数组对象仅有一个属性,这个属性就是length,它表示数组的长度,即其中元素的个数。因为数组的索引总是由0开始,所以一个数组的上下限分别是:0和length-1。和其他大多数语言不同的是,JavaScript数组的length属性是可变的,这一点需要特别注意。当length属性被设置得更大时,整个数组的状态事实上不会发生变化,仅仅是length属性变大;当length属性被设置得比原来小时,则原先数组中索引大于或等于 length的元素的值全部被丢失。下面是演示改变length属性的例子:
var arr=[12,23,5,3,25,98,76,54,56,76];
//定义了一个包含10个数字的数组
alert(arr.length); //显示数组的长度10
arr.length=12; //增大数组的长度
alert(arr.length); //显示数组的长度已经变为12
alert(arr[8]); //显示第9个元素的值,为56
arr.length=5; //将数组的长度减少到5,索引等于或超过5的元素被丢弃
alert(arr[8]); //显示第9个元素已经变为"undefined"
arr.length=10; //将数组长度恢复为10
alert(arr[8]); //虽然长度被恢复为10,但第9个元素却无法收回,显示"undefined"
由上面的代码我们可以清楚的看到length属性的性质。但length对象不仅可以显式的设置,它也有可能被隐式修改。JavaScript中可以使用一个未声明过的变量,同样,也可以使用一个未定义的数组元素(指索引超过或等于length的元素),这时,length属性的值将被设置为所使用元素索引的值加1。例如下面的代码:
var arr=[12,23,5,3,25,98,76,54,56,76];
alert(arr.length);
arr[15]=34;
alert(arr.length);
代码中同样是先定义了一个包含10个数字的数组,通过alert语句可以看出其长度为10。随后使用了索引为15的元素,将其赋值为15,即arr [15]=34,这时再用alert语句输出数组的长度,得到的是16。无论如何,对于习惯于强类型编程的开发人员来说,这是一个很令人惊讶的特性。事实上,使用new Array()形式创建的数组,其初始长度就是为0,正是对其中未定义元素的操作,才使数组的长度发生变化。
由上面的介绍可以看到,length属性是如此的神奇,利用它可以方便的增加或者减少数组的容量。因此对length属性的深入了解,有助于在开发过程中灵活运用。
http://hi.baidu.com/kofeffect/blog/item/5b71cefb9f6f51126d22ebd1.html
最近开始从事搜索引擎的工作,所以又重新开始了c/c++的旅程,时隔4年
不得不复习一下c/c++其中的内容,以下内容有网上别的朋友发表的,也有我自己总结的.
1. read
#include
ssize_t read(int filedes, void *buf, size_t nbytes);
返回值:读取到的字节数;0(读到 EOF);-1(出错)
read 函数从 filedes 指定的已打开文件中读取 nbytes 字节到 buf 中。以下几种情况会导致读取到的字节数小于 nbytes :
A. 读取普通文件时,读到文件末尾还不够 nbytes 字节。例如:如果文件只有 30 字节,而我们想读取 100 字节,那么实际读到的只有 30 字节,read 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0 。
B. 从终端设备(terminal device)读取时,一般情况下每次只能读取一行。
C. 从网络读取时,网络缓存可能导致读取的字节数小于 nbytes 字节。
D. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 nbytes 。
E. 从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。
F. 在读取了部分数据时被信号中断。
读操作始于 cfo 。在成功返回之前,cfo 增加,增量为实际读取到的字节数。
2. write
#include
ssize_t write(int filedes, const void *buf, size_t nbytes);
返回值:写入文件的字节数(成功);-1(出错)
write 函数向 filedes 中写入 nbytes 字节数据,数据来源为 buf 。返回值一般总是等于 nbytes,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。
对于普通文件,写操作始于 cfo 。如果打开文件时使用了 O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo 增加,增量为实际写入的字节数。
int main(int argc, char *argv[])
{
int fd;
int nread;
//void *buf;
char buff[512];
//buf=&buff;
//printf ("%d",buf);
char *dev ="/dev/ttyS1";
/* 打开指定的串口设备 */
fd = OpenDev(dev);
if (fd>0) {
set_speed(fd);
}
else {
printf("Can't Open Serial Port!\n");
exit(1);
}
/* 设置串口的参数 */
if (set_para(fd)== 1) {
printf("Set Parity Error\n");
exit(1);
}
/* 在后台守候,检查缓冲区内是否有内容,并将缓冲区的
内容显示出来*/
while(1) {
int nread;
while ((nread = read(fd,buff,512))>0) {
printf ("nnread is %d\n",nread);
buff[nread+1]='\0';
int i;
for (i=0;i printf ("buff is %c\n",buff[i]);
}
printf ("buf is %s\n",buff);
}
}
}
上面是我写的一段程序,目的就是将串口收到的字符显示在控制台上。
现在我可以打印出nnread is "收到的字符的个数",但是无法打印出接收到的字符。
我想打印buff这个数组。但是无论如何也打印不出来。
我把这个数组定义成int形的。然后就可以打印出一些数字,我猜可能是ascii码值。
我没有看懂read函数的参数的具体的用法,特别是第二项那个指针。
最近开始从事搜索引擎的工作,所以又重新开始了c/c++的旅程,时隔4年
不得不复习一下c/c++其中的内容,以下内容有网上别的朋友发表的,也有我自己总结的.
1. read
#include
ssize_t read(int filedes, void *buf, size_t nbytes);
返回值:读取到的字节数;0(读到 EOF);-1(出错)
read 函数从 filedes 指定的已打开文件中读取 nbytes 字节到 buf 中。以下几种情况会导致读取到的字节数小于 nbytes :
A. 读取普通文件时,读到文件末尾还不够 nbytes 字节。例如:如果文件只有 30 字节,而我们想读取 100 字节,那么实际读到的只有 30 字节,read 函数返回 30 。此时再使用 read 函数作用于这个文件会导致 read 返回 0 。
B. 从终端设备(terminal device)读取时,一般情况下每次只能读取一行。
C. 从网络读取时,网络缓存可能导致读取的字节数小于 nbytes 字节。
D. 读取 pipe 或者 FIFO 时,pipe 或 FIFO 里的字节数可能小于 nbytes 。
E. 从面向记录(record-oriented)的设备读取时,某些面向记录的设备(如磁带)每次最多只能返回一个记录。
F. 在读取了部分数据时被信号中断。
读操作始于 cfo 。在成功返回之前,cfo 增加,增量为实际读取到的字节数。
2. write
#include
ssize_t write(int filedes, const void *buf, size_t nbytes);
返回值:写入文件的字节数(成功);-1(出错)
write 函数向 filedes 中写入 nbytes 字节数据,数据来源为 buf 。返回值一般总是等于 nbytes,否则就是出错了。常见的出错原因是磁盘空间满了或者超过了文件大小限制。
对于普通文件,写操作始于 cfo 。如果打开文件时使用了 O_APPEND,则每次写操作都将数据写入文件末尾。成功写入后,cfo 增加,增量为实际写入的字节数。
int main(int argc, char *argv[])
{
int fd;
int nread;
//void *buf;
char buff[512];
//buf=&buff;
//printf ("%d",buf);
char *dev ="/dev/ttyS1";
/* 打开指定的串口设备 */
fd = OpenDev(dev);
if (fd>0) {
set_speed(fd);
}
else {
printf("Can't Open Serial Port!\n");
exit(1);
}
/* 设置串口的参数 */
if (set_para(fd)== 1) {
printf("Set Parity Error\n");
exit(1);
}
/* 在后台守候,检查缓冲区内是否有内容,并将缓冲区的
内容显示出来*/
while(1) {
int nread;
while ((nread = read(fd,buff,512))>0) {
printf ("nnread is %d\n",nread);
buff[nread+1]='\0';
int i;
for (i=0;i
}
printf ("buf is %s\n",buff);
}
}
}
上面是我写的一段程序,目的就是将串口收到的字符显示在控制台上。
现在我可以打印出nnread is "收到的字符的个数",但是无法打印出接收到的字符。
我想打印buff这个数组。但是无论如何也打印不出来。
我把这个数组定义成int形的。然后就可以打印出一些数字,我猜可能是ascii码值。
我没有看懂read函数的参数的具体的用法,特别是第二项那个指针。
错误在fwrite(file_contents,sizeof(file_content),1,fp);这一行.
第2个参数应该是写入块的大小.
应该是 (size_t)strlen(file_contens) 或者file_content.size()
======= 2008-03-11 13:33:25 您在来信中写道:=======
>yifeng,您好!
>
> #include
>#include
>using namespace std;
>int main()
>{
> string file_content="This is a test\n";
> const char* file_contents=file_content.c_str();
> FILE *fp;
> fp=fopen("/tmp/stu_list","wb");
> fwrite(file_contents,sizeof(file_content),1,fp);
> fclose(fp);
>
>}
>
>1.为何只有this是不是空格给截断了?
>2.为何要用const修饰
>3.如何才能把string给写入到文件中.
第2个参数应该是写入块的大小.
应该是 (size_t)strlen(file_contens) 或者file_content.size()
======= 2008-03-11 13:33:25 您在来信中写道:=======
>yifeng,您好!
>
> #include
>#include
>using namespace std;
>int main()
>{
> string file_content="This is a test\n";
> const char* file_contents=file_content.c_str();
> FILE *fp;
> fp=fopen("/tmp/stu_list","wb");
> fwrite(file_contents,sizeof(file_content),1,fp);
> fclose(fp);
>
>}
>
>1.为何只有this是不是空格给截断了?
>2.为何要用const修饰
>3.如何才能把string给写入到文件中.
Fcitx
sudo apt-get install im-switch fcitx
sudo im-switch -s fcitx -z default
im-switch -s fcitx -z default #注意:不加sudo
完成设置最好重启一下X,输入法就生效了
某些情况下可能,在安装了fcitx输入法以后可能会出现和SCIM并存的问题,解决方法如下:
sudo gedit /usr/lib/gtk-2.0/2.10.0/immodule-files.d/scim-gtk2-immodule.immodules
将内容改为如下:
# automatically generated by dh_gtkmodules, do not edit
“/usr/lib/gtk-2.0/2.10.0/immodules/im-scim.so”
“scim” “SCIM Input Method” “scim” “/usr/share/locale” “ja:ko”
#就是将最后一行中的"ja:ko:zh"修改为"ja:ko",这样修改以后,scim在中文环境下将不被启动
更多设置,请访问中文输入法fcitx 以及 英文Locale下使用中文输入法
[编辑] 激活输入法
* 重新启动Xwindow完成。按 Ctrl + 空格 键激活输入法。
sudo apt-get install im-switch fcitx
sudo im-switch -s fcitx -z default
im-switch -s fcitx -z default #注意:不加sudo
完成设置最好重启一下X,输入法就生效了
某些情况下可能,在安装了fcitx输入法以后可能会出现和SCIM并存的问题,解决方法如下:
sudo gedit /usr/lib/gtk-2.0/2.10.0/immodule-files.d/scim-gtk2-immodule.immodules
将内容改为如下:
# automatically generated by dh_gtkmodules, do not edit
“/usr/lib/gtk-2.0/2.10.0/immodules/im-scim.so”
“scim” “SCIM Input Method” “scim” “/usr/share/locale” “ja:ko”
#就是将最后一行中的"ja:ko:zh"修改为"ja:ko",这样修改以后,scim在中文环境下将不被启动
更多设置,请访问中文输入法fcitx 以及 英文Locale下使用中文输入法
[编辑] 激活输入法
* 重新启动Xwindow完成。按 Ctrl + 空格 键激活输入法。
添加Ubuntu的源
Feisty (7.04) 版本
* 请参阅 [[[Ubuntu简介]]]
sudo cp /etc/apt/sources.list /etc/apt/sources.list_backup
sudo gedit /etc/apt/sources.list
* 用以下内容替换文件中的所有内容
deb http://archive.ubuntu.com/ubuntu/ feisty main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ feisty-security main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ feisty-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ feisty-backports main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ feisty-proposed main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu-cn/ feisty main restricted universe multiverse
这里你也可以直接使用更快速的ubuntu.cn99.com的源(推荐):
deb http://ubuntu.cn99.com/ubuntu/ feisty main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ feisty-security main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ feisty-updates main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ feisty-backports main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ feisty-proposed main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu-cn/ feisty main restricted universe multiverse
这里你还可以使用速度也非常快的的mirror.lupaworld.com的源:
deb http://mirror.lupaworld.com/ubuntu/archive/ feisty main restricted universe multiverse
deb http://mirror.lupaworld.com/ubuntu/archive/ feisty-security main restricted universe multiverse
deb http://mirror.lupaworld.com/ubuntu/archive/ feisty-updates main restricted universe multiverse
deb http://mirror.lupaworld.com/ubuntu/archive/ feisty-backports main restricted universe multiverse
deb http://mirror.lupaworld.com/ubuntu/archive/ feisty-proposed main restricted universe multiverse
deb http://mirror.lupaworld.com/ubuntu/ubuntu-cn/ feisty main restricted universe multiverse
如果是教育网用户请使用上海交通大学的源:
deb http://ftp.sjtu.edu.cn/ubuntu/ feisty main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ feisty-security main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ feisty-updates main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ feisty-backports main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ feisty-proposed main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu-cn/ feisty main multiverse restricted universe bleeding
上海电信的一个源:
deb http://mirror.rootguide.org/ubuntu/ feisty main restricted universe multiverse
deb http://mirror.rootguide.org/ubuntu/ feisty-proposed main restricted universe multiverse
deb http://mirror.rootguide.org/ubuntu/ feisty-updates main restricted universe multiverse
deb http://mirror.rootguide.org/ubuntu/ feisty-backports main restricted universe multiverse
deb http://mirror.rootguide.org/ubuntu/ feisty-security main restricted universe multiverse
deb http://mirror.rootguide.org/ubuntu-cn/ feisty main restricted universe multiverse
* 保存编辑好的文件
sudo apt-get update
Feisty (7.04) 版本
* 请参阅 [[[Ubuntu简介]]]
sudo cp /etc/apt/sources.list /etc/apt/sources.list_backup
sudo gedit /etc/apt/sources.list
* 用以下内容替换文件中的所有内容
deb http://archive.ubuntu.com/ubuntu/ feisty main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ feisty-security main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ feisty-updates main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ feisty-backports main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu/ feisty-proposed main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu-cn/ feisty main restricted universe multiverse
这里你也可以直接使用更快速的ubuntu.cn99.com的源(推荐):
deb http://ubuntu.cn99.com/ubuntu/ feisty main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ feisty-security main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ feisty-updates main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ feisty-backports main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu/ feisty-proposed main restricted universe multiverse
deb http://ubuntu.cn99.com/ubuntu-cn/ feisty main restricted universe multiverse
这里你还可以使用速度也非常快的的mirror.lupaworld.com的源:
deb http://mirror.lupaworld.com/ubuntu/archive/ feisty main restricted universe multiverse
deb http://mirror.lupaworld.com/ubuntu/archive/ feisty-security main restricted universe multiverse
deb http://mirror.lupaworld.com/ubuntu/archive/ feisty-updates main restricted universe multiverse
deb http://mirror.lupaworld.com/ubuntu/archive/ feisty-backports main restricted universe multiverse
deb http://mirror.lupaworld.com/ubuntu/archive/ feisty-proposed main restricted universe multiverse
deb http://mirror.lupaworld.com/ubuntu/ubuntu-cn/ feisty main restricted universe multiverse
如果是教育网用户请使用上海交通大学的源:
deb http://ftp.sjtu.edu.cn/ubuntu/ feisty main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ feisty-security main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ feisty-updates main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ feisty-backports main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu/ feisty-proposed main multiverse restricted universe
deb http://ftp.sjtu.edu.cn/ubuntu-cn/ feisty main multiverse restricted universe bleeding
上海电信的一个源:
deb http://mirror.rootguide.org/ubuntu/ feisty main restricted universe multiverse
deb http://mirror.rootguide.org/ubuntu/ feisty-proposed main restricted universe multiverse
deb http://mirror.rootguide.org/ubuntu/ feisty-updates main restricted universe multiverse
deb http://mirror.rootguide.org/ubuntu/ feisty-backports main restricted universe multiverse
deb http://mirror.rootguide.org/ubuntu/ feisty-security main restricted universe multiverse
deb http://mirror.rootguide.org/ubuntu-cn/ feisty main restricted universe multiverse
* 保存编辑好的文件
sudo apt-get update
断断续续的学习C++也有三两个月了,(2006年8月下旬起)。很感谢C++,可以说它是
一种催化剂,让我自身的修养和人生观都有很大的提高。(当然这也离不开个人自身
的基础)
在C++大门前,我只能算是刚刚看到这个大门的台阶,但这段小小的距离让我受益颇
浅,现将其中心得奉献出来,欢迎老鸟指正,以供刚接触的新人参考。心得中,尽量减少对语言技术上的评论,以另一种心理来分析指正学习。
注:别人的心得和经验你可以学习,但是不可以完全照而搬之。因为每个人的心理,生活方式,看待事物及个人志向立场等等其他都不同,我的方法不一定适合你,但是可以学习借鉴。
在写之前,想先写一句话,千万不要以为你是天才,所谓天才即胜别人N倍的人,如果你真认为你是天才,请以天才的标准来要求自己,将自己付出和别人的比例以N:1来进行。
学习编程无外乎跟三者打交道,第一自己,第二编程,第三计算机。
第一自己:
心态:很多朋友都是在刚步入大学校门开始接触计算机或者其他。大学是一个很让人迷茫颓废的地方,可能你在高中踌躇满志,但在大学的管理机制下却心态渐糜。能够在大学(大一)里找到自己人生之志的人才是通过了大学的考验,能够真正迈上社会的人。
目标要明确,不要得过且过,不要追逐热门的东西。不管在什么时候,心里都要有自己的信念和理想,不要被其他东西左右了自己。
要从兴趣和竞争出发,因为这才是你产生动力源的地方。学东西时,要让自己先喜欢上它,要学会享受学习它的时间,喜欢乔丹的都知道,乔老爷子最常说的一句就是要享受比赛的时间。因为只有你喜欢上了它,享受了它,才不会被它所产生的压力压倒自己,不要产生一种厌恶的心理。而且心态要调整,不要一味的蛮学,死学,在没有什么兴趣学的时候可以先玩几天,在回过头来,或者用其他的方式来转换。
要抱着一种十年方能磨一剑的心理,扎扎实实从基础开始,不要为高速开发的一些现象所迷惑,那些都只是软件的功劳。我个人认为搞编程的英语和数学很重要,大家一定要时时的抓,至于怎么抓那就是另外一回事了,当然其他的知识也要汲取,可以多接触点天文,地理,人文方面的东西来补充自己。
转换的生活方式。要学会生活,在人的一生中,都会遇到一些很不开心的事,或者大大小小的意外,孙子兵法里面一句大意就是说,战场上要会将劣势转成优势,即最大的劣势很可能就是你的优势所在。一个打击你可以把他变为前进的动力,不必为了一点挫折和捶胸顿足,不必为了一点小事争吵不休。空闲的时候可以躺下来看看天,你就会明白自强不息,厚德载物之理。(看到一些为了失恋或者其他而堕落的人真的很难受和可惜)
要给人以帮助。别人有困难时或者一些举手之劳在自己解决范围内的尽力帮之,这对自己不会损失什么,相反在对方心里能够树立你的形象和增加好感。记住,给人之恩时也是给你自己铺路,这个世界不是靠你一个人可改变的。
第二编程:
语言的争论。几个月来,看到最有争议的帖子即关于某种语言好坏得,往往能顶个几百几千的人气。试问,中国牌子有几个不被骂的,试问,世界上哪部影片没人说他坏话的?术业有专攻,各司其职,每个语言的出现肯定都有自己价值和领域的范围,不同的只是价值和领域的高级和大小。就像高,矮,胖,瘦组成一个“型”。但是技术是进步的,这个进步也是在旧的基础上。放心,技术再飞跃也不会今天奔I,明天扣肉的迅速,在学习的同时也要时时关注整个语言界的走势(貌似哪个伟人说的与时俱进),保持一种同步曲线的前进。
语言能干什么?能问这句话,很欣慰。表明你是从基础学起才会问出这样的话来。但换而言之你一开始对这门语言调查的信息不足。庙算者胜,对自己身边的事物了解是非常重要的,这也能说明说你的教材或者教导你的人不够充分。如果是我对新手交流,我会花足时间让他明白这门语言是什么,能干什么,整个体系等等。这样在学的时候,对同一条语句上的理解你可能跟别人就不一样了, 因为你知道这门语言的体系架构,你可以从更多的特性角度去挖掘使用它。
提高自己对语言阅读能力。如何快速的进步?可以告诉你没有一步登天的办法,但是也可以告诉你如何让你快速掌握你所学的技术。多读他人代码,多领悟他人思维,多给自己出难题,多几种方法来解问题,多将语言的思想和身边的事物联系起来。想想你是怎么掌握普通话的,方法就不具体说了。
编程的思维。计算机是充满诱惑的东西,很多人都说热爱计算机,那只是它的表面,你热爱的只是他的一种扩展性的东西。能学计算机我很高兴,因为计算机从某种意义上来说将空间的距离给消除掉了。这里没有流血,没有牺牲。你可以以一种艺术家的思维来塑造它,可以以侦探的逻辑思维来破解它,可以以一种战争狂人的思维来摧毁它。你可以将你的这种思维体现在代码和整个布局里,不要惊异别人代码思维的异风,你也有自己的思维风格,你也可以,你也可以用自己代码让别人惊异。记住你学东西时更多的要体会他的思维,比如数学,微积分,离散,无数的定理,你学完以后可能永远都用不到,但是你可能有这种感觉,在你碰到某一问题时,这一问题的性质和你学到的东西很类似,但是中间就是差了一点东西,对了,这就是它思维上的一种运用,一种抽象,一种转换。
第三计算机
前面从说了很多,但是基本都是从理论上来说。因为我也没走到技术的高端(刚窥门槛还不到),说技术只是班门弄斧,但是我想我一些入门的方法还是可以和大家共享的。
前面提过,学一个东西首先要了解它,这样你才能知道你学的东西特性这个词。
不管你是什么目的学它的,你都要让自己喜欢上它。
建议新手们先从微机原理入手,弄懂后,再在自己脑中重绘一遍计算机的布局,学的时候,尽量将自己模拟的布局和语言给联系起来
接着学习英语,和数学,切记,学习数学是锻炼自己的逻辑,抽象和转换。
搞定这三个后再重新定位一下自己对计算机哪部分感兴趣,再从前辈中那里获取一些信息,这里教材和入门的准备很重要。一开始我也走了很多弯路,现在只能狂补英语和数学。
遇到问题时先不要问他人(常谈的问题了),应先以自己的所掌握的知识和思维来推敲问题,做个大胆的假设。
一种催化剂,让我自身的修养和人生观都有很大的提高。(当然这也离不开个人自身
的基础)
在C++大门前,我只能算是刚刚看到这个大门的台阶,但这段小小的距离让我受益颇
浅,现将其中心得奉献出来,欢迎老鸟指正,以供刚接触的新人参考。心得中,尽量减少对语言技术上的评论,以另一种心理来分析指正学习。
注:别人的心得和经验你可以学习,但是不可以完全照而搬之。因为每个人的心理,生活方式,看待事物及个人志向立场等等其他都不同,我的方法不一定适合你,但是可以学习借鉴。
在写之前,想先写一句话,千万不要以为你是天才,所谓天才即胜别人N倍的人,如果你真认为你是天才,请以天才的标准来要求自己,将自己付出和别人的比例以N:1来进行。
学习编程无外乎跟三者打交道,第一自己,第二编程,第三计算机。
第一自己:
心态:很多朋友都是在刚步入大学校门开始接触计算机或者其他。大学是一个很让人迷茫颓废的地方,可能你在高中踌躇满志,但在大学的管理机制下却心态渐糜。能够在大学(大一)里找到自己人生之志的人才是通过了大学的考验,能够真正迈上社会的人。
目标要明确,不要得过且过,不要追逐热门的东西。不管在什么时候,心里都要有自己的信念和理想,不要被其他东西左右了自己。
要从兴趣和竞争出发,因为这才是你产生动力源的地方。学东西时,要让自己先喜欢上它,要学会享受学习它的时间,喜欢乔丹的都知道,乔老爷子最常说的一句就是要享受比赛的时间。因为只有你喜欢上了它,享受了它,才不会被它所产生的压力压倒自己,不要产生一种厌恶的心理。而且心态要调整,不要一味的蛮学,死学,在没有什么兴趣学的时候可以先玩几天,在回过头来,或者用其他的方式来转换。
要抱着一种十年方能磨一剑的心理,扎扎实实从基础开始,不要为高速开发的一些现象所迷惑,那些都只是软件的功劳。我个人认为搞编程的英语和数学很重要,大家一定要时时的抓,至于怎么抓那就是另外一回事了,当然其他的知识也要汲取,可以多接触点天文,地理,人文方面的东西来补充自己。
转换的生活方式。要学会生活,在人的一生中,都会遇到一些很不开心的事,或者大大小小的意外,孙子兵法里面一句大意就是说,战场上要会将劣势转成优势,即最大的劣势很可能就是你的优势所在。一个打击你可以把他变为前进的动力,不必为了一点挫折和捶胸顿足,不必为了一点小事争吵不休。空闲的时候可以躺下来看看天,你就会明白自强不息,厚德载物之理。(看到一些为了失恋或者其他而堕落的人真的很难受和可惜)
要给人以帮助。别人有困难时或者一些举手之劳在自己解决范围内的尽力帮之,这对自己不会损失什么,相反在对方心里能够树立你的形象和增加好感。记住,给人之恩时也是给你自己铺路,这个世界不是靠你一个人可改变的。
第二编程:
语言的争论。几个月来,看到最有争议的帖子即关于某种语言好坏得,往往能顶个几百几千的人气。试问,中国牌子有几个不被骂的,试问,世界上哪部影片没人说他坏话的?术业有专攻,各司其职,每个语言的出现肯定都有自己价值和领域的范围,不同的只是价值和领域的高级和大小。就像高,矮,胖,瘦组成一个“型”。但是技术是进步的,这个进步也是在旧的基础上。放心,技术再飞跃也不会今天奔I,明天扣肉的迅速,在学习的同时也要时时关注整个语言界的走势(貌似哪个伟人说的与时俱进),保持一种同步曲线的前进。
语言能干什么?能问这句话,很欣慰。表明你是从基础学起才会问出这样的话来。但换而言之你一开始对这门语言调查的信息不足。庙算者胜,对自己身边的事物了解是非常重要的,这也能说明说你的教材或者教导你的人不够充分。如果是我对新手交流,我会花足时间让他明白这门语言是什么,能干什么,整个体系等等。这样在学的时候,对同一条语句上的理解你可能跟别人就不一样了, 因为你知道这门语言的体系架构,你可以从更多的特性角度去挖掘使用它。
提高自己对语言阅读能力。如何快速的进步?可以告诉你没有一步登天的办法,但是也可以告诉你如何让你快速掌握你所学的技术。多读他人代码,多领悟他人思维,多给自己出难题,多几种方法来解问题,多将语言的思想和身边的事物联系起来。想想你是怎么掌握普通话的,方法就不具体说了。
编程的思维。计算机是充满诱惑的东西,很多人都说热爱计算机,那只是它的表面,你热爱的只是他的一种扩展性的东西。能学计算机我很高兴,因为计算机从某种意义上来说将空间的距离给消除掉了。这里没有流血,没有牺牲。你可以以一种艺术家的思维来塑造它,可以以侦探的逻辑思维来破解它,可以以一种战争狂人的思维来摧毁它。你可以将你的这种思维体现在代码和整个布局里,不要惊异别人代码思维的异风,你也有自己的思维风格,你也可以,你也可以用自己代码让别人惊异。记住你学东西时更多的要体会他的思维,比如数学,微积分,离散,无数的定理,你学完以后可能永远都用不到,但是你可能有这种感觉,在你碰到某一问题时,这一问题的性质和你学到的东西很类似,但是中间就是差了一点东西,对了,这就是它思维上的一种运用,一种抽象,一种转换。
第三计算机
前面从说了很多,但是基本都是从理论上来说。因为我也没走到技术的高端(刚窥门槛还不到),说技术只是班门弄斧,但是我想我一些入门的方法还是可以和大家共享的。
前面提过,学一个东西首先要了解它,这样你才能知道你学的东西特性这个词。
不管你是什么目的学它的,你都要让自己喜欢上它。
建议新手们先从微机原理入手,弄懂后,再在自己脑中重绘一遍计算机的布局,学的时候,尽量将自己模拟的布局和语言给联系起来
接着学习英语,和数学,切记,学习数学是锻炼自己的逻辑,抽象和转换。
搞定这三个后再重新定位一下自己对计算机哪部分感兴趣,再从前辈中那里获取一些信息,这里教材和入门的准备很重要。一开始我也走了很多弯路,现在只能狂补英语和数学。
遇到问题时先不要问他人(常谈的问题了),应先以自己的所掌握的知识和思维来推敲问题,做个大胆的假设。
C++学习重点分析
一、#include “filename.h”和#include 的区别
#include “filename.h”是指编译器将从当前工作目录上开始查找此文件
#include 是指编译器将从标准库目录中开始查找此文件
二、头文件的作用
加强安全检测
通过头文件可能方便地调用库功能,而不必关心其实现方式
三、* , &修饰符的位置
对于*和&修饰符,为了避免误解,最好将修饰符紧靠变量名
四、if语句
不要将布尔变量与任何值进行比较,那会很容易出错的。
整形变量必须要有类型相同的值进行比较
浮点变量最好少比点,就算要比也要有值进行限制
指针变量要和NULL进行比较,不要和布尔型和整形比较
五、const和#define的比较
const有数据类型,#define没有数据类型
个别编译器中const可以进行调试,#define不可以进行调试
在类中定义常量有两种方式
1、 在类在声明常量,但不赋值,在构造函数初始化表中进行赋值;
2、 用枚举代替const常量。
六、C++函数中值的传递方式
有三种方式:值传递(Pass by value)、指针传递(Pass by pointer)、引用传递(Pass by reference)
void fun(char c) //pass by value
void fun(char *str) //pass by pointer
void fun(char &str) //pass by reference
如果输入参数是以值传递的话,最好使用引用传递代替,因为引用传递省去了临时对象的构造和析构
函数的类型不能省略,就算没有也要加个void
七、函数体中的指针或引用常量不能被返回
Char *func(void)
{
char str[]=”Hello Word”;
//这个是不能被返回的,因为str是个指定变量,不是一般的值,函数结束后会被注销掉
return str;
}
函数体内的指针变量并不会随着函数的消亡而自动释放
八、一个内存拷贝函数的实现体
void *memcpy(void *pvTo,const void *pvFrom,size_t size)
{
assert((pvTo!=NULL)&&(pvFrom!=NULL));
byte *pbTo=(byte*)pvTo; //防止地址被改变
byte *pbFrom=(byte*)pvFrom;
while (size-- >0)
pbTo++ = pbForm++;
return pvTo;
}
九、内存的分配方式
分配方式有三种,请记住,说不定那天去面试的时候就会有人问你这问题
1、 静态存储区,是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量。
2、 栈上分配,函数内的局部变量就是从这分配的,但分配的内存容易有限。
3、 堆上分配,也称动态分配,如我们用new,malloc分配内存,用delete,free来释放的内存。
十、内存分配的注意事项
用new或malloc分配内存时,必须要对此指针赋初值。
用delete 或free释放内存后,必须要将指针指向NULL
不能修改指向常量的指针数据
十一、内容复制与比较
//数组……
char a[]=”Hello Word!”;
char b[10];
strcpy(b,a);
if (strcmp(a,b)==0)
{}
//指针……
char a[]=”Hello Word!”;
char *p;
p=new char[strlen(a)+1];
strcpy(p,a);
if (strcmp(p,a)==0)
{}
十二、sizeof的问题
记住一点,C++无法知道指针所指对象的大小,指针的大小永远为4字节
char a[]=”Hello World!”
char *p=a;
count<
count<
而且,在函数中,数组参数退化为指针,所以下面的内容永远输出为4
void fun(char a[1000])
{
count<
}
十三、关于指针
1、 指针创建时必须被初始化
2、 指针在free 或delete后必须置为NULL
3、 指针的长度都为4字节
4、释放内存时,如果是数组指针,必须要释放掉所有的内存,如
char *p=new char[100];
strcpy(p,”Hello World”);
delete []p; //注意前面的[]号
p=NULL;
5、数组指针的内容不能超过数组指针的最大容易。
如:
char *p=new char[5];
strcpy(p,”Hello World”); //报错 目标容易不够大
delete []p; //注意前面的[]号
p=NULL;
十四、关于malloc/free 和new /delete
l malloc/free 是C/C+的内存分配符,new /delete是C++的内存分配符。
l 注意:malloc/free是库函数,new/delete是运算符
l malloc/free不能执行构造函数与析构函数,而new/delete可以
l new/delete不能在C上运行,所以malloc/free不能被淘汰
l 两者都必须要成对使用
l C++中可以使用_set_new_hander函数来定义内存分配异常的处理
十五、C++的特性
C++新增加有重载(overload),内联(inline),Const,Virtual四种机制
重载和内联:即可用于全局函数,也可用于类的成员函数;
Const和Virtual:只可用于类的成员函数;
重载:在同一类中,函数名相同的函数。由不同的参数决定调用那个函数。函数可要不可要Virtual关键字。和全局函数同名的函数不叫重载。如果在类中调用同名的全局函数,必须用全局引用符号::引用。
覆盖是指派生类函数覆盖基类函数
函数名相同;
参数相同;
基类函数必须有Virtual关键字;
不同的范围(派生类和基类)。
隐藏是指派生类屏蔽了基类的同名函数相同
1、 函数名相同,但参数不同,此时不论基类有无Virtual关键字,基类函数将被隐藏。
2、 函数名相同,参数也相同,但基类无Virtual关键字(有就是覆盖),基类函数将被隐藏。
内联:inline关键字必须与定义体放在一起,而不是单单放在声明中。
Const:const是constant的缩写,“恒定不变”的意思。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
1、 参数做输入用的指针型参数,加上const可防止被意外改动。
2、 按值引用的用户类型做输入参数时,最好将按值传递的改为引用传递,并加上const关键字,目的是为了提高效率。数据类型为内部类型的就没必要做这件事情;如:
将void Func(A a) 改为void Func(const A &a)。
而void func(int a)就没必要改成void func(const int &a);
3、 给返回值为指针类型的函数加上const,会使函数返回值不能被修改,赋给的变量也只能是const型变量。如:函数const char*GetString(void); char *str=GetString()将会出错。而const char *str=GetString()将是正确的。
4、 Const成员函数是指此函数体内只能调用Const成员变量,提高程序的键壮性。如声明函数 int GetCount(void) const;此函数体内就只能调用Const成员变量。
Virtual:虚函数:派生类可以覆盖掉的函数,纯虚函数:只是个空函数,没有函数实现体;
十六、extern“C”有什么作用?
Extern “C”是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数。这是因为C++编译后库中函数名会变得很长,与C生成的不一致,造成C++不能直接调用C函数,加上extren “c”后,C++就能直接调用C函数了。
Extern “C”主要使用正规DLL函数的引用和导出 和 在C++包含C函数或C头文件时使用。使用时在前面加上extern “c” 关键字即可。
十七、构造函数与析构函数
派生类的构造函数应在初始化表里调用基类的构造函数;
派生类和基类的析构函数应加Virtual关键字。
不要小看构造函数和析构函数,其实编起来还是不容易。
#include
class Base
{
public:
virtual ~Base() { cout<< "~Base" << endl ; }
};
class Derived : public Base
{
public:
virtual ~Derived() { cout<< "~Derived" << endl ; }
};
void main(void)
{
Base * pB = new Derived; // upcast
delete pB;
}
输出结果为:
~Derived
~Base
如果析构函数不为虚,那么输出结果为
~Base
十八、#IFNDEF/#DEFINE/#ENDIF有什么作用
仿止该头文件被重复引用
一、#include “filename.h”和#include 的区别
#include “filename.h”是指编译器将从当前工作目录上开始查找此文件
#include 是指编译器将从标准库目录中开始查找此文件
二、头文件的作用
加强安全检测
通过头文件可能方便地调用库功能,而不必关心其实现方式
三、* , &修饰符的位置
对于*和&修饰符,为了避免误解,最好将修饰符紧靠变量名
四、if语句
不要将布尔变量与任何值进行比较,那会很容易出错的。
整形变量必须要有类型相同的值进行比较
浮点变量最好少比点,就算要比也要有值进行限制
指针变量要和NULL进行比较,不要和布尔型和整形比较
五、const和#define的比较
const有数据类型,#define没有数据类型
个别编译器中const可以进行调试,#define不可以进行调试
在类中定义常量有两种方式
1、 在类在声明常量,但不赋值,在构造函数初始化表中进行赋值;
2、 用枚举代替const常量。
六、C++函数中值的传递方式
有三种方式:值传递(Pass by value)、指针传递(Pass by pointer)、引用传递(Pass by reference)
void fun(char c) //pass by value
void fun(char *str) //pass by pointer
void fun(char &str) //pass by reference
如果输入参数是以值传递的话,最好使用引用传递代替,因为引用传递省去了临时对象的构造和析构
函数的类型不能省略,就算没有也要加个void
七、函数体中的指针或引用常量不能被返回
Char *func(void)
{
char str[]=”Hello Word”;
//这个是不能被返回的,因为str是个指定变量,不是一般的值,函数结束后会被注销掉
return str;
}
函数体内的指针变量并不会随着函数的消亡而自动释放
八、一个内存拷贝函数的实现体
void *memcpy(void *pvTo,const void *pvFrom,size_t size)
{
assert((pvTo!=NULL)&&(pvFrom!=NULL));
byte *pbTo=(byte*)pvTo; //防止地址被改变
byte *pbFrom=(byte*)pvFrom;
while (size-- >0)
pbTo++ = pbForm++;
return pvTo;
}
九、内存的分配方式
分配方式有三种,请记住,说不定那天去面试的时候就会有人问你这问题
1、 静态存储区,是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量。
2、 栈上分配,函数内的局部变量就是从这分配的,但分配的内存容易有限。
3、 堆上分配,也称动态分配,如我们用new,malloc分配内存,用delete,free来释放的内存。
十、内存分配的注意事项
用new或malloc分配内存时,必须要对此指针赋初值。
用delete 或free释放内存后,必须要将指针指向NULL
不能修改指向常量的指针数据
十一、内容复制与比较
//数组……
char a[]=”Hello Word!”;
char b[10];
strcpy(b,a);
if (strcmp(a,b)==0)
{}
//指针……
char a[]=”Hello Word!”;
char *p;
p=new char[strlen(a)+1];
strcpy(p,a);
if (strcmp(p,a)==0)
{}
十二、sizeof的问题
记住一点,C++无法知道指针所指对象的大小,指针的大小永远为4字节
char a[]=”Hello World!”
char *p=a;
count<
count<
而且,在函数中,数组参数退化为指针,所以下面的内容永远输出为4
void fun(char a[1000])
{
count<
}
十三、关于指针
1、 指针创建时必须被初始化
2、 指针在free 或delete后必须置为NULL
3、 指针的长度都为4字节
4、释放内存时,如果是数组指针,必须要释放掉所有的内存,如
char *p=new char[100];
strcpy(p,”Hello World”);
delete []p; //注意前面的[]号
p=NULL;
5、数组指针的内容不能超过数组指针的最大容易。
如:
char *p=new char[5];
strcpy(p,”Hello World”); //报错 目标容易不够大
delete []p; //注意前面的[]号
p=NULL;
十四、关于malloc/free 和new /delete
l malloc/free 是C/C+的内存分配符,new /delete是C++的内存分配符。
l 注意:malloc/free是库函数,new/delete是运算符
l malloc/free不能执行构造函数与析构函数,而new/delete可以
l new/delete不能在C上运行,所以malloc/free不能被淘汰
l 两者都必须要成对使用
l C++中可以使用_set_new_hander函数来定义内存分配异常的处理
十五、C++的特性
C++新增加有重载(overload),内联(inline),Const,Virtual四种机制
重载和内联:即可用于全局函数,也可用于类的成员函数;
Const和Virtual:只可用于类的成员函数;
重载:在同一类中,函数名相同的函数。由不同的参数决定调用那个函数。函数可要不可要Virtual关键字。和全局函数同名的函数不叫重载。如果在类中调用同名的全局函数,必须用全局引用符号::引用。
覆盖是指派生类函数覆盖基类函数
函数名相同;
参数相同;
基类函数必须有Virtual关键字;
不同的范围(派生类和基类)。
隐藏是指派生类屏蔽了基类的同名函数相同
1、 函数名相同,但参数不同,此时不论基类有无Virtual关键字,基类函数将被隐藏。
2、 函数名相同,参数也相同,但基类无Virtual关键字(有就是覆盖),基类函数将被隐藏。
内联:inline关键字必须与定义体放在一起,而不是单单放在声明中。
Const:const是constant的缩写,“恒定不变”的意思。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
1、 参数做输入用的指针型参数,加上const可防止被意外改动。
2、 按值引用的用户类型做输入参数时,最好将按值传递的改为引用传递,并加上const关键字,目的是为了提高效率。数据类型为内部类型的就没必要做这件事情;如:
将void Func(A a) 改为void Func(const A &a)。
而void func(int a)就没必要改成void func(const int &a);
3、 给返回值为指针类型的函数加上const,会使函数返回值不能被修改,赋给的变量也只能是const型变量。如:函数const char*GetString(void); char *str=GetString()将会出错。而const char *str=GetString()将是正确的。
4、 Const成员函数是指此函数体内只能调用Const成员变量,提高程序的键壮性。如声明函数 int GetCount(void) const;此函数体内就只能调用Const成员变量。
Virtual:虚函数:派生类可以覆盖掉的函数,纯虚函数:只是个空函数,没有函数实现体;
十六、extern“C”有什么作用?
Extern “C”是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数。这是因为C++编译后库中函数名会变得很长,与C生成的不一致,造成C++不能直接调用C函数,加上extren “c”后,C++就能直接调用C函数了。
Extern “C”主要使用正规DLL函数的引用和导出 和 在C++包含C函数或C头文件时使用。使用时在前面加上extern “c” 关键字即可。
十七、构造函数与析构函数
派生类的构造函数应在初始化表里调用基类的构造函数;
派生类和基类的析构函数应加Virtual关键字。
不要小看构造函数和析构函数,其实编起来还是不容易。
#include
class Base
{
public:
virtual ~Base() { cout<< "~Base" << endl ; }
};
class Derived : public Base
{
public:
virtual ~Derived() { cout<< "~Derived" << endl ; }
};
void main(void)
{
Base * pB = new Derived; // upcast
delete pB;
}
输出结果为:
~Derived
~Base
如果析构函数不为虚,那么输出结果为
~Base
十八、#IFNDEF/#DEFINE/#ENDIF有什么作用
仿止该头文件被重复引用
控制台一闪一闪的小光标从当前位置后退一格= =、
比如printf("1234");结果就是1234
而printf("123\b4");结果是124,因为退一格再打印4就把先前打印的3洗掉了
猎鹰(905607625) 15:47:51
\b 退格。将当前的输出位置退回前一列处,即消除前一个已输出的字符。
比如printf("1234");结果就是1234
而printf("123\b4");结果是124,因为退一格再打印4就把先前打印的3洗掉了
=============================================
printf可能是许多程序员在开始学习C语言时接触到的第二个函数(我猜第一个是main),说起来,自然是老朋友了,可是,你对这个老朋友了解多吗?你对它的那个孪生兄弟sprintf了解多吗?在将各种类型的数据构造成字符串时,sprintf的强大功能很少会让你失望。
由于sprintf跟printf在用法上几乎一样,只是打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出。这也导致sprintf比printf有用得多。所以本文着重介绍sprintf,有时也穿插着用用pritnf。
sprintf是个变参函数,定义如下:
int sprintf( char *buffer, const char *format [, argument] ... );
除了前两个参数类型固定外,后面可以接任意多个参数。而它的精华,显然就在第二个参数:格式化字符串上。
printf和sprintf都使用格式化字符串来指定串的格式,在格式串内部使用一些以“%”开头的格式说明符(format specifications)来占据一个位置,在后边的变参列表中提供相应的变量,最终函数就会用相应位置的变量来替代那个说明符,产生一个调用者想要的字符串。
1. 格式化数字字符串
sprintf最常见的应用之一莫过于把整数打印到字符串中,所以,spritnf在大多数场合可以替代itoa。如:
//把整数123打印成一个字符串保存在s中。
sprintf(s, "%d", 123); //产生"123"
可以指定宽度,不足的左边补空格:
sprintf(s, "%8d%8d", 123, 4567); //产生:" 123 4567"
当然也可以左对齐:
sprintf(s, "%-8d%8d", 123, 4567); //产生:"123 4567"
也可以按照16进制打印:
sprintf(s, "%8x", 4567); //小写16进制,宽度占8个位置,右对齐
sprintf(s, "%-8X", 4568); //大写16进制,宽度占8个位置,左对齐
这样,一个整数的16进制字符串就很容易得到,但我们在打印16进制内容时,通常想要一种左边补0的等宽格式,那该怎么做呢?很简单,在表示宽度的数字前面加个0就可以了。
sprintf(s, "%08X", 4567); //产生:"000011D7"
上面以”%d”进行的10进制打印同样也可以使用这种左边补0的方式。
这里要注意一个符号扩展的问题:比如,假如我们想打印短整数(short)-1的内存16进制表示形式,在Win32平台上,一个short型占2个字节,所以我们自然希望用4个16进制数字来打印它:
short si = -1;
sprintf(s, "%04X", si);
产生“FFFFFFFF”,怎么回事?因为spritnf是个变参函数,除了前面两个参数之外,后面的参数都不是类型安全的,函数更没有办法仅仅通过一个“%X”就能得知当初函数调用前参数压栈时被压进来的到底是个4字节的整数还是个2字节的短整数,所以采取了统一4字节的处理方式,导致参数压栈时做了符号扩展,扩展成了32位的整数-1,打印时4个位置不够了,就把32位整数-1的8位16进制都打印出来了。如果你想看si的本来面目,那么就应该让编译器做0扩展而不是符号扩展(扩展时二进制左边补0而不是补符号位):
sprintf(s, "%04X", (unsigned short)si);
就可以了。或者:
unsigned short si = -1;
sprintf(s, "%04X", si);
sprintf和printf还可以按8进制打印整数字符串,使用”%o”。注意8进制和16进制都不会打印出负数,都是无符号的,实际上也就是变量的内部编码的直接的16进制或8进制表示。
2. 控制浮点数打印格式
浮点数的打印和格式控制是sprintf的又一大常用功能,浮点数使用格式符”%f”控制,默认保留小数点后6位数字,比如:
sprintf(s, "%f", 3.1415926); //产生"3.141593"
但有时我们希望自己控制打印的宽度和小数位数,这时就应该使用:”%m.nf”格式,其中m表示打印的宽度,n表示小数点后的位数。比如:
sprintf(s, "%10.3f", 3.1415626); //产生:" 3.142"
sprintf(s, "%-10.3f", 3.1415626); //产生:"3.142 "
sprintf(s, "%.3f", 3.1415626); //不指定总宽度,产生:"3.142"
注意一个问题,你猜
int i = 100;
sprintf(s, "%.2f", i);
会打出什么东东来?“100.00”?对吗?自己试试就知道了,同时也试试下面这个:
sprintf(s, "%.2f", (double)i);
第一个打出来的肯定不是正确结果,原因跟前面提到的一样,参数压栈时调用者并不知道跟i相对应的格式控制符是个”%f”。而函数执行时函数本身则并不知道当年被压入栈里的是个整数,于是可怜的保存整数i的那4个字节就被不由分说地强行作为浮点数格式来解释了,整个乱套了。
不过,如果有人有兴趣使用手工编码一个浮点数,那么倒可以使用这种方法来检验一下你手工编排的结果是否正确。J
字符/Ascii码对照
我们知道,在C/C++语言中,char也是一种普通的scalable类型,除了字长之外,它与short,int,long这些类型没有本质区别,只不过被大家习惯用来表示字符和字符串而已。(或许当年该把这个类型叫做“byte”,然后现在就可以根据实际情况,使用byte或short来把char通过typedef定义出来,这样更合适些)
于是,使用”%d”或者”%x”打印一个字符,便能得出它的10进制或16进制的ASCII码;反过来,使用”%c”打印一个整数,便可以看到它所对应的ASCII字符。以下程序段把所有可见字符的ASCII码对照表打印到屏幕上(这里采用printf,注意”#”与”%X”合用时自动为16进制数增加”0X”前缀):
for(int i = 32; i < 127; i++) {
printf("[ %c ]: %3d 0x%#04X\n", i, i, i);
}
3. 连接字符串
sprintf的格式控制串中既然可以插入各种东西,并最终把它们“连成一串”,自然也就能够连接字符串,从而在许多场合可以替代strcat,但sprintf能够一次连接多个字符串(自然也可以同时在它们中间插入别的内容,总之非常灵活)。比如:
char* who = "I";
char* whom = "CSDN";
sprintf(s, "%s love %s.", who, whom); //产生:"I love CSDN. "
strcat只能连接字符串(一段以’\0’结尾的字符数组或叫做字符缓冲,null-terminated-string),但有时我们有两段字符缓冲区,他们并不是以’\0’结尾。比如许多从第三方库函数中返回的字符数组,从硬件或者网络传输中读进来的字符流,它们未必每一段字符序列后面都有个相应的’\0’来结尾。如果直接连接,不管是sprintf还是strcat肯定会导致非法内存操作,而strncat也至少要求第一个参数是个null-terminated-string,那该怎么办呢?我们自然会想起前面介绍打印整数和浮点数时可以指定宽度,字符串也一样的。比如:
char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'};
如果:
sprintf(s, "%s%s", a1, a2); //Don't do that!
十有八九要出问题了。是否可以改成:
sprintf(s, "%7s%7s", a1, a2);
也没好到哪儿去,正确的应该是:
sprintf(s, "%.7s%.7s", a1, a2);//产生:"ABCDEFGHIJKLMN"
这可以类比打印浮点数的”%m.nf”,在”%m.ns”中,m表示占用宽度(字符串长度不足时补空格,超出了则按照实际宽度打印),n才表示从相应的字符串中最多取用的字符数。通常在打印字符串时m没什么大用,还是点号后面的n用的多。自然,也可以前后都只取部分字符:
sprintf(s, "%.6s%.5s", a1, a2);//产生:"ABCDEFHIJKL"
在许多时候,我们或许还希望这些格式控制符中用以指定长度信息的数字是动态的,而不是静态指定的,因为许多时候,程序要到运行时才会清楚到底需要取字符数组中的几个字符,这种动态的宽度/精度设置功能在sprintf的实现中也被考虑到了,sprintf采用”*”来占用一个本来需要一个指定宽度或精度的常数数字的位置,同样,而实际的宽度或精度就可以和其它被打印的变量一样被提供出来,于是,上面的例子可以变成:
sprintf(s, "%.*s%.*s", 7, a1, 7, a2);
或者:
sprintf(s, "%.*s%.*s", sizeof(a1), a1, sizeof(a2), a2);
实际上,前面介绍的打印字符、整数、浮点数等都可以动态指定那些常量值,比如:
sprintf(s, "%-*d", 4, 'A'); //产生"65 "
sprintf(s, "%#0*X", 8, 128); //产生"0X000080","#"产生0X
sprintf(s, "%*.*f", 10, 2, 3.1415926); //产生" 3.14"
4. 打印地址信息
有时调试程序时,我们可能想查看某些变量或者成员的地址,由于地址或者指针也不过是个32位的数,你完全可以使用打印无符号整数的”%u”把他们打印出来:
sprintf(s, "%u", &i);
不过通常人们还是喜欢使用16进制而不是10进制来显示一个地址:
sprintf(s, "%08X", &i);
然而,这些都是间接的方法,对于地址打印,sprintf 提供了专门的”%p”:
sprintf(s, "%p", &i);
我觉得它实际上就相当于:
sprintf(s, "%0*x", 2 * sizeof(void *), &i);
5. 利用sprintf的返回值
较少有人注意printf/sprintf函数的返回值,但有时它却是有用的,spritnf返回了本次函数调用最终打印到字符缓冲区中的字符数目。也就是说每当一次sprinf调用结束以后,你无须再调用一次strlen便已经知道了结果字符串的长度。如:
int len = sprintf(s, "%d", i);
对于正整数来说,len便等于整数i的10进制位数。
下面的是个完整的例子,产生10个[0, 100)之间的随机数,并将他们打印到一个字符数组s中,以逗号分隔开。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main() {
srand(time(0));
char s[64];
int offset = 0;
for(int i = 0; i < 10; i++) {
offset += sprintf(s + offset, "%d,", rand() % 100);
}
s[offset - 1] = '\n';//将最后一个逗号换成换行符。
printf(s);
return 0;
}
设想当你从数据库中取出一条记录,然后希望把他们的各个字段按照某种规则连接成一个字符串时,就可以使用这种方法,从理论上讲,他应该比不断的strcat效率高,因为strcat每次调用都需要先找到最后的那个’\0’的位置,而在上面给出的例子中,我们每次都利用sprintf返回值把这个位置直接记下来了。
6. 使用sprintf的常见问题
sprintf是个变参函数,使用时经常出问题,而且只要出问题通常就是能导致程序崩溃的内存访问错误,但好在由sprintf误用导致的问题虽然严重,却很容易找出,无非就是那么几种情况,通常用眼睛再把出错的代码多看几眼就看出来了。
Ø 缓冲区溢出
第一个参数的长度太短了,没的说,给个大点的地方吧。当然也可能是后面的参数的问题,建议变参对应一定要细心,而打印字符串时,尽量使用”%.ns”的形式指定最大字符数。
Ø 忘记了第一个参数
低级得不能再低级问题,用printf用得太惯了。//偶就常犯。:。(
Ø 变参对应出问题
通常是忘记了提供对应某个格式符的变参,导致以后的参数统统错位,检查检查吧。尤其是对应”*”的那些参数,都提供了吗?不要把一个整数对应一个”%s”,编译器会觉得你欺她太甚了(编译器是obj和exe的妈妈,应该是个女的,:P)。
7. strftime
sprintf还有个不错的表妹:strftime,专门用于格式化时间字符串的,用法跟她表哥很像,也是一大堆格式控制符,只是毕竟小姑娘家心细,她还要调用者指定缓冲区的最大长度,可能是为了在出现问题时可以推卸责任吧。这里举个例子:
time_t t = time(0);
//产生"YYYY-MM-DD hh:mm:ss"格式的字符串。
char s[32];
strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", localtime(&t));
sprintf在MFC中也能找到他的知音:CString::Format,strftime在MFC中自然也有她的同道:CTime::Format,这一对由于从面向对象哪里得到了赞助,用以写出的代码更觉优雅。
8. 后记
本文介绍的所有这些功能,在MSDN中都可以很容易地查到,笔者只是根据自己的使用经验,结合一些例子,把一些常用的,有用的,而可能为许多初学者所不知的用法介绍了一点,希望大家不要笑话,也希望大家批评指正。
有人认为这种带变参的函数会引起各种问题,因而不提倡使用。但笔者本人每每还是抵挡不了它们强大功能的诱惑,在实际工作中一直在使用。实际上,C#.NET从开始就支持变参,刚发布不久的Java5.0也支持变参了。
感谢ericzhangali(另一个空间)仔细审阅了全稿,纠正了很多小错误,并提出了一些建议。也感谢laomai(老迈)阅读了全稿并给出了增删一些内容的建议。
①获取System时间: void GetSystemTime(LPSYSTEMTIME lpSystemTime); 下面是例子:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
void main() {
SYSTEMTIME st; //定义存放时间的结构体
char strTime[256];
int n=0;
GetSystemTime(&st);
n = sprintf(strTime,"Year:\t%d\n",st.wYear);
n += sprintf(strTime+n,"Month:\t%d\n",st.wMonth);
n += sprintf(strTime+n,"Day:\t%d\n",st.wDay);
n += sprintf(strTime+n,"Date:\t%d\n",st.wDayOfWeek);
n += sprintf(strTime+n,"Hour:\t%d\n",st.wHour);
n += sprintf(strTime+n,"Minute:\t%d\n",st.wMinute);
n += sprintf(strTime+n,"Second:\t%d\n",st.wSecond);
n += sprintf(strTime+n,"MilliSecond:\t%d\n",st.wMilliseconds);
printf("%s",strTime);
system("pause");
}
比如printf("1234");结果就是1234
而printf("123\b4");结果是124,因为退一格再打印4就把先前打印的3洗掉了
猎鹰(905607625) 15:47:51
\b 退格。将当前的输出位置退回前一列处,即消除前一个已输出的字符。
比如printf("1234");结果就是1234
而printf("123\b4");结果是124,因为退一格再打印4就把先前打印的3洗掉了
=============================================
printf可能是许多程序员在开始学习C语言时接触到的第二个函数(我猜第一个是main),说起来,自然是老朋友了,可是,你对这个老朋友了解多吗?你对它的那个孪生兄弟sprintf了解多吗?在将各种类型的数据构造成字符串时,sprintf的强大功能很少会让你失望。
由于sprintf跟printf在用法上几乎一样,只是打印的目的地不同而已,前者打印到字符串中,后者则直接在命令行上输出。这也导致sprintf比printf有用得多。所以本文着重介绍sprintf,有时也穿插着用用pritnf。
sprintf是个变参函数,定义如下:
int sprintf( char *buffer, const char *format [, argument] ... );
除了前两个参数类型固定外,后面可以接任意多个参数。而它的精华,显然就在第二个参数:格式化字符串上。
printf和sprintf都使用格式化字符串来指定串的格式,在格式串内部使用一些以“%”开头的格式说明符(format specifications)来占据一个位置,在后边的变参列表中提供相应的变量,最终函数就会用相应位置的变量来替代那个说明符,产生一个调用者想要的字符串。
1. 格式化数字字符串
sprintf最常见的应用之一莫过于把整数打印到字符串中,所以,spritnf在大多数场合可以替代itoa。如:
//把整数123打印成一个字符串保存在s中。
sprintf(s, "%d", 123); //产生"123"
可以指定宽度,不足的左边补空格:
sprintf(s, "%8d%8d", 123, 4567); //产生:" 123 4567"
当然也可以左对齐:
sprintf(s, "%-8d%8d", 123, 4567); //产生:"123 4567"
也可以按照16进制打印:
sprintf(s, "%8x", 4567); //小写16进制,宽度占8个位置,右对齐
sprintf(s, "%-8X", 4568); //大写16进制,宽度占8个位置,左对齐
这样,一个整数的16进制字符串就很容易得到,但我们在打印16进制内容时,通常想要一种左边补0的等宽格式,那该怎么做呢?很简单,在表示宽度的数字前面加个0就可以了。
sprintf(s, "%08X", 4567); //产生:"000011D7"
上面以”%d”进行的10进制打印同样也可以使用这种左边补0的方式。
这里要注意一个符号扩展的问题:比如,假如我们想打印短整数(short)-1的内存16进制表示形式,在Win32平台上,一个short型占2个字节,所以我们自然希望用4个16进制数字来打印它:
short si = -1;
sprintf(s, "%04X", si);
产生“FFFFFFFF”,怎么回事?因为spritnf是个变参函数,除了前面两个参数之外,后面的参数都不是类型安全的,函数更没有办法仅仅通过一个“%X”就能得知当初函数调用前参数压栈时被压进来的到底是个4字节的整数还是个2字节的短整数,所以采取了统一4字节的处理方式,导致参数压栈时做了符号扩展,扩展成了32位的整数-1,打印时4个位置不够了,就把32位整数-1的8位16进制都打印出来了。如果你想看si的本来面目,那么就应该让编译器做0扩展而不是符号扩展(扩展时二进制左边补0而不是补符号位):
sprintf(s, "%04X", (unsigned short)si);
就可以了。或者:
unsigned short si = -1;
sprintf(s, "%04X", si);
sprintf和printf还可以按8进制打印整数字符串,使用”%o”。注意8进制和16进制都不会打印出负数,都是无符号的,实际上也就是变量的内部编码的直接的16进制或8进制表示。
2. 控制浮点数打印格式
浮点数的打印和格式控制是sprintf的又一大常用功能,浮点数使用格式符”%f”控制,默认保留小数点后6位数字,比如:
sprintf(s, "%f", 3.1415926); //产生"3.141593"
但有时我们希望自己控制打印的宽度和小数位数,这时就应该使用:”%m.nf”格式,其中m表示打印的宽度,n表示小数点后的位数。比如:
sprintf(s, "%10.3f", 3.1415626); //产生:" 3.142"
sprintf(s, "%-10.3f", 3.1415626); //产生:"3.142 "
sprintf(s, "%.3f", 3.1415626); //不指定总宽度,产生:"3.142"
注意一个问题,你猜
int i = 100;
sprintf(s, "%.2f", i);
会打出什么东东来?“100.00”?对吗?自己试试就知道了,同时也试试下面这个:
sprintf(s, "%.2f", (double)i);
第一个打出来的肯定不是正确结果,原因跟前面提到的一样,参数压栈时调用者并不知道跟i相对应的格式控制符是个”%f”。而函数执行时函数本身则并不知道当年被压入栈里的是个整数,于是可怜的保存整数i的那4个字节就被不由分说地强行作为浮点数格式来解释了,整个乱套了。
不过,如果有人有兴趣使用手工编码一个浮点数,那么倒可以使用这种方法来检验一下你手工编排的结果是否正确。J
字符/Ascii码对照
我们知道,在C/C++语言中,char也是一种普通的scalable类型,除了字长之外,它与short,int,long这些类型没有本质区别,只不过被大家习惯用来表示字符和字符串而已。(或许当年该把这个类型叫做“byte”,然后现在就可以根据实际情况,使用byte或short来把char通过typedef定义出来,这样更合适些)
于是,使用”%d”或者”%x”打印一个字符,便能得出它的10进制或16进制的ASCII码;反过来,使用”%c”打印一个整数,便可以看到它所对应的ASCII字符。以下程序段把所有可见字符的ASCII码对照表打印到屏幕上(这里采用printf,注意”#”与”%X”合用时自动为16进制数增加”0X”前缀):
for(int i = 32; i < 127; i++) {
printf("[ %c ]: %3d 0x%#04X\n", i, i, i);
}
3. 连接字符串
sprintf的格式控制串中既然可以插入各种东西,并最终把它们“连成一串”,自然也就能够连接字符串,从而在许多场合可以替代strcat,但sprintf能够一次连接多个字符串(自然也可以同时在它们中间插入别的内容,总之非常灵活)。比如:
char* who = "I";
char* whom = "CSDN";
sprintf(s, "%s love %s.", who, whom); //产生:"I love CSDN. "
strcat只能连接字符串(一段以’\0’结尾的字符数组或叫做字符缓冲,null-terminated-string),但有时我们有两段字符缓冲区,他们并不是以’\0’结尾。比如许多从第三方库函数中返回的字符数组,从硬件或者网络传输中读进来的字符流,它们未必每一段字符序列后面都有个相应的’\0’来结尾。如果直接连接,不管是sprintf还是strcat肯定会导致非法内存操作,而strncat也至少要求第一个参数是个null-terminated-string,那该怎么办呢?我们自然会想起前面介绍打印整数和浮点数时可以指定宽度,字符串也一样的。比如:
char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char a2[] = {'H', 'I', 'J', 'K', 'L', 'M', 'N'};
如果:
sprintf(s, "%s%s", a1, a2); //Don't do that!
十有八九要出问题了。是否可以改成:
sprintf(s, "%7s%7s", a1, a2);
也没好到哪儿去,正确的应该是:
sprintf(s, "%.7s%.7s", a1, a2);//产生:"ABCDEFGHIJKLMN"
这可以类比打印浮点数的”%m.nf”,在”%m.ns”中,m表示占用宽度(字符串长度不足时补空格,超出了则按照实际宽度打印),n才表示从相应的字符串中最多取用的字符数。通常在打印字符串时m没什么大用,还是点号后面的n用的多。自然,也可以前后都只取部分字符:
sprintf(s, "%.6s%.5s", a1, a2);//产生:"ABCDEFHIJKL"
在许多时候,我们或许还希望这些格式控制符中用以指定长度信息的数字是动态的,而不是静态指定的,因为许多时候,程序要到运行时才会清楚到底需要取字符数组中的几个字符,这种动态的宽度/精度设置功能在sprintf的实现中也被考虑到了,sprintf采用”*”来占用一个本来需要一个指定宽度或精度的常数数字的位置,同样,而实际的宽度或精度就可以和其它被打印的变量一样被提供出来,于是,上面的例子可以变成:
sprintf(s, "%.*s%.*s", 7, a1, 7, a2);
或者:
sprintf(s, "%.*s%.*s", sizeof(a1), a1, sizeof(a2), a2);
实际上,前面介绍的打印字符、整数、浮点数等都可以动态指定那些常量值,比如:
sprintf(s, "%-*d", 4, 'A'); //产生"65 "
sprintf(s, "%#0*X", 8, 128); //产生"0X000080","#"产生0X
sprintf(s, "%*.*f", 10, 2, 3.1415926); //产生" 3.14"
4. 打印地址信息
有时调试程序时,我们可能想查看某些变量或者成员的地址,由于地址或者指针也不过是个32位的数,你完全可以使用打印无符号整数的”%u”把他们打印出来:
sprintf(s, "%u", &i);
不过通常人们还是喜欢使用16进制而不是10进制来显示一个地址:
sprintf(s, "%08X", &i);
然而,这些都是间接的方法,对于地址打印,sprintf 提供了专门的”%p”:
sprintf(s, "%p", &i);
我觉得它实际上就相当于:
sprintf(s, "%0*x", 2 * sizeof(void *), &i);
5. 利用sprintf的返回值
较少有人注意printf/sprintf函数的返回值,但有时它却是有用的,spritnf返回了本次函数调用最终打印到字符缓冲区中的字符数目。也就是说每当一次sprinf调用结束以后,你无须再调用一次strlen便已经知道了结果字符串的长度。如:
int len = sprintf(s, "%d", i);
对于正整数来说,len便等于整数i的10进制位数。
下面的是个完整的例子,产生10个[0, 100)之间的随机数,并将他们打印到一个字符数组s中,以逗号分隔开。
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main() {
srand(time(0));
char s[64];
int offset = 0;
for(int i = 0; i < 10; i++) {
offset += sprintf(s + offset, "%d,", rand() % 100);
}
s[offset - 1] = '\n';//将最后一个逗号换成换行符。
printf(s);
return 0;
}
设想当你从数据库中取出一条记录,然后希望把他们的各个字段按照某种规则连接成一个字符串时,就可以使用这种方法,从理论上讲,他应该比不断的strcat效率高,因为strcat每次调用都需要先找到最后的那个’\0’的位置,而在上面给出的例子中,我们每次都利用sprintf返回值把这个位置直接记下来了。
6. 使用sprintf的常见问题
sprintf是个变参函数,使用时经常出问题,而且只要出问题通常就是能导致程序崩溃的内存访问错误,但好在由sprintf误用导致的问题虽然严重,却很容易找出,无非就是那么几种情况,通常用眼睛再把出错的代码多看几眼就看出来了。
Ø 缓冲区溢出
第一个参数的长度太短了,没的说,给个大点的地方吧。当然也可能是后面的参数的问题,建议变参对应一定要细心,而打印字符串时,尽量使用”%.ns”的形式指定最大字符数。
Ø 忘记了第一个参数
低级得不能再低级问题,用printf用得太惯了。//偶就常犯。:。(
Ø 变参对应出问题
通常是忘记了提供对应某个格式符的变参,导致以后的参数统统错位,检查检查吧。尤其是对应”*”的那些参数,都提供了吗?不要把一个整数对应一个”%s”,编译器会觉得你欺她太甚了(编译器是obj和exe的妈妈,应该是个女的,:P)。
7. strftime
sprintf还有个不错的表妹:strftime,专门用于格式化时间字符串的,用法跟她表哥很像,也是一大堆格式控制符,只是毕竟小姑娘家心细,她还要调用者指定缓冲区的最大长度,可能是为了在出现问题时可以推卸责任吧。这里举个例子:
time_t t = time(0);
//产生"YYYY-MM-DD hh:mm:ss"格式的字符串。
char s[32];
strftime(s, sizeof(s), "%Y-%m-%d %H:%M:%S", localtime(&t));
sprintf在MFC中也能找到他的知音:CString::Format,strftime在MFC中自然也有她的同道:CTime::Format,这一对由于从面向对象哪里得到了赞助,用以写出的代码更觉优雅。
8. 后记
本文介绍的所有这些功能,在MSDN中都可以很容易地查到,笔者只是根据自己的使用经验,结合一些例子,把一些常用的,有用的,而可能为许多初学者所不知的用法介绍了一点,希望大家不要笑话,也希望大家批评指正。
有人认为这种带变参的函数会引起各种问题,因而不提倡使用。但笔者本人每每还是抵挡不了它们强大功能的诱惑,在实际工作中一直在使用。实际上,C#.NET从开始就支持变参,刚发布不久的Java5.0也支持变参了。
感谢ericzhangali(另一个空间)仔细审阅了全稿,纠正了很多小错误,并提出了一些建议。也感谢laomai(老迈)阅读了全稿并给出了增删一些内容的建议。
①获取System时间: void GetSystemTime(LPSYSTEMTIME lpSystemTime); 下面是例子:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
void main() {
SYSTEMTIME st; //定义存放时间的结构体
char strTime[256];
int n=0;
GetSystemTime(&st);
n = sprintf(strTime,"Year:\t%d\n",st.wYear);
n += sprintf(strTime+n,"Month:\t%d\n",st.wMonth);
n += sprintf(strTime+n,"Day:\t%d\n",st.wDay);
n += sprintf(strTime+n,"Date:\t%d\n",st.wDayOfWeek);
n += sprintf(strTime+n,"Hour:\t%d\n",st.wHour);
n += sprintf(strTime+n,"Minute:\t%d\n",st.wMinute);
n += sprintf(strTime+n,"Second:\t%d\n",st.wSecond);
n += sprintf(strTime+n,"MilliSecond:\t%d\n",st.wMilliseconds);
printf("%s",strTime);
system("pause");
}
#include "iostream"
#include "../../lib/sinalib/StrLib/str.h"
#include "StrLib.h"
using namespace std;
int main()
{
string strinfo="Please input your name:";
cout << strinfo ;
cin >> strinfo;
if( strinfo == "winter" )
cout << "you are winter!"<<endl;
strinfo += " , Welcome to China!";
cout << strinfo ;
string strtmp = "How are you? " + strinfo;
for(int i = 0 ; i < strtmp.size(); i ++)
cout<<strtmp<<strtmp.size();
}
前言: string 的角色
1 string 使用
1.1 充分使用string 操作符
1.2 眼花缭乱的string find 函数
1.3 string insert, replace, erase 2 string 和 C风格字符串
3 string 和 Charactor Traits
4 string 建议
5 小结
6 附录前言: string 的角色
C++ 语言是个十分优秀的语言,但优秀并不表示完美。还是有许多人不愿意使用C或者C++,为什么?原因众多,其中之一就是C/C++的文本处理功能太麻烦,用起来很不方便。以前没有接触过其他语言时,每当别人这么说,我总是不屑一顾,认为他们根本就没有领会C++的精华,或者不太懂C++,现在我接触perl, php, 和Shell脚本以后,开始理解了以前为什么有人说C++文本处理不方便了。
举例来说,如果文本格式是:用户名 电话号码,文件名name.txt
Tom 23245332
Jenny 22231231
Heny 22183942
Tom 23245332
...
现在我们需要对用户名排序,且只输出不同的姓名。
那么在shell 编程中,可以这样用:
awk '{print $1}' name.txt | sort | uniq
简单吧?
如果使用C/C++ 就麻烦了,他需要做以下工作:
先打开文件,检测文件是否打开,如果失败,则退出。
声明一个足够大得二维字符数组或者一个字符指针数组
读入一行到字符空间
然后分析一行的结构,找到空格,存入字符数组中。
关闭文件
写一个排序函数,或者使用写一个比较函数,使用qsort排序
遍历数组,比较是否有相同的,如果有,则要删除,copy...
输出信息
你可以用C++或者C语言去实现这个流程。如果一个人的主要工作就是处理这种类似的文本(例如做apache的日志统计和分析),你说他会喜欢C/C++么?
当然,有了STL,这些处理会得到很大的简化。我们可以使用 fstream来代替麻烦的fopen fread fclose, 用vector 来代替数组。最重要的是用 string来代替char * 数组,使用sort排序算法来排序,用unique 函数来去重。听起来好像很不错 。看看下面代码(例程1):
#i nclude <string>
#i nclude <iostream>
#i nclude <algorithm>
#i nclude <vector>
#i nclude <fstream>
using namespace std;
int main(){
ifstream in("name.txt");
string strtmp;
vector<string> vect;
while(getline(in, strtmp, '\n'))
vect.push_back(strtmp.substr(0, strtmp.find(' ')));
sort(vect.begin(), vect.end());
vector<string>::iterator it=unique(vect.begin(), vect.end());
copy(vect.begin(), it, ostream_iterator<string>(cout, "\n"));
return 0;
}
也还不错吧,至少会比想象得要简单得多!(代码里面没有对错误进行处理,只是为了说明问题,不要效仿).
当然,在这个文本格式中,不用vector而使用map会更有扩充性,例如,还可通过人名找电话号码等等,但是使用了map就不那么好用sort了。你可以用map试一试。
这里string的作用不只是可以存储字符串,还可以提供字符串的比较,查找等。在sort和unique函数中就默认使用了less 和equal_to函数, 上面的一段代码,其实使用了string的以下功能:
存储功能,在getline() 函数中
查找功能,在find() 函数中
子串功能,在substr() 函数中
string operator < , 默认在sort() 函数中调用
string operator == , 默认在unique() 函数中调用
总之,有了string 后,C++的字符文本处理功能总算得到了一定补充,加上配合STL其他容器使用,其在文本处理上的功能已经与perl, shell, php的距离缩小很多了。 因此掌握string 会让你的工作事半功倍。
1 string 使用
其实,string并不是一个单独的容器,只是basic_string 模板类的一个typedef 而已,相对应的还有wstring, 你在string 头文件中你会发现下面的代码:
extern "C++" {
typedef basic_string <char> string;
typedef basic_string <wchar_t> wstring;
} // extern "C++"
由于只是解释string的用法,如果没有特殊的说明,本文并不区分string 和 basic_string的区别。
string 其实相当于一个保存字符的序列容器,因此除了有字符串的一些常用操作以外,还有包含了所有的序列容器的操作。字符串的常用操作包括:增加、删除、修改、查找比较、链接、输入、输出等。详细函数列表参看附录。不要害怕这么多函数,其实有许多是序列容器带有的,平时不一定用的上。
如果你要想了解所有函数的详细用法,你需要查看basic_string,或者下载STL编程手册。这里通过实例介绍一些常用函数。
1.1 充分使用string 操作符
string 重载了许多操作符,包括 +, +=, <, =, , [], <<, >>等,正式这些操作符,对字符串操作非常方便。先看看下面这个例子:tt.cpp(例程2)
#i nclude <string>
#i nclude <iostream>
using namespace std;
int main(){
string strinfo="Please input your name:";
cout << strinfo ;
cin >> strinfo;
if( strinfo == "winter" )
cout << "you are winter!"<<endl;
else if( strinfo != "wende" )
cout << "you are not wende!"<<endl;
else if( strinfo < "winter")
cout << "your name should be ahead of winter"<<endl;
else
cout << "your name should be after of winter"<<endl;
strinfo += " , Welcome to China!";
cout << strinfo<<endl;
cout <<"Your name is :"<<endl;
string strtmp = "How are you? " + strinfo;
for(int i = 0 ; i < strtmp.size(); i ++)
cout<<strtmp;
return 0;
}
下面是程序的输出
-bash-2.05b$ make tt
c++ -O -pipe -march=pentiumpro tt.cpp -o tt
-bash-2.05b$ ./tt
Please input your name:Hero
you are not wende!
Hero , Welcome to China!
How are you? Hero , Welcome to China!
有了这些操作符,在STL中仿函数都可以直接使用string作为参数,例如 less, great, equal_to 等,因此在把string作为参数传递的时候,它的使用和int 或者float等已经没有什么区别了。例如,你可以使用:
map<string, int> mymap;
//以上默认使用了 less<string>
有了 operator + 以后,你可以直接连加,例如:
string strinfo="Winter";
string strlast="Hello " + strinfo + "!";
//你还可以这样:
string strtest="Hello " + strinfo + " Welcome" + " to China" + " !";
看见其中的特点了吗?只要你的等式里面有一个 string 对象,你就可以一直连续"+",但有一点需要保证的是,在开始的两项中,必须有一项是 string 对象。其原理很简单:
系统遇到"+"号,发现有一项是string 对象。
系统把另一项转化为一个临时 string 对象。
执行 operator + 操作,返回新的临时string 对象。
如果又发现"+"号,继续第一步操作。
由于这个等式是由左到右开始检测执行,如果开始两项都是const char* ,程序自己并没有定义两个const char* 的加法,编译的时候肯定就有问题了。
有了操作符以后,assign(), append(), compare(), at()等函数,除非有一些特殊的需求时,一般是用不上。当然at()函数还有一个功能,那就是检查下标是否合法,如果是使用:
string str="winter";
//下面一行有可能会引起程序中断错误
str[100]='!';
//下面会抛出异常:throws: out_of_range
cout<<str.at(100)<<endl;
了解了吗?如果你希望效率高,还是使用[]来访问,如果你希望稳定性好,最好使用at()来访问。
1.2 眼花缭乱的string find 函数
由于查找是使用最为频繁的功能之一,string 提供了非常丰富的查找函数。其列表如下:
函数名 描述 find 查找 rfind 反向查找 find_first_of 查找包含子串中的任何字符,返回第一个位置 find_first_not_of 查找不包含子串中的任何字符,返回第一个位置 find_last_of 查找包含子串中的任何字符,返回最后一个位置 find_last_not_of 查找不包含子串中的任何字符,返回最后一个位置 以上函数都是被重载了4次,以下是以find_first_of 函数为例说明他们的参数,其他函数和其参数一样,也就是说总共有24个函数 :
size_type find_first_of(const basic_string& s, size_type pos = 0)
size_type find_first_of(const charT* s, size_type pos, size_type n)
size_type find_first_of(const charT* s, size_type pos = 0)
size_type find_first_of(charT c, size_type pos = 0)
所有的查找函数都返回一个size_type类型,这个返回值一般都是所找到字符串的位置,如果没有找到,则返回string::npos。有一点需要特别注意,所有和string::npos的比较一定要用string::size_type来使用,不要直接使用int 或者unsigned int等类型。其实string::npos表示的是-1, 看看头文件:
template <class _CharT, class _Traits, class _Alloc>
const basic_string<_CharT,_Traits,_Alloc>::size_type
basic_string<_CharT,_Traits,_Alloc>::npos
= basic_string<_CharT,_Traits,_Alloc>::size_type) -1;
find 和 rfind 都还比较容易理解,一个是正向匹配,一个是逆向匹配,后面的参数pos都是用来指定起始查找位置。对于find_first_of 和find_last_of 就不是那么好理解。
find_first_of 是给定一个要查找的字符集,找到这个字符集中任何一个字符所在字符串中第一个位置。或许看一个例子更容易明白。
有这样一个需求:过滤一行开头和结尾的所有非英文字符。看看用string 如何实现:
#i nclude <string>
#i nclude <iostream>
using namespace std;
int main(){
string strinfo=" //*---Hello Word!......------";
string strset="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int first = strinfo.find_first_of(strset);
if(first == string::npos) {
cout<<"not find any characters"<<endl;
return -1;
}
int last = strinfo.find_last_of(strset);
if(last == string::npos) {
cout<<"not find any characters"<<endl;
return -1;
}
cout << strinfo.substr(first, last - first + 1)<<endl;
return 0;
}
这里把所有的英文字母大小写作为了需要查找的字符集,先查找第一个英文字母的位置,然后查找最后一个英文字母的位置,然后用substr 来的到中间的一部分,用于输出结果。下面就是其结果:
Hello Word
前面的符号和后面的符号都没有了。像这种用法可以用来查找分隔符,从而把一个连续的字符串分割成为几部分,达到 shell 命令中的 awk 的用法。特别是当分隔符有多个的时候,可以一次指定。例如有这样的需求:
张三|3456123, 湖南
李四,4564234| 湖北
王小二, 4433253|北京
...
我们需要以 "|" ","为分隔符,同时又要过滤空格,把每行分成相应的字段。可以作为你的一个家庭作业来试试,要求代码简洁。
1.3 string insert, replace, erase
了解了string 的操作符,查找函数和substr,其实就已经了解了string的80%的操作了。insert函数, replace函数和erase函数在使用起来相对简单。下面以一个例子来说明其应用。
string只是提供了按照位置和区间的replace函数,而不能用一个string字串来替换指定string中的另一个字串。这里写一个函数来实现这个功能:
void string_replace(string & strBig, const string & strsrc, const string &strdst) {
string::size_type pos=0;
string::size_type srclen=strsrc.size();
string::size_type dstlen=strdst.size();
while( (pos=strBig.find(strsrc, pos)) != string::npos){
strBig.replace(pos, srclen, strdst);
pos += dstlen;
}
}看看如何调用:
#i nclude <string>
#i nclude <iostream>
using namespace std;
int main() {
string strinfo="This is Winter, Winter is a programmer. Do you know Winter?";
cout<<"Orign string is :\n"<<strinfo<<endl;
string_replace(strinfo, "Winter", "wende");
cout<<"After replace Winter with wende, the string is :\n"<<strinfo<<endl;
return 0;
}其输出结果:
Orign string is :
This is Winter, Winter is a programmer. Do you know Winter?
After replace Winter with wende, the string is :
This is wende, wende is a programmer. Do you know wende?如果不用replace函数,则可以使用erase和insert来替换,也能实现string_replace函数的功能:
void string_replace(string & strBig, const string & strsrc, const string &strdst) {
string::size_type pos=0;
string::size_type srclen=strsrc.size();
string::size_type dstlen=strdst.size();
while( (pos=strBig.find(strsrc, pos)) != string::npos){
strBig.erase(pos, srclen);
strBig.insert(pos, strdst);
pos += dstlen;
}
}当然,这种方法没有使用replace来得直接。
2 string 和 C风格字符串
现在看了这么多例子,发现const char* 可以和string 直接转换,例如我们在上面的例子中,使用
string_replace(strinfo, "Winter", "wende");来代用
void string_replace(string & strBig, const string & strsrc, const string &strdst) 在C语言中只有char* 和 const char*,为了使用起来方便,string提供了三个函数满足其要求:
const charT* c_str() const
const charT* data() const
size_type copy(charT* buf, size_type n, size_type pos = 0) const 其中:
c_str 直接返回一个以\0结尾的字符串。
data 直接以数组方式返回string的内容,其大小为size()的返回值,结尾并没有\0字符。
copy 把string的内容拷贝到buf空间中。
你或许会问,c_str()的功能包含data(),那还需要data()函数干什么?看看源码:
const charT* c_str () const
{ if (length () == 0) return ""; terminate (); return data (); }原来c_str()的流程是:先调用terminate(),然后在返回data()。因此如果你对效率要求比较高,而且你的处理又不一定需要以\0的方式结束,你最好选择data()。但是对于一般的C函数中,需要以const char*为输入参数,你就要使用c_str()函数。
对于c_str() data()函数,返回的数组都是由string本身拥有,千万不可修改其内容。其原因是许多string实现的时候采用了引用机制,也就是说,有可能几个string使用同一个字符存储空间。而且你不能使用sizeof(string)来查看其大小。详细的解释和实现查看Effective STL的条款15:小心string实现的多样性。
另外在你的程序中,只在需要时才使用c_str()或者data()得到字符串,每调用一次,下次再使用就会失效,如:
string strinfo("this is Winter");
...
//最好的方式是:
foo(strinfo.c_str());
//也可以这么用:
const char* pstr=strinfo.c_str();
foo(pstr);
//不要再使用了pstr了, 下面的操作已经使pstr无效了。
strinfo += " Hello!";
foo(pstr);//错误!会遇到什么错误?当你幸运的时候pstr可能只是指向"this is Winter Hello!"的字符串,如果不幸运,就会导致程序出现其他问题,总会有一些不可遇见的错误。总之不会是你预期的那个结果。
3 string 和 Charactor Traits
了解了string的用法,该详细看看string的真相了。前面提到string 只是basic_string的一个typedef。看看basic_string 的参数:
template <class charT, class traits = char_traits<charT>,
class Allocator = allocator<charT> >
class basic_string
{
//...
}char_traits不仅是在basic_string 中有用,在basic_istream 和 basic_ostream中也需要用到。
就像Steve Donovan在过度使用C++模板中提到的,这些确实有些过头了,要不是系统自己定义了相关的一些属性,而且用了个typedef,否则还真不知道如何使用。
但复杂总有复杂道理。有了char_traits,你可以定义自己的字符串类型。当然,有了char_traits < char > 和char_traits < wchar_t > 你的需求使用已经足够了,为了更好的理解string ,咱们来看看char_traits都有哪些要求。
如果你希望使用你自己定义的字符,你必须定义包含下列成员的结构: 表达式 描述
char_type 字符类型
int_type int 类型
pos_type 位置类型
off_type 表示位置之间距离的类型
state_type 表示状态的类型
assign(c1,c2) 把字符c2赋值给c1
eq(c1,c2) 判断c1,c2 是否相等
lt(c1,c2) 判断c1是否小于c2
length(str) 判断str的长度
compare(s1,s2,n) 比较s1和s2的前n个字符
copy(s1,s2, n) 把s2的前n个字符拷贝到s1中
move(s1,s2, n) 把s2中的前n个字符移动到s1中
assign(s,n,c) 把s中的前n个字符赋值为c
find(s,n,c) 在s的前n个字符内查找c
eof() 返回end-of-file
to_int_type(c) 将c转换成int_type
to_char_type(i) 将i转换成char_type
not_eof(i) 判断i是否为EOF
eq_int_type(i1,i2) 判断i1和i2是否相等
想看看实际的例子,你可以看看sgi STL的char_traits结构源码.
现在默认的string版本中,并不支持忽略大小写的比较函数和查找函数,如果你想练练手,你可以试试改写一个char_traits , 然后生成一个case_string类, 也可以在string 上做继承,然后派生一个新的类,例如:ext_string,提供一些常用的功能,例如:
定义分隔符。给定分隔符,把string分为几个字段。
提供替换功能。例如,用winter, 替换字符串中的wende
大小写处理。例如,忽略大小写比较,转换等
整形转换。例如把"123"字符串转换为123数字。
这些都是常用的功能,如果你有兴趣可以试试。其实有人已经实现了,看看Extended STL string。如果你想偷懒,下载一个头文件就可以用,有了它确实方便了很多。要是有人能提供一个支持正则表达式的string,我会非常乐意用。
4 string 建议
使用string 的方便性就不用再说了,这里要重点强调的是string的安全性。
string并不是万能的,如果你在一个大工程中需要频繁处理字符串,而且有可能是多线程,那么你一定要慎重(当然,在多线程下你使用任何STL容器都要慎重)。
string的实现和效率并不一定是你想象的那样,如果你对大量的字符串操作,而且特别关心其效率,那么你有两个选择,首先,你可以看看你使用的STL版本中string实现的源码;另一选择是你自己写一个只提供你需要的功能的类。
string的c_str()函数是用来得到C语言风格的字符串,其返回的指针不能修改其空间。而且在下一次使用时重新调用获得新的指针。
string的data()函数返回的字符串指针不会以'\0'结束,千万不可忽视。
尽量去使用操作符,这样可以让程序更加易懂(特别是那些脚本程序员也可以看懂)
5 小结
难怪有人说:
string 使用方便功能强,我们一直用它!
6 附录
string 函数列表 函数名 描述
begin 得到指向字符串开头的Iterator
end 得到指向字符串结尾的Iterator
rbegin 得到指向反向字符串开头的Iterator
rend 得到指向反向字符串结尾的Iterator
size 得到字符串的大小
length 和size函数功能相同
max_size 字符串可能的最大大小
capacity 在不重新分配内存的情况下,字符串可能的大小
empty 判断是否为空
operator[] 取第几个元素,相当于数组
c_str 取得C风格的const char* 字符串
data 取得字符串内容地址
operator= 赋值操作符
reserve 预留空间
swap 交换函数
insert 插入字符
append 追加字符
push_back 追加字符
operator+= += 操作符
erase 删除字符串
clear 清空字符容器中所有内容
resize 重新分配空间
assign 和赋值操作符一样
replace 替代
copy 字符串到空间
find 查找
rfind 反向查找
find_first_of 查找包含子串中的任何字符,返回第一个位置
find_first_not_of 查找不包含子串中的任何字符,返回第一个位置
find_last_of 查找包含子串中的任何字符,返回最后一个位置
find_last_not_of 查找不包含子串中的任何字符,返回最后一个位置
substr 得到字串
compare 比较字符串
operator+ 字符串链接
operator== 判断是否相等
operator!= 判断是否不等于
operator< 判断是否小于
operator>> 从输入流中读入字符串
operator<< 字符串写入输出流
getline 从输入流中读入一行
#include "../../lib/sinalib/StrLib/str.h"
#include "StrLib.h"
using namespace std;
int main()
{
string strinfo="Please input your name:";
cout << strinfo ;
cin >> strinfo;
if( strinfo == "winter" )
cout << "you are winter!"<<endl;
strinfo += " , Welcome to China!";
cout << strinfo ;
string strtmp = "How are you? " + strinfo;
for(int i = 0 ; i < strtmp.size(); i ++)
cout<<strtmp<<strtmp.size();
}
前言: string 的角色
1 string 使用
1.1 充分使用string 操作符
1.2 眼花缭乱的string find 函数
1.3 string insert, replace, erase 2 string 和 C风格字符串
3 string 和 Charactor Traits
4 string 建议
5 小结
6 附录前言: string 的角色
C++ 语言是个十分优秀的语言,但优秀并不表示完美。还是有许多人不愿意使用C或者C++,为什么?原因众多,其中之一就是C/C++的文本处理功能太麻烦,用起来很不方便。以前没有接触过其他语言时,每当别人这么说,我总是不屑一顾,认为他们根本就没有领会C++的精华,或者不太懂C++,现在我接触perl, php, 和Shell脚本以后,开始理解了以前为什么有人说C++文本处理不方便了。
举例来说,如果文本格式是:用户名 电话号码,文件名name.txt
Tom 23245332
Jenny 22231231
Heny 22183942
Tom 23245332
...
现在我们需要对用户名排序,且只输出不同的姓名。
那么在shell 编程中,可以这样用:
awk '{print $1}' name.txt | sort | uniq
简单吧?
如果使用C/C++ 就麻烦了,他需要做以下工作:
先打开文件,检测文件是否打开,如果失败,则退出。
声明一个足够大得二维字符数组或者一个字符指针数组
读入一行到字符空间
然后分析一行的结构,找到空格,存入字符数组中。
关闭文件
写一个排序函数,或者使用写一个比较函数,使用qsort排序
遍历数组,比较是否有相同的,如果有,则要删除,copy...
输出信息
你可以用C++或者C语言去实现这个流程。如果一个人的主要工作就是处理这种类似的文本(例如做apache的日志统计和分析),你说他会喜欢C/C++么?
当然,有了STL,这些处理会得到很大的简化。我们可以使用 fstream来代替麻烦的fopen fread fclose, 用vector 来代替数组。最重要的是用 string来代替char * 数组,使用sort排序算法来排序,用unique 函数来去重。听起来好像很不错 。看看下面代码(例程1):
#i nclude <string>
#i nclude <iostream>
#i nclude <algorithm>
#i nclude <vector>
#i nclude <fstream>
using namespace std;
int main(){
ifstream in("name.txt");
string strtmp;
vector<string> vect;
while(getline(in, strtmp, '\n'))
vect.push_back(strtmp.substr(0, strtmp.find(' ')));
sort(vect.begin(), vect.end());
vector<string>::iterator it=unique(vect.begin(), vect.end());
copy(vect.begin(), it, ostream_iterator<string>(cout, "\n"));
return 0;
}
也还不错吧,至少会比想象得要简单得多!(代码里面没有对错误进行处理,只是为了说明问题,不要效仿).
当然,在这个文本格式中,不用vector而使用map会更有扩充性,例如,还可通过人名找电话号码等等,但是使用了map就不那么好用sort了。你可以用map试一试。
这里string的作用不只是可以存储字符串,还可以提供字符串的比较,查找等。在sort和unique函数中就默认使用了less 和equal_to函数, 上面的一段代码,其实使用了string的以下功能:
存储功能,在getline() 函数中
查找功能,在find() 函数中
子串功能,在substr() 函数中
string operator < , 默认在sort() 函数中调用
string operator == , 默认在unique() 函数中调用
总之,有了string 后,C++的字符文本处理功能总算得到了一定补充,加上配合STL其他容器使用,其在文本处理上的功能已经与perl, shell, php的距离缩小很多了。 因此掌握string 会让你的工作事半功倍。
1 string 使用
其实,string并不是一个单独的容器,只是basic_string 模板类的一个typedef 而已,相对应的还有wstring, 你在string 头文件中你会发现下面的代码:
extern "C++" {
typedef basic_string <char> string;
typedef basic_string <wchar_t> wstring;
} // extern "C++"
由于只是解释string的用法,如果没有特殊的说明,本文并不区分string 和 basic_string的区别。
string 其实相当于一个保存字符的序列容器,因此除了有字符串的一些常用操作以外,还有包含了所有的序列容器的操作。字符串的常用操作包括:增加、删除、修改、查找比较、链接、输入、输出等。详细函数列表参看附录。不要害怕这么多函数,其实有许多是序列容器带有的,平时不一定用的上。
如果你要想了解所有函数的详细用法,你需要查看basic_string,或者下载STL编程手册。这里通过实例介绍一些常用函数。
1.1 充分使用string 操作符
string 重载了许多操作符,包括 +, +=, <, =, , [], <<, >>等,正式这些操作符,对字符串操作非常方便。先看看下面这个例子:tt.cpp(例程2)
#i nclude <string>
#i nclude <iostream>
using namespace std;
int main(){
string strinfo="Please input your name:";
cout << strinfo ;
cin >> strinfo;
if( strinfo == "winter" )
cout << "you are winter!"<<endl;
else if( strinfo != "wende" )
cout << "you are not wende!"<<endl;
else if( strinfo < "winter")
cout << "your name should be ahead of winter"<<endl;
else
cout << "your name should be after of winter"<<endl;
strinfo += " , Welcome to China!";
cout << strinfo<<endl;
cout <<"Your name is :"<<endl;
string strtmp = "How are you? " + strinfo;
for(int i = 0 ; i < strtmp.size(); i ++)
cout<<strtmp;
return 0;
}
下面是程序的输出
-bash-2.05b$ make tt
c++ -O -pipe -march=pentiumpro tt.cpp -o tt
-bash-2.05b$ ./tt
Please input your name:Hero
you are not wende!
Hero , Welcome to China!
How are you? Hero , Welcome to China!
有了这些操作符,在STL中仿函数都可以直接使用string作为参数,例如 less, great, equal_to 等,因此在把string作为参数传递的时候,它的使用和int 或者float等已经没有什么区别了。例如,你可以使用:
map<string, int> mymap;
//以上默认使用了 less<string>
有了 operator + 以后,你可以直接连加,例如:
string strinfo="Winter";
string strlast="Hello " + strinfo + "!";
//你还可以这样:
string strtest="Hello " + strinfo + " Welcome" + " to China" + " !";
看见其中的特点了吗?只要你的等式里面有一个 string 对象,你就可以一直连续"+",但有一点需要保证的是,在开始的两项中,必须有一项是 string 对象。其原理很简单:
系统遇到"+"号,发现有一项是string 对象。
系统把另一项转化为一个临时 string 对象。
执行 operator + 操作,返回新的临时string 对象。
如果又发现"+"号,继续第一步操作。
由于这个等式是由左到右开始检测执行,如果开始两项都是const char* ,程序自己并没有定义两个const char* 的加法,编译的时候肯定就有问题了。
有了操作符以后,assign(), append(), compare(), at()等函数,除非有一些特殊的需求时,一般是用不上。当然at()函数还有一个功能,那就是检查下标是否合法,如果是使用:
string str="winter";
//下面一行有可能会引起程序中断错误
str[100]='!';
//下面会抛出异常:throws: out_of_range
cout<<str.at(100)<<endl;
了解了吗?如果你希望效率高,还是使用[]来访问,如果你希望稳定性好,最好使用at()来访问。
1.2 眼花缭乱的string find 函数
由于查找是使用最为频繁的功能之一,string 提供了非常丰富的查找函数。其列表如下:
函数名 描述 find 查找 rfind 反向查找 find_first_of 查找包含子串中的任何字符,返回第一个位置 find_first_not_of 查找不包含子串中的任何字符,返回第一个位置 find_last_of 查找包含子串中的任何字符,返回最后一个位置 find_last_not_of 查找不包含子串中的任何字符,返回最后一个位置 以上函数都是被重载了4次,以下是以find_first_of 函数为例说明他们的参数,其他函数和其参数一样,也就是说总共有24个函数 :
size_type find_first_of(const basic_string& s, size_type pos = 0)
size_type find_first_of(const charT* s, size_type pos, size_type n)
size_type find_first_of(const charT* s, size_type pos = 0)
size_type find_first_of(charT c, size_type pos = 0)
所有的查找函数都返回一个size_type类型,这个返回值一般都是所找到字符串的位置,如果没有找到,则返回string::npos。有一点需要特别注意,所有和string::npos的比较一定要用string::size_type来使用,不要直接使用int 或者unsigned int等类型。其实string::npos表示的是-1, 看看头文件:
template <class _CharT, class _Traits, class _Alloc>
const basic_string<_CharT,_Traits,_Alloc>::size_type
basic_string<_CharT,_Traits,_Alloc>::npos
= basic_string<_CharT,_Traits,_Alloc>::size_type) -1;
find 和 rfind 都还比较容易理解,一个是正向匹配,一个是逆向匹配,后面的参数pos都是用来指定起始查找位置。对于find_first_of 和find_last_of 就不是那么好理解。
find_first_of 是给定一个要查找的字符集,找到这个字符集中任何一个字符所在字符串中第一个位置。或许看一个例子更容易明白。
有这样一个需求:过滤一行开头和结尾的所有非英文字符。看看用string 如何实现:
#i nclude <string>
#i nclude <iostream>
using namespace std;
int main(){
string strinfo=" //*---Hello Word!......------";
string strset="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int first = strinfo.find_first_of(strset);
if(first == string::npos) {
cout<<"not find any characters"<<endl;
return -1;
}
int last = strinfo.find_last_of(strset);
if(last == string::npos) {
cout<<"not find any characters"<<endl;
return -1;
}
cout << strinfo.substr(first, last - first + 1)<<endl;
return 0;
}
这里把所有的英文字母大小写作为了需要查找的字符集,先查找第一个英文字母的位置,然后查找最后一个英文字母的位置,然后用substr 来的到中间的一部分,用于输出结果。下面就是其结果:
Hello Word
前面的符号和后面的符号都没有了。像这种用法可以用来查找分隔符,从而把一个连续的字符串分割成为几部分,达到 shell 命令中的 awk 的用法。特别是当分隔符有多个的时候,可以一次指定。例如有这样的需求:
张三|3456123, 湖南
李四,4564234| 湖北
王小二, 4433253|北京
...
我们需要以 "|" ","为分隔符,同时又要过滤空格,把每行分成相应的字段。可以作为你的一个家庭作业来试试,要求代码简洁。
1.3 string insert, replace, erase
了解了string 的操作符,查找函数和substr,其实就已经了解了string的80%的操作了。insert函数, replace函数和erase函数在使用起来相对简单。下面以一个例子来说明其应用。
string只是提供了按照位置和区间的replace函数,而不能用一个string字串来替换指定string中的另一个字串。这里写一个函数来实现这个功能:
void string_replace(string & strBig, const string & strsrc, const string &strdst) {
string::size_type pos=0;
string::size_type srclen=strsrc.size();
string::size_type dstlen=strdst.size();
while( (pos=strBig.find(strsrc, pos)) != string::npos){
strBig.replace(pos, srclen, strdst);
pos += dstlen;
}
}看看如何调用:
#i nclude <string>
#i nclude <iostream>
using namespace std;
int main() {
string strinfo="This is Winter, Winter is a programmer. Do you know Winter?";
cout<<"Orign string is :\n"<<strinfo<<endl;
string_replace(strinfo, "Winter", "wende");
cout<<"After replace Winter with wende, the string is :\n"<<strinfo<<endl;
return 0;
}其输出结果:
Orign string is :
This is Winter, Winter is a programmer. Do you know Winter?
After replace Winter with wende, the string is :
This is wende, wende is a programmer. Do you know wende?如果不用replace函数,则可以使用erase和insert来替换,也能实现string_replace函数的功能:
void string_replace(string & strBig, const string & strsrc, const string &strdst) {
string::size_type pos=0;
string::size_type srclen=strsrc.size();
string::size_type dstlen=strdst.size();
while( (pos=strBig.find(strsrc, pos)) != string::npos){
strBig.erase(pos, srclen);
strBig.insert(pos, strdst);
pos += dstlen;
}
}当然,这种方法没有使用replace来得直接。
2 string 和 C风格字符串
现在看了这么多例子,发现const char* 可以和string 直接转换,例如我们在上面的例子中,使用
string_replace(strinfo, "Winter", "wende");来代用
void string_replace(string & strBig, const string & strsrc, const string &strdst) 在C语言中只有char* 和 const char*,为了使用起来方便,string提供了三个函数满足其要求:
const charT* c_str() const
const charT* data() const
size_type copy(charT* buf, size_type n, size_type pos = 0) const 其中:
c_str 直接返回一个以\0结尾的字符串。
data 直接以数组方式返回string的内容,其大小为size()的返回值,结尾并没有\0字符。
copy 把string的内容拷贝到buf空间中。
你或许会问,c_str()的功能包含data(),那还需要data()函数干什么?看看源码:
const charT* c_str () const
{ if (length () == 0) return ""; terminate (); return data (); }原来c_str()的流程是:先调用terminate(),然后在返回data()。因此如果你对效率要求比较高,而且你的处理又不一定需要以\0的方式结束,你最好选择data()。但是对于一般的C函数中,需要以const char*为输入参数,你就要使用c_str()函数。
对于c_str() data()函数,返回的数组都是由string本身拥有,千万不可修改其内容。其原因是许多string实现的时候采用了引用机制,也就是说,有可能几个string使用同一个字符存储空间。而且你不能使用sizeof(string)来查看其大小。详细的解释和实现查看Effective STL的条款15:小心string实现的多样性。
另外在你的程序中,只在需要时才使用c_str()或者data()得到字符串,每调用一次,下次再使用就会失效,如:
string strinfo("this is Winter");
...
//最好的方式是:
foo(strinfo.c_str());
//也可以这么用:
const char* pstr=strinfo.c_str();
foo(pstr);
//不要再使用了pstr了, 下面的操作已经使pstr无效了。
strinfo += " Hello!";
foo(pstr);//错误!会遇到什么错误?当你幸运的时候pstr可能只是指向"this is Winter Hello!"的字符串,如果不幸运,就会导致程序出现其他问题,总会有一些不可遇见的错误。总之不会是你预期的那个结果。
3 string 和 Charactor Traits
了解了string的用法,该详细看看string的真相了。前面提到string 只是basic_string的一个typedef。看看basic_string 的参数:
template <class charT, class traits = char_traits<charT>,
class Allocator = allocator<charT> >
class basic_string
{
//...
}char_traits不仅是在basic_string 中有用,在basic_istream 和 basic_ostream中也需要用到。
就像Steve Donovan在过度使用C++模板中提到的,这些确实有些过头了,要不是系统自己定义了相关的一些属性,而且用了个typedef,否则还真不知道如何使用。
但复杂总有复杂道理。有了char_traits,你可以定义自己的字符串类型。当然,有了char_traits < char > 和char_traits < wchar_t > 你的需求使用已经足够了,为了更好的理解string ,咱们来看看char_traits都有哪些要求。
如果你希望使用你自己定义的字符,你必须定义包含下列成员的结构: 表达式 描述
char_type 字符类型
int_type int 类型
pos_type 位置类型
off_type 表示位置之间距离的类型
state_type 表示状态的类型
assign(c1,c2) 把字符c2赋值给c1
eq(c1,c2) 判断c1,c2 是否相等
lt(c1,c2) 判断c1是否小于c2
length(str) 判断str的长度
compare(s1,s2,n) 比较s1和s2的前n个字符
copy(s1,s2, n) 把s2的前n个字符拷贝到s1中
move(s1,s2, n) 把s2中的前n个字符移动到s1中
assign(s,n,c) 把s中的前n个字符赋值为c
find(s,n,c) 在s的前n个字符内查找c
eof() 返回end-of-file
to_int_type(c) 将c转换成int_type
to_char_type(i) 将i转换成char_type
not_eof(i) 判断i是否为EOF
eq_int_type(i1,i2) 判断i1和i2是否相等
想看看实际的例子,你可以看看sgi STL的char_traits结构源码.
现在默认的string版本中,并不支持忽略大小写的比较函数和查找函数,如果你想练练手,你可以试试改写一个char_traits , 然后生成一个case_string类, 也可以在string 上做继承,然后派生一个新的类,例如:ext_string,提供一些常用的功能,例如:
定义分隔符。给定分隔符,把string分为几个字段。
提供替换功能。例如,用winter, 替换字符串中的wende
大小写处理。例如,忽略大小写比较,转换等
整形转换。例如把"123"字符串转换为123数字。
这些都是常用的功能,如果你有兴趣可以试试。其实有人已经实现了,看看Extended STL string。如果你想偷懒,下载一个头文件就可以用,有了它确实方便了很多。要是有人能提供一个支持正则表达式的string,我会非常乐意用。
4 string 建议
使用string 的方便性就不用再说了,这里要重点强调的是string的安全性。
string并不是万能的,如果你在一个大工程中需要频繁处理字符串,而且有可能是多线程,那么你一定要慎重(当然,在多线程下你使用任何STL容器都要慎重)。
string的实现和效率并不一定是你想象的那样,如果你对大量的字符串操作,而且特别关心其效率,那么你有两个选择,首先,你可以看看你使用的STL版本中string实现的源码;另一选择是你自己写一个只提供你需要的功能的类。
string的c_str()函数是用来得到C语言风格的字符串,其返回的指针不能修改其空间。而且在下一次使用时重新调用获得新的指针。
string的data()函数返回的字符串指针不会以'\0'结束,千万不可忽视。
尽量去使用操作符,这样可以让程序更加易懂(特别是那些脚本程序员也可以看懂)
5 小结
难怪有人说:
string 使用方便功能强,我们一直用它!
6 附录
string 函数列表 函数名 描述
begin 得到指向字符串开头的Iterator
end 得到指向字符串结尾的Iterator
rbegin 得到指向反向字符串开头的Iterator
rend 得到指向反向字符串结尾的Iterator
size 得到字符串的大小
length 和size函数功能相同
max_size 字符串可能的最大大小
capacity 在不重新分配内存的情况下,字符串可能的大小
empty 判断是否为空
operator[] 取第几个元素,相当于数组
c_str 取得C风格的const char* 字符串
data 取得字符串内容地址
operator= 赋值操作符
reserve 预留空间
swap 交换函数
insert 插入字符
append 追加字符
push_back 追加字符
operator+= += 操作符
erase 删除字符串
clear 清空字符容器中所有内容
resize 重新分配空间
assign 和赋值操作符一样
replace 替代
copy 字符串到空间
find 查找
rfind 反向查找
find_first_of 查找包含子串中的任何字符,返回第一个位置
find_first_not_of 查找不包含子串中的任何字符,返回第一个位置
find_last_of 查找包含子串中的任何字符,返回最后一个位置
find_last_not_of 查找不包含子串中的任何字符,返回最后一个位置
substr 得到字串
compare 比较字符串
operator+ 字符串链接
operator== 判断是否相等
operator!= 判断是否不等于
operator< 判断是否小于
operator>> 从输入流中读入字符串
operator<< 字符串写入输出流
getline 从输入流中读入一行