string和CString均是字符串模板类,string为标准模板类(STL)定义的字符串类,已经纳入C++标准之中;
CString(typedef CStringT<TCHAR, StrTraitMFC<TCHAR>> CString)为Visual C++中最常用的字符串类,继承自CSimpleStringT类,主要应用在MFC和ATL编程中,主要数据类型有char(应用于ANSI),wchar_t(unicode),TCHAR(ANSI与unicode均可);
char*为C编程中最常用的字符串指针,一般以’/0’为结束标志;
(二) 构造
² string是方便的,可以从几乎所有的字符串构造而来,包括CString和char*;
² CString次之,可以从基本的一些字符串变量构造而来,包括char*等;
² char*没有构造函数,仅可以赋值;
² 举例:
char* psz = “joise”;
CString cstr( psz );
string str( cstr );
(三) 运算符重载
a) operator=
² string是最方便的,几乎可以直接用所有的字符串赋值,包括CString和char*;
² CString次之,可以直接用些基本的字符串赋值,包括char*等;
² char*只能由指针赋值,并且是极危险的操作,建议使用strcpy或者memcpy,而且char*在声明的时候如未赋初值建议先设为NULL,以避免野指针,令你抓狂;
² 举例:
char *psz = NULL;
psz = new char[10]; //当然,以上的直接写成char *psz = new char[10];也是一样
memset( psz, 0, 10 );
strcpy( psz, “joise” );
CString cstr;
cstr = psz;
string str;
str = psz;
str = cstr;
delete []psz;
b) operator+
² string与CString差不多,可以直接与char*进行加法,但不可以相互使用+运算符,即string str = str + cstr是非法的,须转换成char*;
² char*没有+运算,只能使用strcat把两个指针连在一起;
² 举例:
char* psz = “joise”;
CString cstr = psz;
cstr = cstr + psz;
string str = psz;
str = str + str + psz;
strcat( psz, psz );
strcat( psz, cstr );//合法
strcat( psz, str );//非法,由此可见,CString可自动转换为const char*,而string不行
c) operator +=
² string是最强大的,几乎可以与所有的字符串变量+=,包括CString和char*;
² CString次之,可以与基本的一些字符串变量进行+=而来,包括char*等;
² char*没有+=运算符,只能使用strcat把两个指针连在一起;
d) operator[]
² CString最好,当越界时会抛出断言异常;
² string与char*下标越界结果未定义;
² 举例:
char* psz = “joise”;
CString cstr = psz;
cout << cstr[8];
string str = psz;
cout << str[8];
cout << psz[8];
e) operator== 、operator!=、operator> 、operator< 、operator>= 、perator<=
² CString与string之间不可以进行比较,但均可以与char*进行比较,并且比较的是值,而不是地址;
cout << ( psz == cstr );
cout << ( psz == str );
cout << ( str == psz );
cout << ( cstr == psz );//以上代码返回均为1
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[i];
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 从输入流中读入一行
CString,int,string,char*之间的转换2007年01月06日 星期六 11:11 A.M.
string 转 CString
CString.format("%s", string.c_str());
char 转 CString
CString.format("%s", char*);
char 转 string
string s(char *);
string 转 char *
char *p = string.c_str();
CString 转 string
string s(CString.GetBuffer());
1,string -> CString
CString.format("%s", string.c_str());
用c_str()确实比data()要好.
2,char -> string
string s(char *);
你的只能初始化,在不是初始化的地方最好还是用assign().
3,CString -> string
string s(CString.GetBuffer());
GetBuffer()后一定要ReleaseBuffer(),否则就没有释放缓冲区所占的空间.
《C++标准函数库》中说的
有三个函数可以将字符串的内容转换为字符数组和C—string
1.data(),返回没有”/0“的字符串数组
2,c_str(),返回有”/0“的字符串数组
3,copy()
---------------------------
CString与int、char*、char[100]之间的转换- -
CString与int、char*、char[100]之间的转换- -
CString互转int
将字符转换为整数,可以使用atoi、_atoi64或atol。
而将数字转换为CString变量,可以使用CString的Format函数。如
CString s;
int i = 64;
s.Format("%d", i)
Format函数的功能很强,值得你研究一下。
void CStrDlg::OnButton1()
{
// TODO: Add your control notification handler code here
CString
ss="1212.12";
int temp=atoi(ss);
CString aa;
aa.Format("%d",temp);
AfxMessageBox("var is " + aa);
}
sart.Format("%s",buf);
CString互转char*
///char * TO cstring
CString strtest;
char * charpoint;
charpoint="give string a value";
strtest=charpoint;
///cstring TO char *
charpoint=strtest.GetBuffer(strtest.GetLength());
标准C里没有string,char *==char []==string
可以用CString.Format("%s",char *)这个方法来将char *转成CString。要把CString转成char *,用操作符(LPCSTR)CString就可以了。
CString转换 char[100]
char a[100];
CString str("aaaaaa");
strncpy(a,(LPCTSTR)str,sizeof(a));
///////////////////////////////////////////////////////////////
//string 转换为 char 型
char* str = strdup ( SendData.strSql.c_str() );
cout << str << endl;
char 转换为 string 型
char* str = "char 转换为 string 型";
SendData.strSql = str;
本文原创, 转载请注明出处:http://blog.csdn.net/qinjuning
上上周五的下午,也就是2012.09.07,终于向主管提出了辞职,也下定决心准备返回武汉工作,原因当然
有很多, 在此也不讲这“数不清理还乱的”事儿了,让各位笑话了。
最近,工作倒挺清闲,俗话说“人太闲了,就容易奇思乱想” ,这不我也觉得天马行空了。于是心里一直在
嘀咕着,该做点什么有意义的事情出来,开发一些App第一个浮现在我脑海里。可我脑子不好使,没有很好地
创意。每次和朋友同事聊天,“有想法,其他都是浮云”总是会从我的嘴里蹦出来,可我只能空口说白话,插科
打诨。呵呵,我也希望自己能多点创新的点子,说不定那天我也去创业了呢? 额,话说大了。还是拉回现实吧。
我只能想啊想,思啊思,偶然间联想到博客中之前对应用程序中相关信息的一系列总结,索性横下一条心,先
做个简易的任务管理器吧,所谓“麻雀虽小,五脏俱全”。希望也能在逐步完善这个任务管理器的时候,能够从中
汲取更多关于编码、设计以及效率的问题,自己也会随着这个小东西的完善而逐渐变得强大的。
差不多占用了周末时间以及晚上(我可没加班)时间,做出来了这个雏形,马马虎虎而已。废话不多说了,见
下文。
主要知识来源:
2、<<Android中获取应用程序(包)的大小-----PackageManager的使用(二)>>
3、<<Android中获取正在运行的服务-------ActivityManager.RunningServiceInfo的使用>>
4、关于制作底部导航的相关博文如下:
<<新浪微博布局学习——妙用TabHost>>
<<android底部菜单栏demo (仿网易客户端)>>
1、分类列出系统已安装程序的信息:icon(修正app icon大小)、lable、size
2、能卸载第三方应用程序
3、更新系统安装程序信息,例如:卸载、安装程序后。
4、查看、停止正在运行的Service
简单点来说,就是把我上面列出的4条单独地个体整理成一个有机的整体,整体才是有生命、充满活力的。
功能简单,程序当然也很简单。 先上图:
工程逻辑图如下:
伪设计说明
由于我们用于呈现分类的已安装程序的Activity在界面以及功能上大体一致,因此我们将这部分共性(界面等)
抽取出来形成一个抽象父类(BaseActivity),基本上所有功能都由这个抽象父类完成了,子类仅仅提供数据对象。
UML图:
然而,随着后期新功能的加入以及优化,这种在TabActivity中添加Activity的功能已经不能满足需要,甚至
现有设计本身就是欠缺地,只能在后续中重构改善设计。
缺点主要有二:
1、效率比较低下 ---- 需要同时管理多个Activity的生命周期
2、造成了数据之间的过度耦合以及代码冗余 ---- 添加新功能时,发现数据交互之间比较繁杂,例如:添加
一个对ACTION_PACKAGE_XXX的BroadcastReceiver时,需要注册多次,数据交互之间也不很麻烦。
后续优化
计划参考Android2.3 Settings中应用程序功能的实现,采用TabHost.TabContentFactory类为TabActivity提供
视图,在性能上继续优化我们的这个小东西 。
BaseActivity的部分源码如下:
/** @author http://http://blog.csdn.net/qinjuning*/ public abstract class BaseActivity extends Activity implements OnItemClickListener{ private static String TAG = "BaseActivity"; protected ListView mListView = null; protected TextView mTvAppCount = null ; private static PackageManager mPackageManager; protected static List<AppInfo> mAllAppInfoList = new ArrayList<AppInfo>() ; protected static List<AppInfo> mSystemAppInfoList = new ArrayList<AppInfo>(); protected static List<AppInfo> mThirdAppInfoList = new ArrayList<AppInfo>(); protected static List<AppInfo> mSDCardAppInfoList = new ArrayList<AppInfo>(); protected AppInfoAdapter mAppInfoAdapter = null ; //适配器 private PakcageStateChangeReceiver mPackageReceiver = new PakcageStateChangeReceiver(); private static Context mContext ; /** We had loaded the application info*/ private static boolean mHasLoadered = false ; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = this ; mPackageManager = this.getApplicationContext().getPackageManager(); if(!mHasLoadered) { mHasLoadered = true ; // we have load the application loadAppInfoList(); } initUI(R.layout.browse_app_list); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); //Attention registerReceiver(mPackageReceiver , filter); } /**设置UI*/ protected void initUI(int resId) { setContentView(resId); mListView = (ListView) findViewById(R.id.listviewApp); mListView.setAdapter(makeAppInfoAdapter()); mListView.setOnItemClickListener(this); mTvAppCount = (TextView)findViewById(R.id.app_type_count); setAppCountView(); } //构建一个BaseAdapter适配器对象,为ListView提供数据 protected BaseAdapter makeAppInfoAdapter() { mAppInfoAdapter = new AppInfoAdapter(this , getAppInfoList()); return mAppInfoAdapter ; } //返回该应用程序适合的容器 protected abstract List<AppInfo> getAppInfoList(); //查询系统应用程序信息并填充到我们的集合中 private static void loadAppInfoList() { // 查询所有已经安装的应用程序 List<ApplicationInfo> listApplications = mPackageManager .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES); Slog.v(TAG, "initAppInfoList:: listApplications Size # " + listApplications.size()); Collections.sort(listApplications, new ApplicationInfo.DisplayNameComparator(mPackageManager));// 排序 for(ApplicationInfo applicationInfo : listApplications ) { //构建一个我们的AppInfo Model类 AppInfo appInfo = makeAppInfo(applicationInfo); //获得应用程序大小...信息 try { Utility.queryPacakgeSize(applicationInfo.packageName, mPackageManager , appInfo); } catch(Exception ex) { ex.printStackTrace(); } fillListByFlag(applicationInfo.flags , appInfo); } } // 构造一个AppInfo Model对象 ,并赋值 private static AppInfo makeAppInfo(ApplicationInfo applicationInfo) { ... } //根据Flag标记分别添加至我们的集合中 private static void fillListByFlag(int flags , AppInfo appInfo) { //所有程序 mAllAppInfoList.add(appInfo); //系统内置程序 if((flags & ApplicationInfo.FLAG_SYSTEM) != 0 ) { mSystemAppInfoList.add(appInfo); appInfo.isSystem = true ; } //本来是系统程序,被用户手动更新后,该系统程序也成为第三方应用程序了 else if ((flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0){ mThirdAppInfoList.add(appInfo); } //第三方应用程序 else if ((flags & ApplicationInfo.FLAG_SYSTEM) == 0) { mThirdAppInfoList.add(appInfo); } //安装在SD卡中的程序 if ((flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { mSDCardAppInfoList.add(appInfo); } } private class PakcageStateChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Slog.v(TAG, "onReceive Action --" + action); if(Intent.ACTION_PACKAGE_CHANGED.equals(action) || Intent.ACTION_PACKAGE_ADDED.equals(action)|| Intent.ACTION_PACKAGE_REMOVED.equals(action)) { String pkgName = intent.getData().getSchemeSpecificPart() ; boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); Slog.v(TAG, "pkgName --" + pkgName + " replacing : " + replacing); if(!Intent.ACTION_PACKAGE_CHANGED.equals(action)) { if(Intent.ACTION_PACKAGE_REMOVED.equals(action)) { if(!replacing) //Removed { removeAppInfo(pkgName) ; } else { //We will receiver the ACTION_PACKAGE_ADDED later , // do something there. } } else // ACTION_PACKAGE_ADDED { if(!replacing) // 安装了新的应用程序 { addAppInfo(pkgName) ; } else //更新了应用程序 { updateAppInfo(pkgName); } } } } } } }
其他代码就不在贴了,大家有兴趣的自行研究咯。
分享永远是咱移动互联网的精神, 本次雏形版下载地址,
http://download.csdn.net/detail/qinjuning/4572769
希望大家能提供宝贵意见。 Over ~~~~
2楼lvxingang昨天 09:14留名支持!!!1楼yanbin1079415046前天 11:36支持一下,离开上海了?Re: qinjuning昨天 09:10回复yanbin1079415046n是啊 。快了,本周就回去的吧。Re: yanbin1079415046昨天 09:12回复qinjuningn加油
寻址(Addressing)是UPnP网络连接的第0步,通过寻址,设备和控制点将获得IP地址。UPnP设备和控制点可以只支持IPv4,也可以同时支持IPv4和IPv6。对于每个控制设备或控制点而言,如果自身没有实现DHCP服务器,则在它们首次接入UPnP网络时,必须通过DHCP客户端来搜寻DHCP服务器(如果它们自身实现了DHCP服务器,则可以给自己从地址池中分配IP地址)。如果存在DHCP服务器,例如托管网络中,那么设备和控制点必须使用分配到的IP地址;如果DHCP服务器不可用,例如在非托管网络中,那么设备或控制点必须使用Auto-IP来获得IP地址。
定义在RFC3927中的Auto-IP定义了设备或控制点:1)判断DHCP服务器是否可用;2)在链路本地的IP地址集合中选择一个IP地址。这种地址分配方式允许设备或控制点在托管网络和非托管网络间自由的移动。
1)判断是否使用Auto-IP
当设备或控制点既支持Auto-IP,又配置了动态地址分配,那么首先它会通过向DHCP服务器发送一条DHCPDISCOVER消息来请求一个IP地址。DHCP客户端监听DHCPOFFER响应的超时时间视具体实现而定。如果在超时时间内,设备或控制点收到了DHCPOFFER消息,则它们必须继续进行动态地址分配的操作。如果没有收到有效的DHCPOFFER消息,则它们必须使用Auto-IP来获得一个IP地址。
2)选择IP地址
为了使用Auto-IP自动配置一个IP地址,设备或控制点使用由具体实现定义的算法,在169.254/16范围内选择一个IP地址,需要注意的是,在这个范围中的最开始和最后256个地址是保留的,不能使用。
被选中的IP地址必须进行测试,来验证该地址是否已经被使用。如果该地址已经被其他设备或控制点使用了,那么就选择另外一个IP地址,重复上面的测试操作,直到达到了具体实现定义的重试次数为止。地址的选择机制必须是随机的,以保证多个设备或控制点分配的地址不会冲突。设备或控制点使用伪随机算法(使得地址取值分布在169.254.1.0和169.254.254.255之间),以尽可能减少同一时间接入UPnP网络的设备或控制点选择到同一个IP地址,并在冲突后重选到相同的IP地址序列。伪随机算法的种子可以使用设备或控制点的MAC地址。
3)测试IP地址
为了测试选择的IP地址是否发生冲突,设备或控制点使用ARP协议来探测。ARP探测通过发送带有设备或控制点硬件地址和IP地址(设为全0)的ARP请求来实现,并且必须进行ARP响应的监听。ARP探测可以多次重复发送,来最大可能的确定该IP地址没有被使用,建议ARP探测以间隔2s连续发送4次。
当成功配置了本地链路IP地址后,设备或控制点必须再发送两条免费的ARP请求,间隔2s,并填上配置的IP地址。这两条ARP请求的目的是刷新网络上其他主机过期的ARP缓存表。
配备了持久性存储器的控制设备或控制点可以记录它们选择过的IP地址,并在下次启动时使用该地址作为ARP探测的首选IP地址,这样能够增强寻址的稳定性,减少地址解析的冲突。
地址冲突检测并不仅仅在地址测试阶段使用,只要设备或控制点还在使用链路本地IP地址,那么地址冲突检测就会持续进行。在任何时候,只要设备或控制点接收到ARP包中的发送者IP地址和自己本地的IP地址冲突,且发送者的硬件地址和本地硬件地址不一样,那么地址冲突发生了,设备或控制点必须做出如下响应之一:
1)重新配置一个新的链路本地IP地址,如上面所述;
2)如果设备或控制点存在激活的TCP连接或者其他原因导致必须保持现有IP地址,并且在最近时间内(例如最近10s)还没有收到其他的冲突的ARP包,那么设备或控制点可以选择尝试保有现有地址一次。这是通过记录接收到冲突的ARP包的时间、广播一条包含本地IP地址和硬件地址作为源地址的免费ARP请求来实现的。但是,如果在收到一个冲突的ARP包之后,短时间内(例如10s内)又收到另一个冲突的ARP包,那么,设备或控制点必须马上重新通过Auto-IP方式分配新的IP地址。
设备或控制点必须使用上面两种方式之一来响应接收到的冲突ARP包,而不能忽略之。如果选择了新的IP地址,那么设备或控制点必须取消之前的通告,并以新的IP地址重新进行通告。
在成功配置了Auto-IP地址后,所有后续包含该IP作为源地址的ARP包(请求或者回复的),必须使用链路级广播而不是链路级单播来发送,这是为了及时发现重复的地址。
4)转发规则
源地址或目的地址在169.254/16范围内的IP包不能发送到路由器进行转发。相反,发送者必须通过ARP协议来获得目的硬件地址,并将数据包直接发送到位于同一链路上的目的地址。
带有多播目的地址的IP数据报以及Auto-IP源地址不能转发到本地链路之外。设备或控制点可以假定所有169.254/16范围内的目的地址是链路本地的,并且是可直达的。169.254/16范围的地址不能被子网化。
5)周期性检测可用的动态地址
通过Auto-IP机制自动配置IP地址的设备或控制点,必须周期性地检测DHCP服务器是否可用,这可以通过发送DHCPDISCOVER消息来实现。至于检测周期则是由具体实现定义,每5分钟检测一次可以保持网络带宽和连通性维护之间的平衡。如果收到DHCPOFFER响应消息,设备或控制点必须继续进行动态地址分配。一旦通过DHCP服务器分配到了IP地址,那么设备或控制点可以释放自动配置的IP地址,也可以选择持有自动分配的IP地址(持续一段时间或无限期)来保持现有的连接。
要切换到新的IP地址,设备应当取消任何在旧地址上发出的通告,并在新地址上重新通告一次。
对于具有多个IP地址的多宿主设备,该设备除了应当取消任何在旧地址上发出的通告,并在新地址上重新通告一次外,还必须在本机所有不受影响的IP地址上进行通告的更新。
6)设备命名和DNS交互
一旦设备在网络中具有有效的IP地址,它就能在网络中被定位和访问到。对于终端用户而言,设备的一个友好名称要比IP地址更易于使用。这时设备可以提供一个主机名给DHCP服务器,由DHCP服务器到DNS服务器中注册设备主机名和IP地址的映射关系,之后就可以使用主机名来访问该设备了。多数情况下,设备没有使用主机名,而是使用基于IP地址的URL链接。
7)名称到IP地址的解析
当一个设备要访问另一个以DNS名称来标识的设备时,它要通过给DNS服务器提交DNS查询请求,来获得该名称对应的IP地址。设备能够静态地使用DNS服务器列表进行预先配置,也可以通过DHCP服务器采用配置DNS服务器列表进行配置,又或者在IP地址分配后,通过发送一条DHCPINFORM消息进行配置。