Google C++編程

Google有不少本身實現的使C++代碼更加健壯的技巧、功能,以及有異於別處的C++的使用方式。html

1. 智能指針(Smart Pointers)程序員

若是確實須要使用智能指針的話,scoped_ptr徹底能夠勝任。在很是特殊的狀況下,例如對STL容器中對象,你應該只使用std::tr1::shared_ptr,任何狀況下都不要使用auto_ptr。編程

「智能」指針看上去是指針,實際上是附加了語義的對象。以scoped_ptr爲例,scoped_ptr被銷燬時,刪除了它所指向的對象。shared_ptr也是如此,並且,shared_ptr實現了引用計數(reference-counting),從而只有當它所指向的最後一個對象被銷燬時,指針纔會被刪除。數組

通常來講,咱們傾向於設計對象隸屬明確的代碼,最明確的對象隸屬是根本不使用指針,直接將對象做爲一個域(field)或局部變量使用。另外一種極端是引用計數指針不屬於任何對象,這樣設計的問題是容易致使循環引用或其餘致使對象沒法刪除的詭異條件,並且在每一次拷貝或賦值時連原子操做都會很慢。安全

雖然不推薦這麼作,但有些時候,引用計數指針是最簡單有效的解決方案。多線程

譯者注:看來,Google所謂的不一樣之處,在於儘可能避免使用智能指針:D,使用時也儘可能局部化,而且,安全第一。框架

  • 其餘C++特性

1. 引用參數(Reference Arguments)編輯器

因此按引用傳遞的參數必須加上const。函數

定義:在C語言中,若是函數須要修改變量的值,形參(parameter)必須爲指針,如int foo(int *pval)。在C++中,函數還能夠聲明引用形參:int foo(int &val)。post

優勢:定義形參爲引用避免了像(*pval)++這樣醜陋的代碼,像拷貝構造函數這樣的應用也是必需的,並且不像指針那樣不接受空指針NULL。

缺點:容易引發誤解,由於引用在語法上是值卻擁有指針的語義。

結論:

函數形參表中,全部引用必須是const:

void Foo(const string &in, string *out);

 

事實上這是一個硬性約定:輸入參數爲值或常數引用,輸出參數爲指針;輸入參數能夠是常數指針,但不能使用很是數引用形參。

在強調參數不是拷貝而來,在對象生命期內必須一直存在時可使用常數指針,最好將這些在註釋中詳細說明。bind2nd和mem_fun等STL適配器不接受引用形參,這種狀況下也必須以指針形參聲明函數。

2. 函數重載(Function Overloading)

僅在輸入參數類型不一樣、功能相同時使用重載函數(含構造函數),不要使用函數重載模仿缺省函數參數。

定義:能夠定義一個函數參數類型爲const string&,並定義其重載函數類型爲const char*。

class MyClass {
public:
  void Analyze(const string &text);
  void Analyze(const char *text, size_t textlen);
};

 

優勢:經過重載不一樣參數的同名函數,令代碼更加直觀,模板化代碼須要重載,同時爲訪問者帶來便利。

缺點:限制使用重載的一個緣由是在特定調用處很難肯定到底調用的是哪一個函數,另外一個緣由是當派生類只重載函數的部分變量會令不少人對繼承語義產生困惑。此外在閱讀庫的客戶端代碼時,因缺省函數參數形成沒必要要的費解。

結論:若是你想重載一個函數,考慮讓函數名包含參數信息,例如,使用AppendString()、AppendInt()而不是Append()。

3. 缺省參數(Default Arguments)

禁止使用缺省函數參數。

優勢:常常用到一個函數帶有大量缺省值,偶爾會重寫一下這些值,缺省參數爲不多涉及的例外狀況提供了少定義一些函數的方便。

缺點:你們常常會經過查看現有代碼肯定如何使用API,缺省參數使得複製粘貼之前的代碼難以呈現全部參數,當缺省參數不適用於新代碼時可能致使重大問題。

結論:全部參數必須明確指定,強制程序員考慮API和傳入的各參數值,避免使用可能不爲程序員所知的缺省參數。

4. 變長數組和alloca(Variable-Length Arrays and alloca())

禁止使用變長數組和alloca()。

優勢:變長數組具備渾然天成的語法,變長數組和alloca()也都很高效。

