2020-07-02
个人C++学习笔记,有需要的可以拿走(可能做的不详细)。
1 /**********************************************************************************************************************************************/ 2 封装 3 /**********************************************************************************************************************************************/ 4 5 为什么使用堆内存: 6 1.使用比较大的内存时,可能超过栈内存限制 7 2.使用手动控制内存的释放,跨函数使用内存 8 3.可以根据程序实际需要来分配内存大小 9 10 new delete与malloc(),free()的区别: 11 1、new delete 是操作符,而malloc() free()是函数,在申请内存时会创建函数栈,所以new delete效率要高。 12 2、new会自动调用构造函数、delete会自动调用析构函数,而malloc和free没有这些。 13 14 引用的定义(&):int &ref = num (ref为num的引用,绑定后不可更改) 15 注:int *p = &num (这里的&是取地址符) 16 17 ->C++没有提供读取引用地址的方式,访问引用的地址,实际上就是去访问了变量的地址。<- 18 19 引用的类型必须和变量的类型是一样的(error: int num; long &ref = num;)。 20 正确: int num = 10; 21 int &ref = num; 22 引用常量的引用必须是常引用(error: int &ref = 10;) 23 正确: const int &ref = 10; 24 引用和指针使用上的区别:1、引用在声明时必须初始化,而指针可以是野指针 25 2、引用的绑定关系不可更改,而指针可以改变指针指向 26 3、有void的指针 但是没有void的引用,有二级指针,没有二级引用 27 4、有指针引用,没有引用指针 28 5、有数组指针,也有数组引用 29 引用的本质其实是指针常量,一样的不可更改绑定关系,但C++编译器在处理引用时会自动在指针常量前加上“*”号,而处理指针常量时则依旧时地址 30 31 引用在C++中的使用:1、能作参数 32 2、调用简单又能得到地址传递 33 3、效率高 34 既有指针做参数的效率,又比指针方便安全 35 36 引用作返回值注意:千万不能返回一个局部变量的引用 37 38 C++新增的函数特性: 39 1、内联函数(inline):在函数名前面加个inline就是内联函数 40 一般函数在调用的时候,都会创建函数栈和销毁函数栈,对于一下比较简短的函数,会比较浪费时间 41 而使用内联函数的话。在调用的时候会将函数内的逻辑语句替换到函数被调用的位置,这样就不会有 42 创建和销毁函数栈的动作。相当于一个带参宏替换,但是带参宏的话不会检查参数类型是否正确,不 43 安全,因此就有了内联函数,既会对参数类型进行检查,而且在调用的时候会将函数内的逻辑语句替 44 换到函数被调用的位置。 45 注意:内联函数中逻辑语句不能超过5行,否则编译器会识别为一般函数处理。 46 内联函数不能有循环体和switch语句,否则也会当作一般函数处理。 47 内联函数中不能抛出异常,例如(throw),否则也会当作一般函数处理。 48 内联函数中不能递归,否则也会当作一般函数处理。 49 50 2、函数重载(overload):允许在同一范围内(同一个类,同一文件),函数名相同,参数不同(参数个数不同/参数类型不同), 51 ->与返回值类型无关。 52 调用时会根据实参的个数和类型来调用函数。 53 ->注意不要定义容易产生二义性的函数,函数要具有唯一性。 54 55 3、函数缺省值:定义的时候必须从右往左,因为函数参数的使用顺序时从右往左。 56 如果函数参数右边的参数没有设置缺省值,则当前函数也不能设置缺省值,只有右边参数定义了缺省值 57 左边的才能定义。 58 ->注意防止二义性 59 60 61 *面向对象编程: 62 * 封装、继承、多态 63 * 封装成类 64 * 类的继承 65 * 成员函数的多态 66 * 67 * class 与 struct比较,在创建类时只有默认的权限不一样,class创建类时默认权限是private(私有的), 68 而struct在创建类时默认权限是Public(公共的) 69 * 70 * 创建对象: 71 * 1、一般对象创建(栈内存):类名+对象名 72 * 2、对象指针 (堆内存):类名+对象指针名 = new + 类名 delete + 对象指针名 73 * 3、对象数组 (堆内存):类名+对象指针名 = new + 类名[ ] delete[ ] + 对象指针名 74 (栈内存):类名+对象名[i]; 75 76 * 4、使用构造函数创建对象(栈内存) 77 * 5、匿名对象 78 * 79 * 特殊对象: 80 * 常对象(const + 类名 + 对象名),常对象只能调用 81 常函数 82 * 一般对象既能调用一般函数,又能调用常函数 83 84 * 常函数:(类型+函数名()+const)例:void show()const{} 85 * 常函数中不能修改对象的值 86 87 * 特殊的重载:常函数重载(例:void show(){} 和void show()const {} 88 * 这两个函数虽然函数名相同,参数列表相同,但是一样构成重载) 89 操作符重载 90 * 91 * 类中的特殊数据成员:例如static int studentname; 92 * 静态数据成员:不能在类内初始化只能在类内声明,只能在类外初始化,并且初始化时不需要带static,但要加上类域 93 * 一般数据成员必须通过对象才能访问,而静态数据对象可以直接通过类名直接访问。例如:cout <<student::studentname<<endl;,也可以通过对象调用。 94 95 不管有多少个对象,如果只创建了一个静态成员变量,那就永远只会有一个,通过任意对象去调用这个变量,都会是那一个变量。 96 一般数据成员对于不同的对象会有不同的成员。 97 98 静态成员函数:静态成员函数中不能访问一般成员变量,同时也不能使用this指针,可以使用类名直接调用,也可以使用对象调用。 99 只能访问静态成员变量,或者静态成员函数。 100 静态成员函数中不能访问一般成员变量,一般成员函数,同时也不能使用this指针的原因是因为静态成员函数不需要对象就可以使用,而一般函数、一般成员变量、this都需要先创建对象才可以使用。 101 102 用静态成员函数作单例模式: 103 单例模式:保证在同一时刻只有一个对象存在(常用在文件管理对象)(线程池的管理对象) 104 将构造函数、析构函数私有化(private),创建一个静态成员函数来获取一个对象和一个析构函数来销毁对象 105 106 /***********************************************************************/ 107 访问权限: 108 本类 子类 类外 109 默认: 可以 不可以 不可以 110 private: 可以 不可以 不可以 111 public: 可以 可以 可以 112 protected: 可以 可以 不可以 113 /************************************************************************/ 114 115 116 -->20200422 117 构造函数:函数名与类名相同,并且没有任何返回值 118 构造函数重载时一样需要避免二义性。 119 class student 120 { 121 public: 122 string name; 123 int age; 124 static int num; 125 public: 126 student(); <--默认构造函数 127 student(string name,int age):name(name),age(age)<--带参构造函数 128 { 129 num++; 130 } 131 void SetName(string name); 132 void show()const; 133 static int StudentAge(); 134 ~student(); 135 }; 136 137 student stu; //调用无参的构造函数 138 student stu("zhangsan",10);//调用带参的构造函数 139 //student stu(); //没有调用构造函数,调用的是()系统自动创建操作符重载 140 student *stu = new student;//调用默认的构造函数 141 142 没有调用构造函数的情况: 143 student stu4("lisi",23); 144 student stu5 = stu4; //对象stu5的创建不是调用的构造函数,而是调用的拷贝构造函数 145 146 void showstudent(student stu) //把实参值拷贝给形参时不会调用构造函数 147 { 148 stu.show(); 149 } 150 151 152 写类时需要先把默认构造函数写了 153 默认构造函数: 154 在类里没有写任何构造函数时,在创建对象时会自动创建一个默认构造函数,但值会是个随机值。 155 但是如果类里面一旦写了一个带参的构造函数,那么就不会自动生成默认构造函数。 156 (带参的构造函数会把系统创建的默认构造函数给屏蔽掉,所以默认构造函数必须要显示定义) 157 158 159 构造函数特殊其情况: 160 ->类内有成员变量为指针时: 161 浅拷贝:只是拷贝地址,这样有可能产生问题,如果地址指向的内存被销毁,那么指针将会变成野指针 162 深拷贝:重新为指针分配内存并把值拷贝过来(相当于原封不动的Copy)。 163 164 对象初始化的方式: 165 构造函数初始化: 166 初始化列表:在形参列表右边加上“:”再将参数赋值给成员变量。 167 例如:student(string name,int age):name(name),age(age) 168 { ^ ^ 169 } 170 "name(name),age(age)",括号外面的name和age是参数,括号内的是成员变量。 171 172 ->初始化列表是在给对象分配内存的时候就调用的,在构造函数之前调用 173 ->常量数据成员初始化必须在初始化列表中初始化 174 ->引用数据成员初始化时必须在初始化列表中初始化 175 ->其他类的对象作本类的数据成员时必须在初始化列表里初始化,不能放在构造函数内部初始化,放在内部初始化会多创建一个对象 176 ->父类构造一般也在初始化列表中初始化,指定调用父类的哪种构造函数 177 178 class Body 179 { 180 public: 181 Body() 182 { 183 cout << "Body()" << endl; 184 this->year = 0; 185 this->month = 0; 186 this->day = 0; 187 } 188 Body(int year,int month,int day) 189 { 190 cout << "Body(int year,int month,int day)" << endl; 191 this->year = year; 192 this->month = month; 193 this->day = day; 194 } 195 void showData() 196 { 197 cout << this->year << "-" << this->month << "-" << this->day << endl; 198 } 199 private: 200 int year; 201 int month; 202 int day; 203 }; 204 205 class student 206 { 207 public: 208 student() 209 { 210 211 } 212 student (string name,int age,int year,int month ,int day):name(name),age(age),Bd(year,month,day) 213 { 214 215 } 216 void show() 217 { 218 cout << this->name << "," << this->age << ","; 219 this->Bd.showData(); 220 cout << endl; 221 } 222 private: 223 string name; 224 int age; 225 Body Bd; 226 }; 227 228 int main(int argc, char *argv[]) 229 { 230 student stu("zhangsan",21,1998,11,18); 231 stu.show(); 232 return 0; 233 } 234 235 初始化列表执行顺序: 236 1、先执行初始化列表,再执行构造函数 237 ->2、类中有多个其他类对象作数据成员时,初始化列表执行的顺势是按照成员声明顺序来执行的,与初始化列表书写顺序无关。 238 239 析构函数定义和执行: 240 ->析构函数:函数名与类名相同,前面有"~",且不能有参数,所以析构函数不能重载,在一个类里只有一个析构函数。 241 ->析构函数跟构造函数一样也是自动调用,不过是在销毁对象或作用域结束时自动调用 242 ->析构函数执行得顺序与构造函数完全相反,先创建的对象最后析构,后创建的对象最先析构 243 244 类内有成员为指针时析构: 245 246 247 STL(标准库)的string类: 248 C++中的string跟C中字符串的区别; 249 C的字符串是 const char *str = "hello";必须以 结尾; 250 C++中的字符串是typedef basic_string<char> string; 251 C++中的string是只能装char的容器,不再是、0作结束; 252 253 string str1 = "高弟"; 254 cout << sizeof(str1) << endl;//24 255 cout << str1.size() << endl; //字节数 256 cout << str1.length() << endl; //字符个数 257 cout << str1.capacity() << endl; //容量 258 cout << str1.max_size() << endl; //最大容量 259 260 str1.append("好嗨哟"); //追加 261 str1.resever(); 262 str1.resize();' 263 264 C语音字符串转C++字符串-> string data = "hello"; 265 C++ 字符串转C语言字符串->const char * = data.c_str/data.data; 266 267 string的增删改查: 268 访问: 269 1、通过下标访问 270 for(int i = 0;i<str.size();i++) 271 { 272 cout << str1[i] << endl; //这种方法不会查找是否越界 273 cout << str1.at(i) << endl; //这种方法会查找是否越界(推荐使用) 274 } 275 276 2、迭代子访问: 277 //string::iterator it; //迭代子的本质是指针 278 for(string::iterator it = str.begin();it != str.end();it++) 279 { 280 cout << *it << endl; 281 } 282 283 可以通过一般迭代子去修改字符值 284 it = str.begin(); 285 *it = "H"; 286 cout << str1 << endl; 287 288 3、常迭代子:const_iterator 289 for(string::const_iterator it = str.cbegin();it != str.cend();it++) 290 { 291 cout << *it << endl; 292 } 293 不能用常迭代子改变值 294 295 4、反向迭代子:reverse_iterator 296 for(string::reverse_iterator it = str.rbegin();it != str.rend();it++) 297 { 298 cout << *it << endl; 299 } 300 /**************************************************************************************************************************************/ 301 ->增: 302 string src = "hello abc world abc linux abc"; 303 string str1 = "hello"; 304 string str2 = "world"; 305 ->/*append 只能往string类的末尾添加字符串*/ 306 //str1.append("abc"); //往str1后面增加字符串abc 307 //str1.append(str2.begin()+2,str2.end()); //将str2从中的“rld”增加到str1后面 308 //str1.append(5,'6'); //在Str1后面增加5个字符6 309 //str1.append("hello world",1,4); //将字符串从下标1到下标5加到str1后面 310 311 ->/*insert 可以往string类的任意位置添加*/ 312 //str1.insert(str1.begin() + 1,'H'); //往str1的下标1处插入字符H 313 //str1.insert(1,3,'H'); //往str1的下标1处插入三个字符H 314 //str1.insert(1,"hello linux"); //往str1的下标1处插入字符串“hello linux” 315 //str1.insert(0," hello world ",5); //往str1的下标0处插入字符串的前五个字符“hello” 316 //str1.insert(0,"hello world",0,5); //往str1的下标0处插入字符串“hello world”第0个下标到第5个下标的字符,既“hello” 317 /**************************************************************************************************************************************/ 318 ->删: 319 erse 320 clear ->可用empty来判断是否为空 321 pop 322 /**************************************************************************************************************************************/ 323 ->改:(用'='可以直接全部改0变) 324 assign 325 replace 326 /**************************************************************************************************************************************/ 327 ->查: 328 ->find(从左往右查)返回的是查找到的字符的下标,没找到返回-1 329 string src = "hello abc world abc linux abc"; 330 string str1 = "hello"; 331 string str2 = "world"; 332 ->rfind(从右往左查)返回的是查找到的下标,没找到报错 333 334 ->find_first_of(从左往右)查找子串中任意一个字符出现的位置 335 336 ->find_first_not_of(从左往右)查找不是子串中任意一个字符出现的位置 337 338 find_last_of 339 340 find_last_not_of 341 /****************************** ********************************************************************************************************/ 342 ->截取: 343 substr 344 345 346 只有把已初始化对象赋值给为初始化对象的时候才会调用拷贝构造函数 347 把实参对象转换成形参对象时会 348 349 350 无名对象: 351 创建对象时没有对象名,用完后就销毁,更节省内存,而用一般对象则会一直知道主函数结束才会销毁,占用内存比较高。 352 353 临时对象: 354 由函数返回的对象,如果有对象去接收这个对象,那么它的作用域将会得到延申 355 356 友元类: 357 在一个类中用friend声明的函数(友元函数) 358 1、把一个类的成员函数作为另一个类的友元函数 359 2、把一个普通函数声明为类的友元 360 3、声明为友元类后,则这个类的所有函数都能直接访问另一个类的所有数据。 361 362 友元是为了突破类的数据访问限制(private、protected) 363 364 友元会破坏类的封装性,所以尽量不要用友元突破私有限制。 365 366 内部类: 367 在一个类中声明的类,相对于内部类的外部(外部类) 368 内部类的一般函数要用两层类域才能在外部类外面实现 369 370 内部类的访问特性: 371 内部类可以直接访问外部类的所有静态数据,但是外部类不能直接访问内部类的成员。内部类相当于外部类的一个成员。 372 373 局部类:定义在函数中的类 常用在接口设置 374 作用范围只能在这个函数 375 void Test() 376 { 377 class Locale //这个类的作用域仅在这个函数里 378 { 379 public: 380 . 381 . 382 . 383 private: 384 int data; 385 //static int staticdata //error 局部类中不能定义静态数据成员,因为一般函数需要对象调用 386 387 }; 388 //函数调用 389 locale l(10); 390 l.show(); 391 } 392 393 394 /**********************************************************************************************************************************************/ 395 继承 396 /**********************************************************************************************************************************************/ 397 用“:”来继承 398 格式:子类 : 权限 父类名 399 400 继承以后: 401 ->创建子类对象的时候,需要先生父类对象再生子类对象(辈分高的最先创建),可以在初始化列表中指定使用父类哪个构造函数构造父类对象 格式:子类构造函数名 + ; +父类构造函数名 402 ->析构的时候,先析构子类再析构父类 403 ->子类继承父类的数据成员、成员函数(代码重用,体系(创建子类时只需要注意子类比父类多的属性、动作)) 404 ->父类私有数据成员,私有成员函数既不能被调用也不能被继承,构造函数、析构函数、拷贝构造函数、运算符重载不能被继承但是可以被子类调用 405 406 继承权限: 407 public:继承过来后,原来是public的还是public,protected的还是protected(父类的访问权限在子类中保持不变) 408 private:继承过来后,继承过来的公共/保护父类成员函数,公共/保护数据成员权限全部变成private 409 protected:继承过来后,继承过来的公共父类成员函数,公共数据成员权限变成protected,保护的成员权限不变,私有的还是私有 410 默认: private 411 412 多继承: 413 格式:子类 : 权限 父类1 , 权限 父类2... 414 多继承时的构造和析构顺序: 415 构造的时候,按照继承顺序构造父类,再按声明顺序构造子类中其他类对象或数据成员,再构造子类对象。析构的顺序与构造顺序相反。 416 417 #include <iostream> 418 #include <string> 419 420 using namespace std; 421 422 class A 423 { 424 public: 425 int public_data; 426 private: 427 int private_data; 428 protected: 429 int protected_data; 430 431 public: 432 A() 433 { 434 cout << "A()" << endl; 435 this->public_data = 0; 436 this->private_data = 0; 437 this->protected_data = 0; 438 } 439 A(int i,int j,int k) 440 { 441 cout << "A(int i,int j,int k)" << endl; 442 this->public_data = i; 443 this->private_data = j; 444 this->protected_data = k; 445 } 446 ~A() 447 { 448 cout << "~A()" << endl; 449 } 450 void showA() 451 { 452 cout << this->public_data << "," << this->private_data << "," << this->protected_data << endl; 453 } 454 }; 455 456 class B: public A 457 { 458 private: 459 string name; 460 public: 461 B() 462 { 463 cout << "B()" << endl; 464 } 465 B(string name,int i,int j,int k):A(i,j,k) 466 { 467 cout << "B(string name ,int i,int j, int k)" << endl; 468 this->name = name; 469 } 470 ~B() 471 { 472 cout << "~B()" << endl; 473 } 474 void showB() 475 { 476 cout << this->name << endl; 477 } 478 }; 479 480 class C: public B 481 { 482 public: 483 C() 484 { 485 cout << "C()" << endl; 486 } 487 ~C() 488 { 489 cout << "~C()" << endl; 490 } 491 void showC() 492 { 493 cout << "showC()" << endl; 494 cout << this->public_data << endl; 495 cout << this->protected_data << endl; 496 } 497 }; 498 499 int main(int argc, char *argv[]) 500 { 501 B b("zhangsan",1,2,3); 502 b.public_data = 3; 503 b.showA(); 504 C().showC(); 505 cout << "main over" << endl; 506 return 0; 507 } 508 509 510 静态联编(早期联编):根据指针或引用的类型来判断调用哪个类的函数,而不是根据对象类型来决定调用哪个类的 511 (子类指针不能指向父类对象,子类引用不能引用父类对象) 512 动态联编(晚期联编):在运行时确定函数的调用,根据指针或引用实际指向的对象来决定调用哪个类的函数 513 要实现动态连编: 514 1、在父类的同名成员函数前面加virtual(虚函数),子类可加可不加 515 2、父类的同名函数加了virtual,不一定是动态联编 516 517 virtual的本质:加了virtual后在本类中会增加一个指针,这个指针指向了底层的虚函数表,虚函数表里放的就是子类的同名函数。 518 把父类指针或引用作参数,子类的指针或引用都能都当参数传递进去 519 520 521 动态联编从另外一种角度来讲,其实就相当于重写 522 重写: 523 在父子类,子类的函数名与父类的函数名相同,参数列表相同,父类的同名函数必须有vritual 524 重写一定是动态联编,由引用和指针指向的对象来决定调用哪个类的函数 525 526 多态(重写)(重载): 527 调用同名函数时,由于参数不同或由于所调用的对象不同造成调用结果不同 528 同名函数参数不同,就是重载 529 同名同参数,但调用对象不同,就是重写 530 531 重载和重写的区别: 532 重载是在同一个类,而重写是在不同的类 533 重载要求函数名相同,但参数不同,而重写两者都要相同 534 virtual要求不同 535 重载是静态联编,重写是动态联编 536 537 重写容易发生的错误: 538 1、父类函数缺少virtual关键字,只能发送静态联编 539 2、参数不同,此时不管父类有没有vritual关键字,都会屏蔽子类的同名函数,发生静态联编 540 541 父/子类的构造/析构函数问题: 542 ->父类的默认构造函数不能省,否则子类对象会无法创建 543 ->使用父类指针指向子类对象,并通过父类指针去删除子类对象时,无法调用子类的析构函数,需要将 544 父类的析构函数定义成虚析构(虚析构是能够被子类继承的) 545 546 抽象类:有纯虚函数的类就是抽象类; 547 抽象类不能创建对象,只能声明指针或引用 548 抽象类能做框架,接口 549 抽象类的对象必须由它的子类去实现,子类要创建抽象类的对象,必须将父类中的纯虚函数全部实现 550 551 纯虚函数:在虚函数的形参列表右边写上=0 例:vritual void fun() = 0; 552 553 纯虚析构函数:(必须在抽象类内部声明,在类外实现) 554 格式: virtual ~类名() = 0; 555 实现:类域::类名(){}; 556 557 一般纯虚函数可以在抽象类中实现,也可以不实现。 558 559 560 多继承问题: 561 1、菱形继承时会出现二义性,需要利用虚继承来解决二义性 562 563 class A 564 { 565 private: 566 string name; 567 public: 568 void show() 569 { 570 cout << "A" << endl; 571 } 572 }; 573 574 class B : virtual public A 575 { 576 577 }; 578 579 class C : virtual public A 580 { 581 582 }; 583 584 class D : public B,public C 585 { 586 587 }; 588 589 int main(int argc, char *argv[]) 590 { 591 D d; 592 d.show(); 593 594 cout << "Hello World!" << endl; 595 return 0; 596 } 597 598 2、V形继承 : 使用类域指定使用哪个类的成员; 599 600 601 操作符重载: 602 1、类内重载:把操作符重载为函数,当使用这个我操作符时会调用这个函数 603 格式:返回类型 operator 重载的操作符(形参列表)、 604 参数个数由操作符的操作数决定,单目运算符一般不需要参数a++,操作this 605 双目运算符 例:a + b ;则需要1个参数,this是左操作数 606 2、类外重载: 607 类外重载需要把操作符的所有操作数都卸载参数列表里。 608 609 操作符重载时要注意的问题: 610 1、重载不能改变操作符的本义 611 2、不要改变操作符的返回值 612 3、下面的操作符不能重载: :: . ->* .* ?: 标准类型转换(static_cast ,const_cast,reinterpret_cast,dynamic_cast),sizeof 613 4、=操作符必须在类内重载不能在类外重载 614 615 输入输出流的重载(iostream): 616 617 typedef struct student 618 { 619 string name; 620 int age; 621 }stu_t; 622 623 ostream & operator <<(ostream &out,const stu_t &val) //重载一个stu_t类型的输出流 624 { 625 out<<val.name<<" "<<val.age<<endl; 626 return out; 627 } 628 629 istream & operator >> (istream &in,stu_t &val) //重载一个stu_t类型的输入流 630 { 631 cout<<"input name:"; 632 in>>val.name; 633 cout<<"input age:"; 634 in>>val.age; 635 return in; 636 } 637 638 639 指向类内成员指针:数据成员、成员函数 640 641 .* 642 ->* 643 class Student 644 { 645 public: 646 string name; 647 int age; 648 public: 649 Student() 650 { 651 652 } 653 Student(string name ,int age) 654 { 655 this->name = name; 656 this->age = age; 657 } 658 ~Student() 659 { 660 661 } 662 void show() 663 { 664 cout << "show()" << endl; 665 cout << this->name << "," << this->age << endl; 666 } 667 }; 668 669 int main(int argc, char *argv[]) 670 { 671 #if 0 672 int Student::*p; //创建一个指向数据成员的指针 673 Student stu("zhangsan",20); 674 p = &Student::age; //将数据成员地址传递给指针,但应注意这里需要使用类域 675 cout << stu.*p << endl; //创建的对象stu为普通对象,所以调用的时候用.* 676 677 Student *pfun = new Student("lisi",21); //这里创建的对象为指针,因此去调用之创建的指向数据成员的指针时需要用->* 678 cout << pfun->*p << endl; 679 delete pfun; 680 #endif 681 682 #if 1 683 void (Student::*pfun)(); //创建一个指向成员函数的指针 684 pfun = Student::show; //将show函数的地址传递给pfun 685 Student stu1("zhaosi",50); //创建一般对象 686 (stu1.*pfun)(); //一般对象使用.*调用之前创建的pfun指针 687 688 Student *stu2 = new Student("lisi",30); 这里创建的对象为指针,因此去调用之创建的指向成员函数的指针时需要用->* 689 (stu2->*pfun)(); 690 delete stu2; 691 #endif 692 return 0; 693 694 695 因为重载或重写时,虽然降低了调用者的工作量,但并没有降低编写者的工作量,所以有;1模板 696 697 模板的本质:将类型做参数 把类型做形参传递,实际的参数类型在调用的时候才确定 698 699 模板函数:只适用于函数体相同,参数个数相同,类型不同的情况 700 模板类: 701 702 模板函数例子: 703 template <typename T> //定义了一个模板类型名,也可以用template <class T>来定义 704 void add(T a,T b) //实现的模板函数 705 { 706 cout << "add()" << endl; 707 cout << a + b << endl; 708 } 709 710 int main() 711 { 712 float f1 = 1.6f,f2 = 1.7f; 713 add(f1,f2); //如果参数列表里的类型不一样,需要在调用前先声明参数类型 714 add<int>(10, 10); //如果参数列表里的类型都一样,可以用这种方法调用 715 return 0; 716 } 717 718 模板类例子: 719 template<typename T1,typename T2> //声明模板类型 720 class Student 721 { 722 private: 723 T1 name; //将数据成员定义成模板类型 724 T2 age; 725 public: 726 Student() 727 { 728 729 } 730 Student(T1 name ,T2 age); //参数需要定义成模板类型 731 ~Student() 732 { 733 734 } 735 void show(); 736 }; 737 738 template<typename T1,typename T2> //在类外实现时需要将模板类型在函数前声明 739 Student<T1,T2>::Student(T1 name ,T2 age) //实现时需要在类域后面说明一下类型的个数和类型 740 { 741 this->name = name; 742 this->age = age; 743 } 744 745 template<typename T1,typename T2> 746 void Student<T1,T2>::show() 747 { 748 cout << "show()" << endl; 749 cout << this->name << "," << this->age << endl; 750 } 751 752 int main() 753 { 754 Student<string,int> stu("zhangsan",20); //在创建对象时调用构造函数,需要在函数名后面说明类型模板里的类型 755 stu.show(); 756 return 0; 757 } 758 759 760 模板类是实现容器得技术基础 761 762 标准容器库STL:(standard template library)容器所存储得类型是未知的 763 容器:顺序 (线性)容器 764 关联容器:键值对key-value 765 766 vector: 767 使用push_back()往容器尾部添加元素 768 可以使用下标去遍历容器,也可以使用迭代子去便利容器 769 //迭代子的本质是指针 770 771 特点:底层是数组实现的 772 查找效率高 773 无序容器 774 允许有重复值 775 776 777 vevtor例子: 778 #include <iostream> 779 #include <vector> //导入头文件 780 781 using namespoace std; 782 783 template<typename T1,typename T2> //声明模板类型 784 class Student 785 { 786 ... 787 }; 788 ... 789 790 int main() 791 { 792 vector<string> V; //定义一个string类型的容器V 793 V.push_back("zhangsan"); //往容器类放入数据 794 V.push_back("lisi"); 795 796 Student<string ,int> stu0("wangwu",20); 797 vector<Student<string,int>> stu; 798 stu.push_back(stu0); 799 800 for(vector<string>::iterator it = V.begin(); it != V.end(); it++) //使用迭代子的方式去遍历容器 801 { 802 cout << *it << endl; 803 } 804 return 0; 805 } 806 807 808 809 810 通用算法遍历for_each(); 811 812 vector查找:find(自动调用==操作符重载比较(类外重载)), 813 find_if(_Predicate:一个类中的()操纵符重载) 814 find_end();从右往左找连续子集,_BinaryPredicate 815 find_if_not()查找不是 816 817 818 819 820 一般类型容器可以用==操作符判断两个容器是否相等,而自定义类型容器可以用equal进行比较,但需要重载==操作符。 821 822 使用count和count_if对成员进行数量统计 823 824 普通排序sort 825 堆排序sort_heap 826 827 去重unique:只能去除连续重复,它属于通用算法,不能改变容器大小 828 在去重前需要先排序,再统计容器大小,再去重,再改变容器大小。 829 830 使用pop_back删除容器最后一个数据,使用clear清空容器,使用erase删除指定位置的数据 831 832 去重的两种方法: 833 834 vector<Student> stu1; 835 stu1.push_back(Student("wangwu",20)); 836 stu1.push_back(Student("zhangsan",21)); 837 stu1.push_back(Student("lisi",32)); 838 stu1.push_back(Student("zhaoliu",89)); 839 stu1.push_back(Student("gaodi",18)); 840 stu1.push_back(Student("lisi",32)); 841 stu1.push_back(Student("lisi",32)); 842 843 #if 0 //去重,不改变原有顺序 844 bool flag = true; 845 vector<Student>::iterator it = stu1.begin(); 846 while((it = find_if(it ,stu1.end(),Find_lisi())) != stu1.end()) 847 { 848 if(flag) 849 { 850 it++; 851 flag = false; 852 } 853 else 854 { 855 stu1.erase(it); 856 } 857 } 858 #endif 859 860 #if 1 //使用unique去重,但是会改变原有顺序 861 sort(stu1.begin(),stu1.end(),cmpstr); //排序 862 int n = count(stu1.begin(),stu1.end(),Student("lisi",32)); //统计个数 863 unique(stu1.begin(),stu1.end()); //去重 864 stu1.reserve(stu1.size()-n+1); //改变容器大小 865 #endif 866 867 868 双端队列deque: 869 870 单端队列queue:没有at函数,不能通过下标去遍历 不能用迭代子遍历 不能用通用函数遍历 871 只能边删边遍历 872 873 874 875 876 set的容器特性:(在存数据的时候会自动排序) 877 1、底层实现(二叉树,链表),增删效率低,查询非常高 878 2、有序的容器(自动排序) 879 3、值不允许有重复 880 4、有自己的find()按值查找 erase()按值删除。不需要sort(),unique() 881 882 883 map:一个数据包含两部分:key value(键值对存在) 884 885 默认升序排序 886 less显式升序排序 887 greator显式降序排序 888 889 890 map 允许value重复,不允许key重复. 891 892 map数据的操作:查改删,只能通过key去操作value 893 894 一次存两个数据key value 以pair存在,key和value一一对应,通过key操作value 895 有序容器,对key排序 896 不允许key重复,允许value重复 897 增效率低,查询效率高 898 899 900 int main() 901 { 902 map<string ,student> stumap; 903 } 904 905 906 907 908 随机 909 有种随机:每次运行都随机 在运行时确定 910 制种 有效范围,在函数内有效 911 srand(time(NULL)); 912 int num = rand()%10000; 913 914 915 无种随机:随机一次,以后每次运行都一样,在编译时确认 916 int num = rand()%10000; 917 918 919 随机打乱(洗牌)把容器内的数据随机打乱,只能对无序的容器打乱。 920 randdom_shuffle(); 921 922 923 /**********************************************************************************************************************************************/ 924 多态 925 /**********************************************************************************************************************************************/ 926 重载和重写是构成多态的两种方式 927 928 929 930 931 932
17:35:59