1、在C++,不同类型的指针是不能直接赋值的,必须强转
void *p; int *i = (int *)p;
2、class是C++的核心,也是面向对象的核心基础
class Person { public: string name; private: int age; public: int sex; };
3、引入了命名空间防止大型项目中代码冲突
4、new关键字的使用
int *p = new int; *p = 100; //分配内存的时候可以同时赋值,前两句代码相当于 int * p = new int (100); cout << *p << endl;//输出100 delete p;//用完记得删除, 用new分配的内存必须用delete释放,不能用free int *p = new int[10];//用new创建数组 //删除的时候不能这样写 :delete p; 这样写是删除了数组中第一个地址,会造成内存泄漏; delete []p;
5、inline 内联关键字的作用:内联函数不用为函数调用,而是直接把函数的代码嵌入到调用的语句中。
int main(){ for ( int i = 0 ; i < 100; i++) { int a = max (i , 50 ); } return 0 ; } inline int max (int a, int b ){ if ( a > b) return a ; else return b ; } //inline函数适合用在代码少,且大量的调用的函数。
6、引用:一个变量的别名,不是一个变量的地址
int main(){ int a = 1 ; int b = 2 ; int & c = a; //正确,c就是a的别名 c = b; //错误,不能修改 int & c; c = a; //错误,c不能单独命名 swap ( a , b ); cout << "a = " << a << endl ; // a = 2; cout << "b = " << b << endl ; // b = 1; return 0 ; } void swap( int & a , int & b ){ int temp = a ; a = b; b = temp; }
7、函数的默认实参
void Func( int a = 100 ){ cout << "a = " << a << endl ; } int main(){ Func (); // 打印 a = 100; return 0 ; }
8、函数重载:函数名字相同,只是参数不同
void Func(){} void Func( int a ){} void Func( string str ){}
9、模板函数
template <class T> T Add( T a , T b) { return a + b ; } int main() { int a = 1; int b = 2; Add( a , b ); return 0; }
10、private,public,protected
在类里面如果没有权限限定符,默认是private
在结构体里面没有权限限定符,默认是public
11、一个unsigned int类型与int类型相加的结果是一个unsigned int类型。
int main() { int unsigned a = 1 ; int b = - 2; std ::cout << a + b << std :: endl;//输出的类型是一个unsigned int类型,输出结果为:4294967295 }
12、两个字符串位置相邻,实际上是一个整体。
std :: cout << "asdfasdf" "asdfasdf" "ASdfasd" << std:: endl ; //输出的是一个整体
13、定义在函数体内部的内置类型变量不会被初始化,定义在函数体外部的内置类型变量会自动初始化。
int main() { int a ; std ::cout << a << std:: endl ; return 0 ; } //程序会报错,提示局部变量a未被初始化。
14、extern关键字
extern int i;//声明i int j;//声明并定义j extern int k = 1;//定义
15、引用就是别名,引用不是对象,它只是一个已存在的对象的别名,一旦绑定一个对象之后就无法再绑定其他对象。
int main() { int b = 0; int &a = b ;//a指向b int &c;//报错,引用必须初始化 int &d = 1;//报错,引用只能绑定到对象上。 std ::cout << &b << std :: endl; std ::cout << &a << std :: endl;//a和b都指向同一个地址 return 0 ; }
16、void*是一种特殊的指针类型,可以存储任意对象的地址。
int main() { int val = 0 ; void *a = &val ;//合法 long *b = &val;//非法 }
17、const对象在默认情况下只在本文件内有效。若要共享const对象,就必须在前面加上extern关键字
extern const int val = 0;//定义了并初始化了一个常量,这个常量可以在其他文件中使用。 extern const int val;//声明了一个val,说明val并不是本文件独有的,它的定义在别处。
18.const指针
int main() { int val = 1 ; int num = 2 ; int * const p = & val; //p是一个const指针,p将一直指向val,可以修改所指对象的值(也就是val的值) p = &num ; //错误,p是一个const指针 const int num1 = 1 ; const int *const p1 = & num1 ;//p1是一个指向常量对象的常量指针,既不能指向其他地址,也不能修改所指对象的值(也就是num1的值), } //如果表达式太复杂,可以通过从右往左阅读来弄清楚。拿上面例子来讲:p旁边的符号是const,说明p是一个常量对象,再往左的符号是*,表示p是一个常量指针,再往左是int,则说明p是一个指向int类型对象的常量指针。 //通过上面方法可以得知p1是一个指向int类型常量的常量指针。
19、关于指向常量的指针和引用。
int main() { int val = 1 ; const int *p1 = & val; //正确,可以指向一个非常量对象 const int &a = val ; //正确,可以绑定一个非常量对象 } //1、指向常量的指针可以指向非常量,但不可以通过该指针来修改这个非常量的值。 //2、常量引用也可以绑定非常量对象,但是不可以通过该引用来对绑定的这个对象作出修改。
20、字符串的几种初始化方法,一般使用常用方法即可。
int main() { string s = "aaaa" ; //常用 string s1 ( 10, 'a') ;//输出10个a string s2 = { "aaaa" }; string s3 = ( "aaaa" ); string s4 ( "aaaa" ); string s5 = ( "aaaa" ); string s6 = string( "aaaa" ) ; } //若使用=来初始化则表示<strong>拷贝初始化</strong>,不使用=初始化表示<strong>直接初始化</strong>
21、getline会读取一整行,如果希望在得到的字符串中保留空格,就用getline替换>>运算符。
int main() { string s ; while ( getline (cin , s))//读入一整行直到遇到文件末尾 { cout << s << endl ; } return 0 ; }
22、字面值和string对象相加时,必须确保加号两边至少有一个是string对象
int main() { string s1 = "1" ; string s2 = "1" + "2"; //错误,加号两边都是不是string对象 string s3 = s1 + "1"; //正确,加号左边是一个string对象 string s4 = "1" + "2" + s1 ; //错误,第一个加号两边都不是string对象 string s5 = s1 + s2; //正确,加号两边都是string对象 return 0 ; }
23、标准库类型vector表示对象的集合,是一个类模板
int main() { vector <int > a ; vector <vector < int>> b; return 0 ; }
vector对象能够高效的增长,所以在定义的时候设定大小是没有必要的, 甚至性能更差。但是如果所有初始元素都是一样的值的话是可以在定义的时候初始化的。
int main() { vector <int > vInt = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }; for ( auto &i : vInt ) { i *= i; } return 0 ; } //与string类型一样,若要改变元素的值,需要在循环的时候对循环变量定义为引用类型
24、迭代器
int main() { string s = "asdfasdfasfd" ; auto a = s .begin (); //a表示第一个元素 auto b = s .end (); //b表示尾元素的下一个元素 cout << *a << endl;//打印结果是输出'a' cout << *b << endl;//错误,因为b是指向尾元素的下一个元素,没有实际指向一个元素,所以不能对他进行解引用或者递增操作 vector <int > vInt ( 10, 1 ); auto c = vInt .begin (); auto d = vInt .end (); return 0 ; }
容器运算符 *iter 返回该迭代器所指元素的引用 iter->men 返回该迭代器所指元素中名为men的成员,可写成(*iter).men; ++iter 指向该迭代器的下一个元素 --iter 指向该迭代器的上一个元素 iter1 == iter2 判断两个迭代器是否相等,如果两个迭代器指向的是同一个元素或者两个迭代器都指向同一个容器的尾后迭代器,说明相等;反之不相等。 iter1 != iter2
对于迭代器和vector对象的使用上的注意事项:
1、vector对象可以动态的增长,所以这也限制了使用for(rangefor)来循环对vector中push_back元素。
2、另一个就是:如果定义了一个vector的迭代器,之后再向vector对象中push_back元素的话,原迭代器会失效。
int main() { vector <string > vStr; auto a = vStr.begin ();//初始化之后立即定义一个迭代器 vStr.push_back ( "11" ); vStr.push_back ( "22" ); auto b = vStr.begin ();//push_back元素之后重新等一个迭代器 cout << *a << endl;//报错,因为vStr为空,a是指向尾迭代器 cout << *b << endl;//成功,b是指向第一个元素 return 0 ; }
25、字符数组有一种额外的初始化方式。
int main() { char a[] = { 'c' , '+' , '+' , '\n' }; char b[] = "c++"; char c[4] = "c++"; //这3种初始化方式效果是一样的 return 0 ; }
26、指针也可以实现迭代器的功能,但必须得指定头指针和尾后指针,但不安全,c++11提供了新方法:begin()和end()
int main() { const int size = 10 ; int arr[size] = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }; int *pHead = arr; int *pEnd = &arr[size];//arr[size]这个元素并不存在,所以递增操作或者也不能解引用来取值,这里唯一的用处就是提供其地址用于初始化pEnd。 for ( int *p = pHead ; p != pEnd ; ++p) cout << *p << " " ; cout << endl; return 0 ; }
27、c风格的字符串操作
int main() { char a [] = { '1' , '2' }; cout << strlen( a ) << endl ;//错误,没有以空字符结束 char b[] = "11111 22222"; char d[] = "22222 11111"; strcmp(b, d);//如果b>d返回真值,如果b<d返回负值 char s[100] = {}; //初始化一个用于存放结果的数组 strcpy(s, d);//把d复制给s strcat(s, d);//把d加在s的后面 char *p = s; while ( *p ) { cout << *p << endl ;//输出操作结果 p++; } return 0 ; } //对于大多数应用程序来说使用标准库string比c风格字符串更安全高效
//string对象转换为c风格字符串 int main() { string s = "11111111" ; const char *cc = s .c_str(); return 0 ; } //使用数组初始化vector对象 int main() { int arr[] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 }; vector <int> vInt1 ( begin( arr ), end (arr) );//把数组的值全部用于初始化vector对象 //也可以用数组的一部分值来初始化 vector <int> vInt2 ( arr + 1, arr + 3 ); vector <int> vInt3 ( begin(arr) + 1, end(arr) - 1); return 0 ; }
28、用指针来遍历多维数组。
int main() { int arr[3][4] = { 1, 2, 3, 4, 5, 4, 3, 2, 1, 13, 3, 4 }; for ( auto p = arr ; p != arr + 3 ; ++p )//先循环一维得到一维的地址 for ( auto d = *p; d != *p + 4; ++d )//根据一维的地址得到二维地址,并输出值 cout << *d << " " ; return 0 ; } //也可以使用标准函数begin()和end()来实现相同功能,更简洁安全 int main() { int arr[3][4] = { 1 , 2 , 3 , 4 , 5 , 4 , 3 , 2 , 1 , 13 , 3 , 4 }; for ( auto p = begin (arr ); p != end (arr ); ++ p ) for ( auto q = begin (*p ); q != end (*p );++ q) cout << *q << " " ; return 0 ; }
29、关于递增递减运算符的选用
int i = 0; j = 0; j = ++i;//前置版本,i = 1; j = 1 //改变i的值并返回给j j = i++;//后置版本,i = 2; j = 1 //存储i的值并计算返回j //尽量选用前置版本的递增递减运算符: //1、后置版本的需要将原始值储存下来以便返回这个未修改的内容,如果我们不需要这个未修改的内容,那么这个后置版本的操作就是一种浪费。 //2、基于上面的原因,对于整数和指针类型来说,编译器可能会对这种工作进行一定的优化,但对于相对复杂的迭代器类型来说,这种额外的工作是非常耗性能的。
30、sizeof运算符返回一条表达式或者一个类型名字所占的字节数,它不会求实际类型的值
sizeof(type) sizeof expr//返回表达式结果类型的大小 int main() { string s , * p ; sizeof( string );//返回string类型的对象的大小 sizeof s;//s类型的大小,与上面一样 sizeof p;//指针所占空间大小 sizeof *p;//因为sizeof不会求实际类型的值,所以即使p没有初始化也没有影响 return 0 ; } //对string和vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间
31、算术转换的规则是运算符的运算对象转换成最宽的类型,如:
1、一个运算类型是long double,那么不论另一个运算类型是什么都会转换为long double。
2、当表达式中有浮点型也有整型时,整数类型将转换为浮点型。
32、显示转换
1、旧版的强制类型转换 type (expr)//函数形式的 type expr//c语言风格的 int main() { int i = 11 , j = 2; double res1 = double (i ) / j; return 0 ; } 2、新版的强制类型转换 cast-name<type>(expr) cast-name分别有:static_cast、dynamic_cast、const_cast和reinterpret_cast static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以使用它。 dynamic_cast:支持运算时类型识别。 const_cast:只能改变运算对象的底层const。 reinterpret_cast:通常为运算对象的位模式提供较低层次上的重新解释。 int main() { int i = 11 , j = 2; //使用static_cast double res = static_cast <double> (i) / j ; void *d = & i; int *p = static_cast <int*> (d); //使用const_cast const char *pc ; char *cp = const_cast <char*> (pc); //使用reinterpret_cast int *pi; char *ic = reinterpret_cast <char*> (pi); string str = ic;//错误,ic实际存放的是指向int类型的指针 return 0; }
33、局部静态对象:如果我们想让一个局部变量的生命周期贯穿函数以及之后的时间。我们可以把变量定义为static类型,局部静态对象在程序执行第一次经过对象时被初始化,直到程序运行终止时才销毁。
int count() { static int res = 0 ; return ++res ; } int main() { for ( int i = 0 ; i < 10; ++i ) cout << count() << endl; return 0 ; }
34、建议变量和函数的声明放在头文件中,在源文件中定义。
35、在拷贝大的类类型对象或者容器对象时是比较低效的。而且有些类类型对象还不支持拷贝。所以使用引用是非常明智的选择。
//使用引用来比较 bool isShorter( string &s1, string &s2 ) { return s1.size() < s2.size(); } //使用拷贝来比较 bool isShorter( string s1, string s2 ) { return s1.size() < s2.size (); } //使用常量引用来比较,如果函数不用修改形参的值,最好使用常量引用 bool isShorter( const string &s1, const string &s2 ) { return s1.size() < s2.size(); } int main() { string s1 = "111111" ; string s2 = "11111111" ; cout << isShorter( s1, s2 ) << endl; return 0; }
36、函数的数组形参,数组有两个特性:(1)不允许拷贝数组。(2)使用数组时会转为指针
void set(const *int); void set(const int[]); void set(const int[20]);//这里的20表示期望含有多少元素,实际上不一定。 //上面3中表达方式是等价的,都是const *int类型的形参 int i = 0; int j[2] = {1, 2}; set( &i );//正确 set( j );//正确
37、函数中管理指针的3中方法
第一种方法:必须要求数组本身包含结束标记 void set( const char *p ) { if ( p ) while (* p ) { cout << *p ++ << endl; } } void set(const int *p) { if(p) { while(*p) cout << *p << endl; } } //这用方法用于有结束标记的情况如char类型。如果用于int类型就会无效。 第二种方法:是传递一个数组的首元素和尾后元素的指针 void print( const int *begin , const int *end) { while ( begin != end) { cout << *begin ++ << endl; } } int main() { int i[] = { 1, 2 , 3 }; print (begin (i), end(i)); return 0 ; } 第三种方法:是在函数的形参中添加一个表示数据大小的形参,在以前c和c++的程序中经常使用这种方法 void print( const int *p , const int size ) { for ( int i = 0 ; i < size; ++i ) { cout << *(p + i ) << endl ; } } int main() { int i [] = { 1 , 2 , 3 }; print (i , end ( i) - begin (i )); return 0 ; }
38、多维数组就是数组的数组
//p指向数组的首元素,该元素是包含10个整数的数组 void set(int (*p)[10], int rowSize){} //等价于 void set(int p[][10], int rowSize){}
39、返回引用的函数
char & get_value (string &s , int index) { return s [ index] ; } int main() { string s = "11111" ; get_value (s , 0 ) = '2'; cout << s << endl ;//输出21111 return 0 ; }
40、返回数组指针的函数
func(int i)//表示调用func时需要一个int型实参 (*func(int i))//表示可以对该函数的结果解引用 (*func(int i))[10]//表示解引用该函数将得到一个大小为10的数组 int (*func(int i))[10]//表示数组中的元素是int类型
41、函数指针:函数指针指向的是函数,函数类型由它的返回值类型和形参类型共同决定。
如:bool compare( const string & , const string &); 则该函数的类型是bool( const string &, const string &), 则指向该函数的指针是:bool (*p)( const string &, const string &) //函数的重载指针 void set( int *); void set(char *); void (*p)( int * ) = set; //p默认指向了 set(int *) //声明语句直接使用函数指针显得冗长,可以使用typedef来简化。 //下面四个是等价声明 typedef void func1(int *); //func是函数类型 typedef void (*funcp1)(int *);//funcp是函数指针 using func2 = void( int *); using funcp2 = void(*)(int *); 使用的时候直接: void use(int a, int b, func); void use(int a, int b, funcp); //用容器把函数存储起来 int add( const int & a , const int & b ) { return a + b ; } int reduce( const int & a , const int &b ) { return a - b ; } int multiply( const int &a , const int & b) { return a * b ; } int divide( const int &a , const int & b) { return a / b ; } using p = int (*)( const int &, const int &); vector < p> b{ add, reduce, multiply, divide } ; int main() { for ( auto a : b ) { cout << a( 1, 2) << endl ; } return 0 ; }
42、如果一个类允许其他类或者函数访问它的非公有成员,可以让这个其他类或者函数成为他们的友元。
class Person { friend istream & read ( istream& is , Person& person );//友元的声明仅仅指定了访问权限 private : string name; string address; }; inline istream& read( istream& is, Person& person) { is >> person.name >> person.address;//虽然name和address是私有变量,但是成为友元之后可以访问。 return is; }
43、如果我们希望能修改类的某个数据成员,即使在一个const成员函数中,我们可以通过在数据成员的声明前加上mutable关键字
class Person { public : void Total () const; private : mutable int sum; }; void Person:: Total () const { ++sum; }
44、要想让某个成员函数作为友元,必须按顺序来设计程序。以满足声明和定义的依赖关系。
1.首先定义Manager类,其中声明clear函数,但不定义它。在clear使用Screen之前必须先声明Screen 2.然后定义Screen,包括对clear友元声明 3.最后定义clear,这样才能正常使用Screen的成员。
</pre><pre code_snippet_id="1708766" snippet_file_name="blog_20160605_46_4473679" name="code" class="cpp"><pre name="code" class="cpp">class Screen; class Manager { public : using ScreenIndex = vector < string>:: size_type ; inline void clear( ScreenIndex ); private : vector <Screen > screens ; }; class Screen { friend void Manager:: clear (ScreenIndex ); //friend class Manager; private : pos width = 0 ; pos height = 0 ; pos cursor = 0 ; string contents ; }; inline void Manager::clear ( ScreenIndex index ) { if ( index > screens.size()) return; Screen & sc = screens[index]; sc.contents = string( sc.width * sc.height, ' ' ); }
45、当在类的外部定义成员函数时,必须提供类名和函数名
void Manager::clear ( ScreenIndex index )//因为提供了类名,所以我们可以直接使用类中其他的成员:ScreenIndex、screens等 { if ( index > screens.size()) return; Screen & sc = screens[index]; sc.contents = string( sc.width * sc.height, ' ' ); } //但如果是在函数返回值要使用类中的成员,就必须指明这个返回值是属于哪个类的 Screen::pos Screen::size() cosnst { return height * width; }
46、如果类的成员是const、引用、或者属于某种没有提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。
class Test { public : //这个版本的构造函数是对数据成员执行赋值操作 Test (int a, int b , int c ) { val1 = a; //正确 val2 = b; //错误,不能给const赋值 val3 = c; //错误,val3没有初始值 } //下面这个版本是显式的初始化引用和const,是正确做法 //Test(int a, int b, int c) :val1(a), val2(b), val3(c){} private: int val1; const int val2; int &val3; }; //最好使用第二个版本
47、类成员的初始化顺序是由它们在类中定义的顺序决定的
class Test { int i; int j; public: Test(int val ) : j(val),i(j){} //这里i会先初始化,j后初始化,这样初始化的结果是用一个未定义的j来初始化i,这种做法是错误的,我们应该尽量避免这种情况。 }
48、声明一个用默认构造函数初始化的对象常犯的错
int main() { Person person1(); //声明并初始化一个函数,不是对象 Person person2; //定义了一个对象,并使用默认构造函数初始化 return 0 ; }
49、C++中的隐式类类型转换:如果构造函数只接受一个参数,那么它们就默认定义了这个类类型的隐式转换机制。
int main() { Sales_Data item1 ; string newBook = "11111" ; item1.combine(newBook);//合法的,这里的combine实际上是一个接受Sales_Data类型的函数,但在这里却接受了一个string类型:它是先构建一个临时的Sales_Data类型的对象,然后把newBook作为构造函数的参数传给这个临时的Sales_Data对象,然后再把这个临时对象传给combine Sales_Data item2 = "111111" ;//正确,拷贝初始化 item1.combine("11111");//错误的,是因为只允许一次类类型转换,而这里是先把字符串转换成string,然后再把这个临时的string转换成Sales_Data对象,执行了二次类类型转换,所以是错误的。 return 0 ; } //如果我们想禁止隐式类类型转换,可以在构造函数的声明出使用explicit关键字 explicit Sales_Data ( const string s ) : bookId (s ){} explicit Sales_Data ( istream& is );