缺點:變長數組和alloca()不是標準C++的組成部分,更重要的是,它們在堆棧(stack)上根據數據分配大小可能致使難以發現的內存泄漏:「在個人機器上運行的好好的,到了產品中卻莫名其妙的掛掉了」。

結論:

使用安全的分配器(allocator),如scoped_ptr/scoped_array。

5. 友元(Friends)

容許合理使用友元類及友元函數。

一般將友元定義在同一文件下,避免讀者跑到其餘文件中查找其對某個類私有成員的使用。常常用到友元的一個地方是將FooBuilder聲明爲Foo的友元,FooBuilder以即可以正確構造Foo的內部狀態,而無需將該狀態暴露出來。某些狀況下,將一個單元測試用類聲明爲待測類的友元會很方便。

友元延伸了(但沒有打破)類的封裝界線,當你但願只容許另外一個類訪問某個成員時,使用友元一般比將其聲明爲public要好得多。固然,大多數類應該只提供公共成員與其交互。

6. 異常(Exceptions

不要使用C++異常。

優勢:

1) 異常容許上層應用決定如何處理在底層嵌套函數中發生的「不可能發生」的失敗,不像出錯代碼的記錄那麼模糊費解;

2) 應用於其餘不少現代語言中,引入異常使得C++與Python、Java及其餘與C++相近的語言更加兼容;

3) 許多C++第三方庫使用異常,關閉異常將致使難以與之結合;

4) 異常是解決構造函數失敗的惟一方案,雖然能夠經過工廠函數(factory function)或Init()方法模擬異常,但他們分別須要堆分配或新的「非法」狀態;

5) 在測試框架(testing framework)中,異常確實很好用。

缺點:

1) 在現有函數中添加throw語句時,必須檢查全部調用處,即便它們至少具備基本的異常安全保護,或者程序正常結束,永遠不可能捕獲該異常。例如:if f() calls g() calls h()h拋出被f捕獲的異常,g就要小心了,避免沒有徹底清理;

2) 通俗一點說,異常會致使程序控制流(control flow)經過查看代碼沒法肯定:函數有可能在不肯定的地方返回,從而致使代碼管理和調試困難,固然,你能夠經過規定什麼時候何地如何使用異常來最小化的下降開銷,卻給開發人員帶來掌握這些規定的負擔;

3) 異常安全須要RAII和不一樣編碼實踐。輕鬆、正確編寫異常安全代碼須要大量支撐。容許使用異常;

4) 加入異常使二進制執行代碼體積變大,增長了編譯時長(或許影響不大),還可能增長地址空間壓力;

