C++性能优化笔记
最近着手去优化项目中一个模块的性能。该模块是用C++实现,对大量文本数据进行处理。
一开始时,没什么思路,因为不知道性能瓶颈在哪里。于是借助perf工具来对程序进行分析,找出程序的性能都消耗在哪里了。
下面对待优化的程序运行一遍,通过perf统计一下程序中哪些函数运行cpu周期占百分百最多。
我们直接看占用比靠前的这一部分,只需要把这些大头优化好,那么整体的性能就能得到提升。那些本来占用cpu周期很少的函数,再怎么优化都整体的性能也没有很大的改变。
Samples: 629K of event 'cpu-clock', Event count (approx.):
Children Self Command Shared Object Symbol
+ 8.87% 8.77% MyTest libc-2.12.so [.] __memcmp_sse4_1
+ 7.89% 7.79% MyTest libc-2.12.so [.] _int_malloc
+ 7.26% 7.18% MyTest libc-2.12.so [.] malloc
+ 6.80% 6.73% MyTest libc-2.12.so [.] _int_free
+ 4.82% 4.76% MyTest libstdc++.so.6.0. [.] std::string::_Rep::_M_dispose
+ 4.06% 4.01% MyTest MyTest [.] std::_Rb_tree<std::string, std::string, std::_Identity<std::string>, std::less<std::string>, std::allocator<std::string> >::find
+ 3.92% 3.87% MyTest libstdc++.so.6.0. [.] std::string::find
+ 3.79% 3.74% MyTest MyTest [.] std::_Rb_tree<std::string, std::pair<std::string const, std::string>, std::_Select1st<std::pair<std::string const, std::string> >, std::less<std::string>, std::allocator<std::pair<std::string const, std::string> > >::find
+ 2.35% 2.30% MyTest libstdc++.so.6.0. [.] std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string
+ 2.32% 2.30% MyTest libc-2.12.so [.] memcpy
+ 2.27% 2.24% MyTest libstdc++.so.6.0. [.] std::string::assign
+ 1.89% 1.87% MyTest libstdc++.so.6.0. [.] std::string::compare
+ 1.56% 1.54% MyTest libstdc++.so.6.0. [.] std::string::append
+ 1.43% 1.41% MyTest MyTest [.] std::map<std::string, long long, std::less<std::string>, std::allocator<std::pair<std::string const, long long> > >::operator[]
+ 1.37% 1.35% MyTest MyTest [.] meta::BaseMetaElement::do
+ 1.24% 1.22% MyTest libstdc++.so.6.0. [.] std::string::_M_mutate
+ 1.20% 1.19% MyTest libstdc++.so.6.0. [.] std::string::_Rep::_M_dispose
+ 1.19% 1.18% MyTest libc-2.12.so [.] free
+ 1.17% 1.15% MyTest MyTest [.] cppjieba::Trie::Find
+ 1.14% 1.13% MyTest MyTest [.] util::string::HalfFullTransformer::isRemainFullChar
+ 1.10% 1.09% MyTest libstdc++.so.6.0. [.] operator new
+ 1.03% 1.03% MyTest [kernel.kallsyms] [k] retint_careful
+ 0.93% 0.92% MyTest libstdc++.so.6.0. [.] std::string::_Rep::_S_create
+ 0.92% 0.91% MyTest libstdc++.so.6.0. [.] std::string::_Rep::_M_clone
+ 0.92% 0.91% MyTest libc-2.12.so [.] malloc_consolidate
+ 0.92% 0.91% MyTest libc-2.12.so [.] __strlen_sse42
+ 0.85% 0.84% MyTest libstdc++.so.6.0. [.] std::string::reserve
乍一看,基本都是c库和STL库的函数占用了大部分时间,自己实现的函数寥寥无几。
消耗时间最多的就是c库的内存分配和释放函数,再看看第11行,基本可以确认是因为代码中过多使用std::string对象,导致了内存频繁申请和释放。
代码中对字符串的处理,都是使用了string类来处理,我们做不到对string的内部的优化,也很难去实现一个比string很好的类,那么只能从string对象的使用上面入手。
1. string优化
1.1 参数的传递使用引用
这里不止是string,当函数参数只是作为输入只读时,就应该使用常量引用传参。避免不必要的对象构造和释放,在传递大的对象时,效果相差很大。
1.2 变量延时定义
string变量的定义,尽可能的放在必须要使用的时候再定义。有时候可能一个判断分支,导致一个预先定义的对象根本就没有使用,那就这个对象的构造和释放就是一个额外的消耗,这种情况必须避免。
1.3 string::find()
就是在多次字符串查找时,应该合理记录上一次查找的位置,作为下一次查找的开始位置的依据。在查找同一个值时,不难理解,如果是在字符串中,每次查找不同的值时,可以根据实际情况去处理。比如从一个地址中,先查找“市”再查找“镇”,这种情况就不用每次都从开头开始查找。当字符串很大时,效率提升会比较明显。具体什么时候使用这种处理方法,就需要自己根据实际情况去考虑了。
1.4 string拼接与善用reserve()/resize()
字符串拼接是一个很常用的操作,平时简单使用时,也不需要太多注意,怎么简单怎么来。但是,在处理大字符串时,效率就跟不上来了。
例如,需要对数据库表的每一行数据拼接成一个字符串,行数据可能很大。在不断循环表行数进行拼接时,使用string重载的+操作就显得太慢了。string对象默认初始化的空间比较小,可能每次调用+操作时都需要重新分配空间。这样当拼接一个大字符串时,就需要分配释放多次空间,还需要进行内存拷贝,这是非常耗时的。
我们应该在定义string对象时,直接指定分配空间的大小,这个值可以通过预估出来或者通过计算的来。
// 定义时
std::string str(, ); // reserve()
str.reserve(); // size()
str.resize();
如果string对象是新定义的,可以直接调用构造函数来预分配空间大小。如果string对象已经定义了,可以使用reserve()来分配一个指定大小的空间。
当预设好string对象的空间大小后,自己去用memcpy()和string::resize()去实现字符串拼接操作,这样效率上比用string的+操作要快。
1.5 string的Copy-On-Write机制
当string赋值或者拷贝时都是浅拷贝,两个string对象的实际存储字符串的地址是同一个,string中用一个引用计数的变量,来记录当前有多少个string对象使用同一个字符串存储空间,类似于共享指针。当string对象需要修改时,这个时候才会重新分配一个空间,并把字符串拷贝到新空间,string对象指向新的空间,在新的地址空间中对字符串进行修改,这就是Copy-On-Write的意思。
1.6 string转向char*处理
下面代码中,对string对象的字符串的两个不同部分分别进行处理。
方案1中,全部用string对象的方法来实现;
方案2中,把string对象转换为char*,通过对字符串地址直接进行处理,能达到意想不到的效果。
std::map<char, char> mapKV; // 对应表 ////////////////////////////////////////////////////////
// 方案1
void deal(std::string& value){
size_t size = value.size();
for(size_t i = ; i < size; ++i){
value[i] = mapKV[value[i]];
}
} void func(std::string& value){
std::string str = value.sub(,);
deal(str);
value.replace(,,str); str = value.sub(,);
deal(str);
value.replace(,,str);
} ////////////////////////////////////////////////////////
// 方案2
void deal2(char* pValue, size_t len){
for(size_t i = ; i < len; ++i, ++pValue){
*pValue = mapKV[*pValue];
}
} void func2(std::string& value){
deal2((char*)value.c_str()+, );
deal2((char*)value.c_str()+, );
}
优化点:
- 减少子string对象的生成
- 减少字符串替换
- 优化string的[]操作使用
2.map优化
看到perf的分析报告,其中第8、10、16行,看到一些红黑树查找和map的operator[]使用的cpu周期占用的也挺多。由于数据处理中使用到数据替换对应表都是用std::map来实现,std::map内部是用红黑树来实现的,查找效率会比较慢。鉴于对应表中,不需要顺序保存,所以用查找效率更高的boost::unordered_map来代替std::map。
map的内部实现是二叉平衡树(红黑树);unordered_map内部是一个hash_table。map是一个有序的容器,提供了稳定的插入删除查找效率,内存占用也相对较小;而unordered_map是无序容器,可以快速插入删除,查找效率也比map快,只是内存会占用得多一点。
2.1 unordered_map替换map
在程序中,原来数据对应表都是由map来实现的,这里把数据相关的对应表都改为用unordered_map代替。这样在数据处理的时候,通过对应表查找相关数据时的效率有很大提升。
2.2 小心map::operator[]()
map的operator[]()函数有个特性,当调用map[key]时,如果key在map中不存在,则会在map中插入一个键值对,键为key,值则默认构造一个值。map的operator[]()用起来是很方便,但是一但不注意,引入了map中不应该存在的值,很有可能就会带来一些不必要的麻烦。看下面的代码:
void Children:set(std::map<std::string, std::string>& ret) {
Parent::set(ret);
if(!ret["error"].empty()){
return;
}
} int main() {
Children ch;
std::map<std::string, std::string> ret;
ch.set(ret);
if(ret.find("error") != ret.end()){
std::cout << "error" << std::endl;
}
}
子类调用了父类的set()函数后,判断结果是否有错误。这里使用了map::operator[](),就会有问题,在外部调用了子类的set()函数后,判断返回结果集中是否存在"error"的元素,最终结果输出"error"。
所以在使用map的operator[]()函数时,一定要小心。除非你很明确知道这个key是在map中存在的,否则不要直接调用operator[]()。而应该先通过find()函数查找key是否存在于map中,再去获取key的值。
2.3 find()与operator[]()之间的使用
map并不是一个顺序容器,map的[]操作与数组的[]操作不一样。map的operator[]()与find()的实现差不多,通过参数key查找到map中的键值对并返回不同的值,只不过operator[]()在map查找不到时会插入一个键值对。
int getValue(int key){
if(map.find(key) != map.end()){
return map[key];
}
return ;
}
上面的代码,在调用operator[]()前,先用find()函数确保key存在map中,避免了2.2中提到的问题。但是在效率上却慢了,find()和operator[]()中都有查找算法,这里获取一个值就查找了两遍,这是不应该的。应改为一下这种写法:
int getValue(int key){
std::map<int,int>::iterator it = map.find(key);
if(it != map.end()){
return it->second;
}
return ;
}
2.4 map::end()优化
std::map<int, int> mapKV;
int sum(std::vector<int> vecKey){
size_t size = vecKey.size();
int sum = ;
for(size_t i = ; i < size; ++i){
std::map<int, int>::iterator it = mapKV.find(vecKey[i]);
if(it != mapKV.end()){
sum += it->second;
}
}
return sum;
}
当程序中需要多次从map中查找元素时,每一次都需要查找并判断元素是否存在。在上面的代码中第7行判断元素是否存在时,每一次都需要调用end()函数获取map的结束迭代器。还有每次查找中,都需要创建一个迭代器来结束查找的值。这些地方都可以优化,优化如下:
std::map<int, int> mapKV;
int sum(std::vector<int> vecKey){
size_t size = vecKey.size();
int sum = ;
std::map<int, int>::iterator itEnd = mapKV.end();
std::map<int, int>::iterator it = itEnd;
for(size_t i = ; i < size; ++i){
it = mapKV.find(vecKey[i]);
if(it != itEnd){
sum += it->second;
}
}
return sum;
}
在循环体外,先定义两个迭代器,并设置为map的结束迭代器,这样可以避免在循环体内中多次后去map的结束迭代器,提升效率。
3. 算法优化
在优化过程中,会碰到一些小问题,比如vector的顺序访问时,是用迭代器效率高,还是用[]效率高呢。所以在优化前,必须要把这些小点搞清楚。
下面整理一下自己测试出来的结果:
- string
在顺序访问时,迭代器 > [];
在随机访问时,it+i > str[i];
当把string转化为char*访问时:顺序,++it > ++p > p+i > p[i]; 随机,it+i > p+i > p[i]
- 迭代器
前置++ > 后置++
- vector
顺序访问,[] > ++it > it+i
随机访问,[] > it+i
- map\unordered_map
无论顺序还是随机范围,(find,if) > (find,[])
在循环查找中,用变量保存map::end()的值 > 每次调用end()
3.1 全角字符
在程序中,全角字符与半角字符的对应关系是用map来保存的,后面优化为使用unordered_map保存。
全角字符的编码范围在unicode中的FF00-FFEF内,转换为utf8编码,则以0xEF开头。在判断字符是否为全角字符时,可以优先判断字符的第一个字节是否符合全角字符的规范,这样可以省去直接调用unordered_map::find()。
//判断字符是否为全角字符的第一个字节
bool likeFullCharFirstByte(unsigned char c) {
return !(c ^ 0xEF);
}
3.2 utf8字符字节数计算
看下面的实现,依次判断第一个字节的大小范围,计算出utf8的字符长度。
int getUtfCharLen(unsigned char byte){
if (byte < 0xC0){
return ;
}else if (byte < 0xE0){
return ;
}else if (byte < 0xF0){
return ;
}else if (byte < 0xF8){
return ;
}else if (byte < 0xFC){
return ;
}else{
return ;
}
}
但是这种实现,在utf8中文字符串中,效率就有点低。因为中文的utf8字符的长度,基本都是3个字节,这上面的实现中能会多了2字节的字符的判断逻辑。
重新实现,可以根据实际情况进行调整的方案。
int getUtfCharLen(unsigned char byte){
if ((byte & 0x80) == 0x0) {
// 1:0xxx xxx
return ;
} else if ((byte & 0xF0) == 0xE0) {
// 3:1110 xxxx
return ;
} else if ((byte & 0xE0) == 0xC0) {
// 2:110x xxxx
return ;
} else if ((byte & 0xF8) == 0xF0) {
// 4:1111 0xxx
return ;
} else if ((byte & 0xC0) == 0x80) {
// 1:10xx xxxx
return ;
} else if ((byte & 0xFC) == 0xF8) {
// 5:1111 10xx
return ;
} else {
return ;
}
}
3.3 函数变量静态化
在函数内,可能为了实现函数功能需要初始化一个固定的数组或者字符串变量,该变量是不会被改变的,即只读。这个时候可以把变量声明为静态变量,使得在该函数体内只初始化一次,以后调用该函数则可以直接使用了。
/*身份证号最后一位校验码获得函数*/
char getLastVarify4ID(const std::string& id) {
int sum = ;
//int const weight[] = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
static int const weight[] = { , , , , , , , , , , , , , , , , };
//const std::string lastcode = "10X98765432";
static const char* lastcode = "10X98765432"; for (int i = ; i < ; i++) {
sum += (int) (id[i] - '') * weight[i];
}
int index = sum % ;
return lastcode[index];
}
3.4 空间换时间
在程序中处理数据需要用的对应表,有时候需要生成多个不同的对应表,简单的方法就是把保存在map的对应表塞到vector中,实现起来简单方便,使用起来也没什么问题。
只有当程序需要高频地使用这些对应表时,效率的问题才会慢慢暴露出来。
下面来针对n版本字符对应表的情况,进行优化:
// 在实现n个版本数字字母字符对应表
// 简单的方法,把每张对应表用map存起来,再把不同版本的对应表存到vector中
std::vector<std::map<char, char> > vecMaps;
// 获取对应值,index-第几个版本,c字符的对应值。vecMaps[index][c]
char ch = vecMaps[]['a']; ////////////////////////////////////////////////////////////////////////////////
// 空间换时间方法
// 分配一个n*127的二维char数组,可以保存n张ascii字符的对应表
char* pMaps = (char*)malloc(sizeof(char) * n * );
// 初始化n张对应表
for (int i = ; i < n; ++i) {
char* p = pMaps + i * ;
for (int j = ; j < ; ++j) {
*(p + j) = j;
}
}
// 设置隐私表
size_t vecSize = vecMaps.size();
for(size_t i = ; i < vecSize; ++i){
char* p = pMaps + i * ;
std::map<char, char>::iterator itEnd = vecMaps[i].end();
for (std::map<char, char>::iterator it = vecMaps[i].begin(); it != itEnd; ++it) {
*(p + it->first) = it->second;
}
}
// 获取对应值,index-第几个版本,c字符的对应值。*(pMaps+(index*127)+c)
ch = *(pMaps+(*)+'a');
改为用char数组的形式来处理后,需要分配n*127的空间。在空间上其实也未必会比STL实现方式的多多少,因为vector和map对象本身也需要占用一定的空间。但是在效率上,就可以提升很多,而且对应表范围扩展到ascii所有字符,也很方便。
如果只是针对数字字符的对应关系,可以这个基础上修改一下,只分配n*10的空间,记录一个beginChar='0',获取对应值的方式:
*(pMaps+(index*127)+c-beginChar)
3.5 函数按“完美”数据实现处理逻辑
当需要实现一个功能时,可以先实现功能再谈优化。在程序中需要实现一个功能,把字符串中的全角字符去掉,看一下原实现逻辑:
//去除全角字符
map<size_t, string> delFullChars(string &value) {
map<size_t, string> delChars;
size_t size(value.size()), i(), len(), wordCount();
string rmChars, curChar;
for (i = ; i < size; ++wordCount) {
unsigned char byte = (unsigned) value[i];
len = getUtfCharLen(byte); // 获取当前字符的字节个数
curChar.assign(value, i, len); // 获取当前字符
if (isFullChar(curChar)) { // 判断字符是否为全角字符
delChars[wordCount] = curChar;
} else {
rmChars += curChar; // 不是全角字符,追加到目标字符串
}
i += len;
}
value.assign(rmChars); // 设置最终结果字符串
return delChars;
}
实际中,在我们程序中的处理的数据,字符串中存在全角字符的情况比较少。所以在优化的时候,就把输入的字符串设定为“完美数据”(即不需要任何处理的数据),在处理过程中,只做一些必要的判断即可处理该数据。然后再考虑特殊情况(存在全角字符),把一下操作步骤都尽可能延迟。
优化后:
//去除全角字符
map<size_t, string> delFullChars(string &value) {
map<size_t, string> delChars;
//////////////////////////////////////////////////////////////////////////
// 考虑到实际情况中,传入的字符串很少会包含全角字符。所以在遇到全角字符时,再做一些必要的操作。
size_t size(value.size());
size_t wordCount();
size_t len(); const char* pValue = value.c_str();
std::string* pCurChar = NULL;
const char* pCurCharPtr = NULL; for (size_t i = ; i < size; ++wordCount) {
if (!(*pValue & 0x80)) {
// 单字节字符
++i;
++pValue;
} else {
len = getUtfCharLen((unsigned char)*pValue);
if (len == && i + len <= size && likeFullCharFirstByte(*pValue)) {
// 只有当字符的字节数为3时,并且是全角字符开头的字节,才去判断是否为全角字符
if (pCurChar == NULL) {
// 当存在可能是全角字符串时,才分配相关string对象
pCurChar = (std::string*)new std::string(, ); // 分配4字节长度的string对象 std::string(size_type n,char c)
pCurCharPtr = pCurChar->c_str();
}
memcpy((void*)pCurCharPtr, pValue, len);
if (isFullChar(*pCurChar)) {
// 找到一个需要保留的全角字符
// delChars[wordCount] = *pCurChar; // 这样会有问题,由于string的cow机制,delChars内的值string对象都执行同一个地址
delChars[wordCount] = pCurCharPtr; // *pCurChar的内存会被直接修改,所以不能把string对象传给map value.erase(i, len);
size = value.size();
pValue = value.c_str() + i;
continue; // 跳到下一个循环
}
}
i += len;
pValue += len;
}
} if (pCurChar != NULL) {
delete pCurChar;
} return delChars;
}
优化点:
- 先判断当前字符是否为单字节字符,用一个与、非位操作即可实现;减少getUtfCharLen()的调用
- 判断字符个数等于3时,并且当前字节为全角字符开头的字节
- 延迟实现string对象,并使用memcpy来直接获取全角字符
优化后的函数,针对字母数字字符串,“完美数据”的情况下效率有很大的提升;针对中文字符串,需要多执行getUtfCharLen()函数。
优化后的函数,在处理极端的字符串时,效率可能反而比较低,原因是出现在原字符串删除全角字符这一个步骤中(value.erase(i, len);)。当字符串很大时,频繁去erase()是需要多次内存拷贝,效率上反而没有字符串拼接方式的高。
4. 总结
上面介绍到的优化点,都是自己在实践中遇到并着手去优化的地方。虽然不能保证这样优化后效率就一定提高,就像我上面所说的,具体还是要根据实际情况去考虑如何优化。我这里只是指出一个可以优化的地方,和自己优化的方案。在优化过程中也不可能一步优化到位,就像我在优化“多版本对应表”那里,也是反复优化了3次。
在这里总结一下最近优化的收获,把一些优化点,做一个记录。
C++性能优化笔记的更多相关文章
- Android App性能优化笔记之一:性能优化是什么及为什么?
By Long Luo 周星驰的电影<功夫>里面借火云邪神之口说出了一句至理名言:“天下武功,唯快不破”. 在移动互联网时代,同样如此,留给一个公司的窗口往往只有很短的时间,如何把握住 ...
- U3D开发性能优化笔记(待增加版本.x)
http://blog.csdn.net/kaitiren/article/details/45071997 此总结由自己经验及网上收集整理优化内容 包括: .代码方面: .函数使用方面: .ui注意 ...
- 移动web性能优化笔记
移动web性能优化 最近看了一些文章,对移动web性能优化方法,做一个简单笔记 笔记内容主要出自 移动H5前端性能优化指南和移动前端系列——移动页面性能优化
- JS性能优化笔记搜索整理
通过网上查找资料了解关于性能优化方面的内容,现简单整理,仅供大家在优化的过程中参考使用,如有什么问题请及时提出,再做出相应的补充修改. 一. 让代码简洁:一些简略的表达方式也会产生很好的优化 eg:x ...
- MySQL性能优化笔记整理
一.测试篇 1.测试目的,就是量化找出短板(基础参数配置) 2.测试三大指标 IOPS:每秒处理的IO请求数,即IO响应速度(注意和IO吞吐量的区别) QPS:每秒请求(查询)次数 TPS:每秒事务数 ...
- Android应用性能优化笔记(java代码优化)
Java代码优化 缓存结果: 如果计算代价过高,最好把过去的结果缓存起来. 伪代码如下: result=cache.get(n); //输入参数n作为键 if(result==nul ...
- .Net+SQL Server企业应用性能优化笔记—精确查找瓶颈
首先我们需要部署一个测试环境,将Web项目的源代码拷到测试环境Web服务器IIS上,使得可以直接通过IE访问我们的网站.SQL Server环境可以部署在同一台机器上,条件允许的话有专门的数据库测试服 ...
- 【MVC+MySQL+EntityFramework】查询性能优化笔记
通过在DbContext中定了表之间的关系,查询后在View中通过item.ProjectOverHour来显示关联表数据. modelBuilder.Entity<ProjectOverHou ...
- Mysql性能优化笔记
一,索引 1.Innodb索引使用的是B+树 2.尽量简化where条件,比如不要出现 where id + 3 = 5,这无法使用索引 3.索引很大时,可以冗余一列来模拟哈希索引 4.小的表不需要使 ...
随机推荐
- spring定时任务的集中实现
转载博主:感谢博主 http://gong1208.iteye.com/blog/1773177 Spring定时任务的几种实现 近日项目开发中需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前 ...
- 醉盏的第一篇博客-关于title的换行处理
在处理title的时候,有时候我们想要换行,标签元素是不可以的,下面有两种特殊字符来实现 <!DOCTYPE ><html> <head runat="serv ...
- 转 用Oracle自带脚本 重建WMSYS用户的WMSYS.WM_CONCAT函数
https://blog.csdn.net/huaishuming/article/details/41726659?locationNum=1
- Spark最简安装
该环境适合于学习使用的快速Spark环境,采用Apache预编译好的包进行安装.而在实际开发中需要使用针对于个人Hadoop版本进行编译安装,这将在后面进行介绍. Spark预编译安装包下载——Apa ...
- Sqoop Import HDFS
Sqoop import应用场景——密码访问 注:测试用表为本地数据库中的表 1.明码访问 sqoop list-databases \ --connect jdbc:mysql://202.193. ...
- Hadoop实战:明星搜索指数统计,找出人气王
项目介绍 本项目我们使用明星搜索指数数据,分别统计出搜索指数最高的男明星和女明星. 数据集 明星搜索指数数据集,如下图所示.猛戳此链接下载数据集 思路分析 基于项目的需求,我们通过以下几步完成: 1. ...
- linux php 安装xdebug
我的环境是PHP 5.2.5,下载的xdebug是Xdebug v2.2.1 源码包 PHP 5.3.20用的是Xdebug v2.1.0 {版本一定要匹配} 下载地址为:http://xdebug ...
- DNS跳转
switch (window.location.hostname) { case "www.zcom.gov.cn" ://确定域名为 www.zcom.gov.cn //wind ...
- 《C#高效编程》读书笔记05-为类型提供ToString()方法
System.Object.ToString()是.NET环境中最常用的方法之一.编写类型时,要提供一个合理的ToString版本,否则使用者就不得不自己构造一套可以阅读的表示. public cla ...
- [Freemarker] Getting Start
Freemarker是一个模板引擎,在.NET中有类似的T4模板,FreeMarker对ASP.NET MVC也很友好,链接地址,引用官方的一幅图 模板+数据=视图 Following are the ...