5) 異常的實用性可能會刺激開發人員在不恰當的時候拋出異常,或者在不安全的地方從異常中恢復,例如,非法用戶輸入可能致使拋出異常。若是容許使用異常會使得這樣一篇編程風格指南長出不少(譯者注,這個理由有點牽強:-()!

結論:

從表面上看,使用異常利大於弊,尤爲是在新項目中,然而,對於現有代碼,引入異常會牽連到全部依賴代碼。若是容許異常在新項目中使用,在跟之前沒有使用異常的代碼整合時也是一個麻煩。由於Google現有的大多數C++代碼都沒有異常處理,引入帶有異常處理的新代碼至關困難。

鑑於Google現有代碼不接受異常,在現有代碼中使用異常比在新項目中使用的代價多少要大一點,遷移過程會比較慢,也容易出錯。咱們也不相信異常的有效替代方案,如錯誤代碼、斷言等,都是嚴重負擔。

咱們並非基於哲學或道德層面反對使用異常,而是在實踐的基礎上。由於咱們但願使用Google上的開源項目,但項目中使用異常會爲此帶來不便,由於咱們也建議不要在Google上的開源項目中使用異常,若是咱們須要把這些項目推倒重來顯然不太現實。

對於Windows代碼來講,這一點有個例外(等到最後一篇吧:D)。

譯者注:對於異常處理,顯然不是短短几句話可以說清楚的,以構造函數爲例,不少C++書籍上都提到當構造失敗時只有異常能夠處理,Google禁止使用異常這一點,僅僅是爲了自身的方便,說大了,無非是基於軟件管理成本上,實際使用中仍是本身決定。

7. 運行時類型識別(Run-Time Type Information, RTTI

咱們禁止使用RTTI。

定義:RTTI容許程序員在運行時識別C++類對象的類型。

優勢:

RTTI在某些單元測試中很是有用,如在進行工廠類測試時用於檢驗一個新建對象是否爲指望的動態類型。

除測試外,極少用到。

缺點:運行時識別類型意味著設計自己有問題,若是你須要在運行期間肯定一個對象的類型,這一般說明你須要從新考慮你的類的設計。

結論:

除單元測試外,不要使用RTTI,若是你發現須要所寫代碼因對象類型不一樣而動做各異的話,考慮換一種方式識別對象類型。

虛函數能夠實現隨子類類型不一樣而執行不一樣代碼,工做都是交給對象自己去完成。

若是工做在對象以外的代碼中完成,考慮雙重分發方案,如Visitor模式,能夠方便的在對象自己以外肯定類的類型。

若是你認爲上面的方法你掌握不了,可使用RTTI,但務必請三思,不要去手工實現一個貌似RTTI的方案(RTTI-like workaround),咱們反對使用RTTI,一樣反對貼上類型標籤的貌似類繼承的替代方案(譯者注,使用就使用吧,不使用也不要造輪子:D)。

8. 類型轉換(Casting

使用static_cast<>()等C++的類型轉換,不要使用int y = (int)xint y = int(x);

定義:C++引入了有別於C的不一樣類型的類型轉換操做。

優勢:C語言的類型轉換問題在於操做比較含糊:有時是在作強制轉換(如(int)3.5),有時是在作類型轉換(如(int)"hello")。另外,C++的類型轉換查找更容易、更醒目。

缺點:語法比較噁心(nasty)

結論:使用C++風格而不要使用C風格類型轉換。

1) static_cast:和C風格轉換類似可作值的強制轉換,或指針的父類到子類的明確的向上轉換;

2) const_cast:移除const屬性;

3) reinterpret_cast:指針類型和整型或其餘指針間不安全的相互轉換,僅在你對所作一切瞭然於心時使用;

4) dynamic_cast:除測試外不要使用,除單元測試外,若是你須要在運行時肯定類型信息,說明設計有缺陷(參考RTTI)。

9. 流(Streams

只在記錄日誌時使用流。

定義:流是printf()scanf()的替代。

優勢:有了流,在輸出時不須要關心對象的類型,不用擔憂格式化字符串與參數列表不匹配(雖然在gcc中使用printf也不存在這個問題),打開、關閉對應文件時,流能夠自動構造、析構。

缺點:流使得pread()等功能函數很難執行,若是不使用printf之類的函數而是使用流很難對格式進行操做(尤爲是經常使用的格式字符串%.*s),流不支持字符串操做符從新定序(%1s),而這一點對國際化頗有用。

結論:

不要使用流,除非是日誌接口須要,使用printf之類的代替。

使用流還有不少利弊,代碼一致性賽過一切,不要在代碼中使用流。

拓展討論:

對這一條規則存在一些爭論,這兒給出深層次緣由。回憶惟一性原則(Only One Way):咱們但願在任什麼時候候都只使用一種肯定的I/O類型,使代碼在全部I/O處保持一致。所以,咱們不但願用戶來決定是使用流仍是printf + read/write,咱們應該決定到底用哪種方式。把日誌做爲例外是由於流很是適合這麼作,也有必定的歷史緣由。

流的支持者們主張流是不二之選,但觀點並非那麼清晰有力,他們所指出流的全部優點也正是其劣勢所在。流最大的優點是在輸出時不須要關心輸出對象的類型,這是一個亮點,也是一個不足:很容易用錯類型,而編譯器不會報警。使用流時容易形成的一類錯誤是:

cout << this;  // Prints the addresscout << *this;  // Prints the contents

 

編譯器不會報錯,由於<<被重載,就由於這一點咱們反對使用操做符重載。

有人說printf的格式化醜陋不堪、易讀性差,但流也好不到哪兒去。看看下面兩段代碼吧,哪一個更加易讀?

cerr << "Error connecting to '" << foo->bar()->hostname.first     << ":" << foo->bar()->hostname.second << ": " << strerror(errno);fprintf(stderr, "Error connecting to '%s:%u: %s",        foo->bar()->hostname.first, foo->bar()->hostname.second,        strerror(errno));

 

你可能會說,「把流封裝一下就會比較好了」,這兒能夠,其餘地方呢?並且不要忘了,咱們的目標是使語言儘量小,而不是添加一些別人須要學習的新的內容。

每一種方式都是各有利弊,「沒有最好,只有更好」,簡單化的教條告誡咱們必須從中選擇其一,最後的多數決定是printf + read/write

10. 前置自增和自減(Preincrement and Predecrement

對於迭代器和其餘模板對象使用前綴形式(++i)的自增、自減運算符。

定義:對於變量在自增(++ii++)或自減(--ii--)後表達式的值又沒有沒用到的狀況下,須要肯定究竟是使用前置仍是後置的自增自減。

優勢:不考慮返回值的話,前置自增(++i)一般要比後置自增(i++)效率更高,由於後置的自增自減須要對錶達式的值i進行一次拷貝,若是i是迭代器或其餘非數值類型,拷貝的代價是比較大的。既然兩種自增方式動做同樣(譯者注,不考慮表達式的值,相信你知道我在說什麼),爲何不直接使用前置自增呢?

缺點:C語言中,當表達式的值沒有使用時,傳統的作法是使用後置自增,特別是在for循環中,有些人以爲後置自增更加易懂,由於這很像天然語言,主語(i)在謂語動詞(++)前。

結論:對簡單數值(非對象)來講,兩種都無所謂,對迭代器和模板類型來講,要使用前置自增(自減)。

11. const的使用(Use of const

咱們強烈建議你在任何可使用的狀況下都要使用const

定義:在聲明的變量或參數前加上關鍵字const用於指明變量值不可修改(如const int foo),爲類中的函數加上const限定代表該函數不會修改類成員變量的狀態(如class Foo { int Bar(char c) const; };)。

優勢:人們更容易理解變量是如何使用的,編輯器能夠更好地進行類型檢測、更好地生成代碼。人們對編寫正確的代碼更加自信,由於他們知道所調用的函數被限定了能或不能修改變量值。即便是在無鎖的多線程編程中,人們也知道什麼樣的函數是安全的。

缺點:若是你向一個函數傳入const變量,函數原型中也必須是const的(不然變量須要const_cast類型轉換),在調用庫函數時這尤爲是個麻煩。

結論const變量、數據成員、函數和參數爲編譯時類型檢測增長了一層保障,更好的儘早發現錯誤。所以,咱們強烈建議在任何可使用的狀況下使用const

1) 若是函數不會修改傳入的引用或指針類型的參數,這樣的參數應該爲const

2) 儘量將函數聲明爲const,訪問函數應該老是const,其餘函數若是不會修改任何數據成員也應該是const,不要調用非const函數,不要返回對數據成員的非const指針或引用;

3) 若是數據成員在對象構造以後再也不改變,可將其定義爲const

然而,也不要對const過分使用,像const int * const * const x;就有些過了,即使這樣寫精確描述了x,其實寫成const int** x就能夠了。

關鍵字mutable可使用,可是在多線程中是不安全的,使用時首先要考慮線程安全。

const位置

有人喜歡int const *foo形式不喜歡const int* foo,他們認爲前者更加一致所以可讀性更好:遵循了const總位於其描述的對象(int)以後的原則。可是,一致性原則不適用於此,「不要過分使用」的權威抵消了一致性使用。將const放在前面才更易讀,由於在天然語言中形容詞(const)是在名詞(int)以前的。

這是說,咱們提倡const在前,並非要求,但要兼顧代碼的一致性!

12. 整型(Integer Types

C++內建整型中,惟一用到的是int,若是程序中須要不一樣大小的變量,可使用<stdint.h>中的精確寬度(precise-width)的整型,如int16_t

定義:C++沒有指定整型的大小,一般人們認爲short是16位,int是32位,long是32位,long long是64位。

優勢:保持聲明統一。

缺點:C++中整型大小因編譯器和體系結構的不一樣而不一樣。

結論

<stdint.h>定義了int16_tuint32_tint64_t等整型,在須要肯定大小的整型時可使用它們代替shortunsigned long long等,在C整型中,只使用int。適當狀況下,推薦使用標準類型如size_tptrdiff_t

最常使用的是,對整數來講,一般不會用到太大,如循環計數等,可使用普通的int。你能夠認爲int至少爲32位,但不要認爲它會多於32位,須要64位整型的話,可使用int64_tuint64_t

對於大整數,使用int64_t

不要使用uint32_t等無符號整型,除非你是在表示一個位組(bit pattern)而不是一個數值。即便數值不會爲負值也不要使用無符號類型,使用斷言(assertion,譯者注,這一點頗有道理,計算機只會根據變量、返回值等有無符號肯定數值正負,仍然沒法肯定對錯)來保護數據。

無符號整型

有些人,包括一些教科書做者,推薦使用無符號類型表示非負數,類型代表了數值取值形式。可是,在C語言中,這一優勢被由其致使的bugs所淹沒。看看:

for (unsigned int i = foo.Length()-1; i >= 0; --i) ...

 

上述代碼永遠不會終止!有時gcc會發現該bug並報警,但一般不會。相似的bug還會出如今比較有符合變量和無符號變量時,主要是C的類型提高機制(type-promotion scheme,C語言中各類內建類型之間的提高轉換關係)會導致無符號類型的行爲出乎你的意料。

所以,使用斷言聲明變量爲非負數,不要使用無符號型。

13. 64位下的可移植性(64-bit Portability

代碼在64位和32位的系統中,原則上應該都比較友好,尤爲對於輸出、比較、結構對齊(structure alignment)來講:

1) printf()指定的一些類型在32位和64位系統上可移植性不是很好,C99標準定義了一些可移植的格式。不幸的是,MSVC 7.1並不是所有支持,並且標準中也有所遺漏。因此有時咱們就不得不本身定義醜陋的版本(使用標準風格要包含文件inttypes.h):

// printf macros for size_t, in the style of inttypes.h#ifdef _LP64#define __PRIS_PREFIX "z"#else#define __PRIS_PREFIX#endif// Use these macros after a % in a printf format string// to get correct 32/64 bit behavior, like this:// size_t size = records.size();// printf("%"PRIuS"\n", size);#define PRIdS __PRIS_PREFIX "d"#define PRIxS __PRIS_PREFIX "x"#define PRIuS __PRIS_PREFIX "u"#define PRIXS __PRIS_PREFIX "X"#define PRIoS __PRIS_PREFIX "o"

 

類型 不要使用 使用 備註
void *(或其餘指針類型) %lx %p  
int64_t %qd%lld %"PRId64"  
uint64_t %qu%llu%llx %"PRIu64"%"PRIx64"  
size_t %u %"PRIuS"%"PRIxS" C99指定%zu
ptrdiff_t %d %"PRIdS" C99指定%zd


注意宏PRI*會被編譯器擴展爲獨立字符串,所以若是使用很是量的格式化字符串,須要將宏的值而不是宏名插入格式中,在使用宏PRI*時一樣能夠在%後指定長度等信息。例如,printf("x = %30"PRIuS"\n", x)在32位Linux上將被擴展爲printf("x = %30" "u" "\n", x),編譯器會處理爲printf("x = %30u\n", x)

2) 記住sizeof(void *) != sizeof(int),若是須要一個指針大小的整數要使用intptr_t

3) 須要對結構對齊加以留心,尤爲是對於存儲在磁盤上的結構體。在64位系統中,任何擁有int64_t/uint64_t成員的類/結構體將默認被處理爲8字節對齊。若是32位和64位代碼共用磁盤上的結構體,須要確保兩種體系結構下的結構體的對齊一致。大多數編譯器提供了調整結構體對齊的方案。gcc中可以使用__attribute__((packed)),MSVC提供了#pragma pack()__declspec(align())(譯者注,解決方案的項目屬性裏也能夠直接設置)

4) 建立64位常量時使用LLULL做爲後綴,如:

 

int64_t my_value = 0x123456789LL;uint64_t my_mask = 3ULL << 48;

 

5) 若是你確實須要32位和64位系統具備不一樣代碼,能夠在代碼變量前使用。(儘可能不要這麼作,使用時儘可能使修改局部化)。

14. 預處理宏(Preprocessor Macros

使用宏時要謹慎,儘可能之內聯函數、枚舉和常量代替之。

宏意味着你和編譯器看到的代碼是不一樣的,所以可能致使異常行爲,尤爲是當宏存在於全局做用域中。

值得慶幸的是,C++中,宏不像C中那麼必要。宏內聯效率關鍵代碼(performance-critical code)能夠內聯函數替代;宏存儲常量能夠const變量替代;宏「縮寫」長變量名能夠引用替代;使用宏進行條件編譯,這個……,最好不要這麼作,會令測試更加痛苦(#define防止頭文件重包含固然是個例外)。

宏能夠作一些其餘技術沒法實現的事情,在一些代碼庫(尤爲是底層庫中)能夠看到宏的某些特性(如字符串化(stringifying,譯者注,使用#鏈接(concatenation,譯者注,使用##等等)。但在使用前,仔細考慮一下能不能不使用宏實現一樣效果。

譯者注:關於宏的高級應用,能夠參考C語言宏的高級應用

下面給出的用法模式能夠避免一些使用宏的問題,供使用宏時參考:

1) 不要在.h文件中定義宏;

2) 使用前正確#define,使用後正確#undef

3) 不要只是對已經存在的宏使用#undef,選擇一個不會衝突的名稱;

4) 不使用會致使不穩定的C++構造(unbalanced C++ constructs,譯者注)的宏,至少文檔說明其行爲。

15. 0和NULL(0 and NULL

整數用0,實數用0.0,指針用NULL,字符(串)用'\0'

整數用0,實數用0.0,這一點是毫無爭議的。

對於指針(地址值),究竟是用0仍是NULL,Bjarne Stroustrup建議使用最原始的0,咱們建議使用看上去像是指針的NULL,事實上一些C++編譯器(如gcc 4.1.0)專門提供了NULL的定義,能夠給出有用的警告,尤爲是sizeof(NULL)和sizeof(0)不相等的狀況。

字符(串)用'\0',不只類型正確並且可讀性好。

16. sizeof(sizeof

儘量用sizeof(varname)代替sizeof(type)

使用sizeof(varname)是由於當變量類型改變時代碼自動同步,有些狀況下sizeof(type)或許有意義,仍是要儘可能避免,若是變量類型改變的話不能同步。

Struct data;memset(&data, 0, sizeof(data));
memset(&data, 0, sizeof(Struct));

 

17. Boost庫(Boost

只使用Boost中被承認的庫。

定義:Boost庫集是一個很是受歡迎的、同級評議的(peer-reviewed)、免費的、開源的C++庫。

優勢:Boost代碼質量廣泛較高、可移植性好,填補了C++標準庫不少空白,如型別特性(type traits)、更完善的綁定(binders)、更好的智能指針,同時還提供了TR1(標準庫的擴展)的實現。

缺點:某些Boost庫提倡的編程實踐可讀性差,像元程序(metaprogramming)和其餘高級模板技術,以及過分「函數化」("functional")的編程風格。

結論:爲了向閱讀和維護代碼的人員提供更好的可讀性,咱們只容許使用Boost特性的一個成熟子集,當前,這些庫包括:

1) Compressed Pairboost/compressed_pair.hpp

2) Pointer Containerboost/ptr_container不包括ptr_array.hpp和序列化(serialization)。

咱們會積極考慮添加能夠的Boost特性,因此沒必要拘泥於該規則。

______________________________________

譯者:關於C++特性的注意事項,總結一下:

1. 對於智能指針,安全第1、方便第二,儘量局部化(scoped_ptr)

2. 引用形參加上const,不然使用指針形參;

3. 函數重載的使用要清晰、易讀;

4. 鑑於容易誤用,禁止使用缺省函數參數(值得商榷);

5. 禁止使用變長數組;

6. 合理使用友元;

7. 爲了方便代碼管理,禁止使用異常(值得商榷);

8. 禁止使用RTTI,不然從新設計代碼吧;

9. 使用C++風格的類型轉換,除單元測試外不要使用dynamic_cast;

10. 使用流還printf + read/write,it is a problem;

11. 能用前置自增/減不用後置自增/減;

12. const能用則用,提倡const在前;

13. 使用肯定大小的整型,除位組外不要使用無符號型;

14. 格式化輸出及結構對齊時,注意32位和64位的系統差別;

15. 除字符串化、鏈接外儘可能避免使用宏;

16. 整數用0,實數用0.0,指針用NULL,字符(串)用'\0';

17. 用sizeof(varname)代替sizeof(type);

18. 只使用Boost中被承認的庫。

相關文章
相關標籤/搜索