C++基础知识点
1 "天命不足畏,祖宗不足法,人言不足恤" 2 3 第一天 C++基础班 ************************************============================================= 4 1.封装,继承,多态。 5 2.作用域运算符(::) 6 :: 前面不写东西,表示调用全局变量 7 3.名字控制(命名空间(namespace)): 8 1)命名空间只能在全局范围内定义 9 2)命名空间可以嵌套命名空间 10 3)命名空间是开放的,可以随时向命名空间添加东西(变量,函数,类等)。 11 4) 声明和实现可分离 12 5) 匿名命名空间,默认此空间中的函数,变量等只在当前文件中有效,相当于给每个变量、函数前加入static. 13 namespace TestA 14 { 15 int a; //这是在命名空间中未初始化的定义。 当在命名空间进行定义时,而且此命名空间放在头文件,当在其他文件进行 16 //调用时,会产生重复定义的问题,编译不过。 17 int b = 9; //这是在命名空间中初始化的定义 18 extern int c; //这是命名空间中的声明。 19 } 20 21 4.条件编译 22 #pragma once (第一种) 23 #ifndef ___ ; #define _____; #endif; ____ 代表一个定义的名字。(第二种) 24 25 5.bool 类型: bool 类型的变量可以把"任何非0值"转换为1. 26 27 6.三目运算符:C和C++的区别: 28 C语言中,三目运算符返回的是一个变量的值,没有内存空间,不可以修改。 29 int a = 10; int b = 20; a > b ? a : b; (a > b ? a : b) = 30(不可以这样操作,原因如上); 30 C++语言中,三目运算符返回的是一个变量本身,具有内存空间,可以修改。 31 int a = 10; int b = 20; a > b ? a : b; (a > b ? a : b) = 30(可以这样操作,原因如上) 32 33 7.当一个变量有内存空间时(只要在堆栈区),就可以通过它的地址修改内存里的值 34 35 8.const 的使用 36 如:C语言中,const 修饰的变量是一个只读变量,具有内存。 37 C++语言中,const 修饰的为常量不具有内存(有额外情况,下面有),它放在符号表中。 38 39 1)C++ 中两种情况不一样 40 const int a = 10; 此时 a 不具有内存,(用一个常量初始化)它放在符号表中。所以不可以对它进行修改。 41 int b = 9; const int a = b; 此时a 具有内存(在栈区),因为对它进行赋值的是一个变量b(栈区),可以通过指针间接赋值改变 a 的值。 42 (上面这种情况为用一个变量初始化 const 变量,所以它可以修改)。 43 44 2)在局部变量中。( C语言 和 C++ 语言这种情况不一样) 45 C++语言中: const int a = 9; int *p = (int *)&a; *p = 99; 46 此时当对 const 常量 a 取地址,编译器会为它分配地址(在栈区);但是 a 常量从符号表中取值,不可被修改。 47 *p 修改的只是分配的内存空间的值。打印结果: a = 9; *p = 99; 48 C语言中,变量 a 可以被修改。 49 50 3)在全局变量( C语言 和 C++ 语言这两种情况一样) 51 extern const int a = 9; int *p = (int *)&a; *p = 99;(第一种) 52 const int a = 9(它放在全局变量); int *p = (int *)&a; *p = 99(第二种); 53 此时对 const 变量 a 取地址或声明 extern 时,编译器会为它分配地址(在只读变量区);这段代码可以编译通过,但是运行阶段会报错,不 54 以被运行。 55 56 4)C语言中 const 修饰的变量为外部链接(和普通变量一样)。 57 C++语言中,const 修饰的变量为内部链接(和普通变量不一样)。 58 不管是C语言还是C++ 语言,普通变量都是外部链接。 59 60 5)const 与 #define 的不同, const 有类型,有作用域,可进行编译器类型安全检查。 61 #define 无类型,不重视作用域(从定义开始到文件结尾),不可以进行安全检查。 62 63 9.extern 的使用 64 适用范围是不同文件之间变量的调用。(不可以再同一文件不同函数之间使用) 65 在不同文件之间调用一个外部链接的变量时,只用在调用的文件中写一个 extern 关键字就可以了。 66 当调用一个内部链接时,需要在被调文件和调用文件中都写 extern 关键字。 67 68 10.变量可以定义数组 int n = 9; int arr[n]; 69 :不同编译器编译环境效果不同。在vs2013中不可以通过;在QT,linux中可以通过。支持C99的编译器可以通过。 70 71 11.using 编译指令 与 using 声明的区别 72 namespace A{ int a = 9; func(){} } 73 namespace B{int a = 9; func(){} } 74 using 声明: int main(){using A::a; cout << a <<endl; int a = 6;}当第二个 a 被定义时,函数编译通不过,命名冲突 75 using 编译指令:int main(){using namespace A; cout <<a<<endl; int a = 6; cout << a << endl;} 打印结果为:9, 6; 76 此时打印最近定义的那个。 77 int main(){ using namespace A; using namespace B; cout << a << endl; } 此时就会报错。编译器不知道选择哪个a 78 注意:使用using声明或using编译指令会增加命名冲突的可能性。也就是说,如果有名称空间,在代码中 79 使用作用域解析运算符,则不会出现二义性。 80 81 12.在 C 语言中,重复定义多个同名的全局变量是合法的(不正规),但在C++ 中是不合法的。 82 83 84 "天命不足畏,祖宗不足法,人言不足恤" 85 第二天 C++基础班 ************************************============================================= 86 87 1.变量都是有内存空间的,当它在堆栈区时可以通过指针修改它的值。 88 89 2.引用:本质地址传递;编译器帮忙做了基本地址传递的部分。 90 int &b = a;=====> int * const b = &a;(实质) b常指针,指向不可以改变。 91 (本质上:引用是一个常指针) 92 1)同一个内存块可以取多个别名。int& b = a; int& c = a; 93 2)引用的基本语法:int a = 10; int &b = a;(引用) b = 100; cout << a << endl; cout << b << endl; 94 输出的结果都为 100. 95 3)指针的引用; int *p = NULL; int *&p1 = p; p1 = (int *)malloc(sizeof(int)); 96 4)数组的引用:(两种方式) 97 1. typedef int ARR[10];/*建立数组类型*/ int a[10];/*创建一个数组*/; ARR &p = a;/*对数组的引用*/ p[i] = i;/*对数组的某个元素赋值*/ 98 2.int (&p1)[10] = a;/*直接建立数组类型,进行引用,并初始化。*/ p1[i] = i; /*对数组的某个元素赋值*/ 99 "注意内容:" 100 5) 引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故 而类型与原类型保持一致,且不 101 分配内存。与被引用的变量有相同的地址 102 声明引用变量时必须初始化, int& b; //错误 103 必须确保引用是和一块合法的内存块关联( NULL 不可内存不可引用)。 104 可以建立数组引用。 105 6)引用一旦初始化,不能改变。(原因如上:本质) 106 int &b = a; b = c (这种写法并不是改变b的指向,是将c的值赋给 b; b并没有指向c); 107 7)(常量引用)const int &b = a; b的值、指向都不能改变。 108 因为它等同于: const int *const b = &a;(这种写法常用来保护"实参"不受"形参"的改变). 109 8)C++编译器在编译过程中使用"常指针"作为引用的内部实现,因此引用所占用的空间大小与指针相同。(非官方的说法,但是大家都这么说) 110 9) 111 //建立普通变量的引用 112 int ma = 9; 113 int &ra = ma; //建立引用。 ===》 int *const ra = &ma; 即 ra = &ma; 114 ra = 88; //通过引用修改变量的值。这步是编译器帮忙进行解引用,然后赋值的。 115 116 //建立对指针的引用 117 int *p2 = NULL; 118 int *&mp2 = p2; //建立引用。===》 int ** const mp2 = &p2; 119 mp2 = (int *)malloc(sizeof(int)); //通过引用给指针p2分配空间。也就是给p2重新赋值,让他重新指向。 120 121 //建立对数组的引用 122 typedef int Arr[10]; /*建立数组类型 */ Arr a; //建立一个普通数组 123 Arr &p3 = a; //建立引用 ===》 Arr * const p3 = &a; 124 p3[3] = 10; //对数组的第四个元素赋值。 125 126 3.引用的几点基本知识: 127 1)单独定义引用时,必须初始化;说明它很像一个常量,因为常亮在定义时也必须初始化(const int a = 5)。 128 2) 普通引用有自己的空间。(在32位平台下占4个字节。)但是引用变量的地址和初始化它的变量是同一块地址。 129 int &a = b; a 和 b 的地址相同。() 130 struct teacher {int a; char b; int &d; double &c; }; 这个结构体所占内存为16; 131 struct teacher {int a; char b; }; 这个结构体所占内存为8; 132 3)引用的本质是一个常量指针。 133 4) 134 135 3.函数中的引用:引用做函数的参数,引用做函数的返回值 136 1)引用做参数不需要初始化 137 2)不能返回局部变量引用;(和返回局部指针变量原因一样)。 138 3)引用做返回值。(可以做左值和右值) 139 4)指针的引用。 140 5)(常量引用):const 对引用的使用(如上:)。const 引用的值不能修改(主要用在函数的形参:不想用形参改变实参的值) 141 6)"函数的返回值当左值需要返回引用。" 142 143 4.类: 144 1)使用class关键字 145 2)类里面可以放变量、函数。 146 3)public: 访问权限 147 4) 148 149 5.内联函数:C++中使用(既有宏函数的效率,又没有普通函数的开销;可以像普通函数那样,进行参数,返回值类型的安全检查,又可以 150 作为成员函数。 在C++中,定义内联函数,只是对编译器的一个建议,并不一定会成为内联函数。 151 (内联函数的语法) 152 1)普通函数; inline void func(int x){ return; }但是必须注意必须函数体和声明结合在一起,否则编译器将它作为普通函数来对待。 153 2)要求:不能存在任何形式的循环语句;不能存在过多的条件判断语句; 函数体不能过于庞大; 不能对函数进行取址操作 154 155 6.宏函数:C语言中使用 156 1) #define ADD(X, Y) X+Y 157 int main() {int ret = ADD(10,20); return 0;} 158 2)副作用很多:无脑替换,不检查语法;没有作用域,从定义开始到文件结束 159 160 161 7.函数的默认参数和占位参数: 162 1)默认参数:int func(int x = 10,int y = 20);此时形参的赋值就是默认参数,当函数调用不传参数时,就将使用默认参数。。 163 2)注意: 164 int func051(int x, int y = 0, int z = 0);//函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数。 165 函数的声明和函数的定义不能同时写默认参数,("即使默认参数相同也不行")编译器不知道该选择哪套 166 3)占位参数: 167 "函数的占位参数也是参数,必须要给个值,只是函数内部用不了而已. " int func(int , int y);或 int func(int ,int y = 3); 168 当占位参数与默认参数结合时:int func(int =5, int y);"erro" ;这种写法,int (int y, int =30);占位参数只能写在最后一个形参的位置。 169 而且可以不给它传参。因为它有了默认参数。 170 171 8.函数重载: 172 1)函数重载的条件: 173 "可以作为条件的:" 174 同一个作用域:函数名相同,形参的个数、形参的类型、形参的类型顺序不同; 175 用 const 进行修饰的函数也可以进行重载。 非 const 对象优先调用非 const 函数。 176 const 对象只能调用 const 函数,const 函数只能调用 const 函数,可以被 const 函数和非 const 函数调用。 177 注意:不可以作为条件的 178 "函数的返回值不能作为函数重载的条件" 179 "函数重载和默认参数不能同时出现";函数重载碰到默认参数,那么要考虑是否会出现函数调用二义性(会报错,编译通不过)。 180 2)重载函数的调用: 181 正常调用:"函数调用正常匹配函数形参(可以找到)。" 182 隐式类型转换后调用:"当找不到匹配的形参时,编译器会进行隐式转换,仍然找不到后会进行报错。(如下)" 183 void func(char b); int main(){ int a = 3; func(a);} 此时就会进行隐式转换,因为与ASCII码匹配。 184 185 3)函数重载的原理: 186 编译器为了实现函数重载,在编译的时候做了一些优化,用不同的类型来修饰不同的函数名。 187 如:void func(){} 188 void func(int a){} 189 void func(int a,char b){} 190 上述三个函数编译完后:生成的函数名为:_z4funcv "v代表void,无参数" 191 _z4funci "i代表参数为int类型" 192 _z4funcic "i代表第一个参数为int类型,第二个参数为char 类型" 193 194 195 "天命不足畏,祖宗不足法,人言不足恤" 196 第三天 C++基础班 ************************************============================================= 197 198 1.struct 的区别(C 和 C++): 199 C语言中只能定义变量。 200 C++语言中可以定义变量和函数。同时C++语言中,struct 中所有变量和函数都是 "public" 权限 201 2.类的封装: 202 3.类内部的三种权限 203 public:共有属性(修饰的成员变量和方法; 可以在类的内部和外部使用。) 204 private:私有属性(修饰的成员变量和方法,只能在类的内部使用,不能在类的外部使用) 205 protected:主要用于继承,保护属性(修饰的成员变量和方法; 可以在类的内部和继承的子类使用,不能在类的外部使用) 206 4.struct 和 class 的区别: 207 struct 中成员的默认权限为 public; 208 class 中成员的默认权限为 private; 209 5.类的调用(一个类调另一个类) 210 6.对象的构造和析构; 211 1)构造函数:"名称和类名相同";"没有返回值 "。"可以有多个,进行函数重载" 212 在内存开辟之后调用构造函数。 213 214 2)无参构造函数和有参构造函数(又分为两种) 215 1.无参构造函数:定义对象的时候,对象后不能加括号。如:class stu{ stu(){"无参构造函数"}}; stu st;(正确定义对象)。 216 如果加上括号: stu st(); 编译的时候不会报错,(因为编译器把它当作函数声明),运行的时候会报错。 217 2.有参构造函数: 218 "普通参数": 根据形参的类型和个数,也可以进行函数重载。 219 class Animal{ 220 public: 221 Animal(int age) 222 { 223 cout << "一个参数构造函数数字!" << endl; 224 mName = "undefined"; 225 mAge = age; 226 } 227 Animal(string name) 228 { 229 cout << "一个参数构造函数字母!" << endl; 230 mName = name; 231 mAge = 0; 232 } 233 Animal(string name,int age) 234 { 235 cout << "两个参数构造函数!" << endl; 236 mName = name; 237 mAge = age; 238 } 239 //拷贝(复制)构造函数 用一个对象来初始化另一个对象 240 Animal(const Animal& animal) 241 { //拷贝构造函数,形参必须是引用。否则会造成死循环,一直调用它本身。本质上成了递归调用本身。 242 cout << "拷贝构造函数!" << endl; 243 mName = animal.mName; 244 mAge = animal.mAge; 245 } 246 "拷贝参数":用一个对象来初始化另一个对象 247 它的形参必须是引用。如果是普通变量,就会造成本质上的递归调用本身;无意义。 248 249 3)析构函数:"名称=类名+"~""; "没有返回值";"没有参数"。"一个类只可以有一个析构函数" 250 在内存释放之前调用析构函数。 251 7.构造函数调用规则: (参考上面的代码) 252 1) 括号法; 253 Animal animal1("smith"); //一个参数构造函数字母! 254 Animal animal2(20); //一个参数构造函数数字! 255 Animal animal3("John",30); //两个参数构造函数! 256 Animal animal4(animal3); //拷贝构造 257 2)显示调用构造函数(匿名对象调用拷贝构造的问题) 258 Animal("Smith",30); //匿名函数生命周期仅限于当前行;此行运行完后立即析构。 259 Animal animal5 = Animal(30); // 一个参数构造函数数字!;形参为普通变量 260 Animal animal5 = Animal("smith",90); 注意:/"两个参数构造函数!;拷贝构造。这行比上一行多了一个拷贝构造,是因为string容器的缘故。只有这时才会有拷贝构造" 261 Animal animal5(animal(30)); //一个参数构造函数数字!。 262 Animal animal5(animal(30,"smith")); "两个参数构造函数!;拷贝构造。" 原因同上,当不调用 string 参数时,它不会调用拷贝构造。"是由编译器处理的,具体处理方式不清楚" 263 "上面那两种情况,调不调用拷贝构造函数,主要看参数类型。===有string:就调拷贝构造函数;==== 没有string: 就不调拷贝构造函数====" 264 匿名对象如果有变量来接,它就是一个对象;"此时这个变量就相当于匿名对象的名称。" 265 匿名对象如果没有变量来接,他就是一个实例化对象。 266 Animal4("john",92); Animal(animal4); 此时当这两句在一起,编译器会报错,重定义。 267 3)等号法 268 Animal animal5 = 10; //不常用。调用一个函数构造函数 269 Animal animal5 = animal4l; “拷贝构造,常用 270 8.拷贝构造常用的两种调用方式: 271 Animal animal5 = animal4l; 272 Animal animal5(animal4l); 273 274 9.拷贝构造的调用时机: 275 1)对象以值传递的方式传递给函数参数。 void func(Animal animal2){ }; 用实参初始化形参,调用了拷贝构造函数 276 2)用一个对象初始化另一个对象。Animal animal5 = animal4l; 或者:Animal animal5(animal4l); 277 3)函数返回局部对象。(注意这里:debug模式 和release模式不一样)。 278 debug模式下:会调用拷贝构造,打印两个地址也不相同。 279 release模式下,不会调用拷贝构造,打印的地址相同,编译器这里做了优化,直接把原来的对象返回回去了。 280 281 Animal MyBussiness() 282 { 283 //局部对象 284 Animal animal; //无参构造 285 cout << &animal << endl; 286 return animal; //编译器先拷贝一份,然后返回。相当于返回一个匿名对象 287 } 288 void test03() 289 { 290 Animal animal = MyBussiness(); 291 cout << &animal << endl; 292 } 293 294 10.构造函数调用规则: 295 默认情况下,编译器至少为我们写的类增加三个函数 296 1)默认构造函数(无参,函数体为空) 297 默认析构函数(无参,函数体为空) 298 默认拷贝构造函数,对类中非静态成员属性简单 "值"拷贝。 299 2) 如果用户定义了"拷贝构造函数",C++不会再提供"任何默认构造函数"。 300 3)如果用户定义了"普通构造,"C++"不会"再提供"默认无参构造",但是"会提供默认拷贝构造"。 301 4)在构造函数中调用构造函数是一个危险的行为。本质上:里面的构造函数相当于一个"匿名对象"。生命周期只有本行(没有变量接的情况下)。 302 303 11.初始化列表:初始化成员列表只能在构造函数使用。 304 "构造函数的初始化列表顺序:" 305 1)当一个类中组合了其他类的对象,先执行被组合的构造函数。 306 2)如果组合对象有多个,来自不同的类,根据定义的顺序进行构造函数的调用,而不是初始化列表的顺序。 307 3)“被组合对象的构造顺序:与定义顺序有关系,与初始化列表的顺序,没有关系” 308 4)若类成员中有 const 修饰,必须在对象初始化的时候,给 const 元素初始化。此时就用到了"初始化列表"。 309 310 "析构函数:与构造函数的调用顺序相反。" 311 312 12.深拷贝和浅拷贝 313 class Person 314 { 315 public: 316 Person(int a, int b) :_b(b),_a(b){} "初始化列表" 317 "深拷贝" 318 Person(const Person &p1) 319 { 320 mstr = (char *)malloc(strlen(p1.mstr) + 1); "核心步骤" “会进行内存拷贝” 321 strcpy(mstr, p1.mstr); 322 _a = p1._a; 323 _b = p1._b; 324 } 325 326 "浅拷贝" 327 Person(const Person &p1) 328 { 329 mstr = p1.mstr; "这里只会把一个对象的指针地址,赋给另一个对象的指针。";“不会进行内存拷贝” 330 _a = p1._a; 331 _b = p1._b; 332 } 333 void print_func() 334 { 335 cout << _a <<" " << _b << endl; 336 } 337 private: 338 int _a; 339 int _b; 340 char *mstr; 341 }; 342 343 13.C++中 string 是一个类。 344 345 14.已知一个Print类,在该类的main()函数里执行语句 "Print a, b[2], *c;" 后;程序会自动调用该类的构造函数 4 次。 346 a会调用一次;b会调用两次;*c是空指针,没有开辟空间不会调用。 347 348 15. 349 350 "天命不足畏,祖宗不足法,人言不足恤" 351 第四天C++ ++++++======================================================================================= 352 353 1.explicit:修饰构造函数; 禁止编译器隐式转换 354 1)针对单参数的构造函数 2)或者除第一个参数外其他参数都有默认值的多参构造函数。 355 356 2. C++ 中动态分配内存: 357 new :在堆上分配内存。分配对象的堆内存时会调用构造函数 358 申请string类型的空间只能string类型数据,可改为string *ps = new string(“10”); 359 delete:释放堆内存,在释放堆内存前会调用析构函数。 360 delete void*指针,不会调用析构函数。 361 362 3.创建动态数组: 363 int * arr = new int[10]; 创建一个 int 类型数组,元素10个。 364 delete [] arr; 释放内存。 365 4.创建自定义对象数组,必须提供无参构造函数。 366 同时数组有几个元素就调用几次"无参构造函数和析构函数"。 367 delete void* 指针可能会出错。没有调用析构函数 368 class print 369 { 370 private: 371 static int a; //在类中声明静态变量 372 public: 373 374 }; 375 int print::a = 0; //在类外初始化静态变量 376 377 5.静态成员变量: 378 1)在编译阶段就分配空间。对象还没有创建 379 2)必须在类中声明,在类外初始化(或定义)。 380 3)归同一个类的所有对象所有,共享同一个静态变量。在为对象分配的空间中不包含静态成员所占空间 381 4)有权限限制:private静态变量 和 public静态变量。 382 private静态变量;在类外不能访问和操作。 类外访问:print::a;"erro"; 类外操作:print::a = 6;"erro" 383 public静态变量;在类外可以访问和操作。 类外访问:print::a;"yes"; 类外操作:print::a = 6;"yes" 384 5)const 静态成员变量(const static int a = 8); 385 定义静态变量 const 静态成员变量时,最好在类的内部初始化。 386 387 "静态变量的调用:" 388 用类名加域作用符: print::a; 389 用对象名加取址符: class p1; p1.a; 390 391 6.静态成员函数:(主要是为了访问静态变量) 392 与静态变量一样,在没有创建对象前即可通过类名调用。 393 1).静态成员函数 只能访问 "静态成员变量",不能访问普通成员变量。 394 2).静态成员函数的使用规则和静态成员变量一样 395 3)静态成员函数也有使用权限。类外无法访问私有静态函数。 396 4)普通成员函数可访问"静态成员变量",也可访问"非静态成员变量" 397 398 399 400 7.单例模式:一个类只存在一个对象。 401 条件:1)构造函数、拷贝函数、析构函数私有化。 402 2)提供私有化静态成员变量指向一个对象 403 3)提供外部获得对象的静态成员函数。 404 class person 405 { 406 public: 407 void print_func(string content) 408 { 409 cout << "打印内容" << content << endl; 410 mcount++; 411 cout << "打印次数" << mcount << endl; 412 } 413 static person *getInstance(){ //静态成员函数 “在公共区域”,提供接口 414 return p1; 415 } 416 private: 417 person(){ //私有化构造函数 418 mcount = 0; 419 } 420 person(const person &){} //私有化拷贝函数 421 ~person(){} //私有化析构函数 422 private: 423 static person * p1; //私有化静态成员变量 424 int mcount; 425 }; 426 person *person::p = new person; (类外初始化静态变量)//私有化的静态成员变量指向一个对象 427 428 int main(void) 429 { 430 person *p1 = person::getInstance(); p1和p2的地址一样。 431 person *p2 = person::getInstance(); 因为他们为指向同一个对象。 432 433 p1->print_func("晚上好!!!"); 434 p2->print_func("加油,胜利就在眼前!!!"); 435 436 return 0; 437 } 438 439 8.C++ 成员变量和函数的存储:(成员变量和函数分开存储) 440 1)静态变量不在类对象中存储 441 2)函数不在类对象中存储(静态函数与非静态函数都是) 442 3)类对象中只存储 普通成员变量。 443 444 9.this 指针:(因为C++成员的存储特点。) 445 1)this指针永远指向当前对象。 446 2)"静态成员函数" 内部没有 this 指针,所以静态成员函数不能操作非静态成员变量。 447 "this 指针的使用:" 448 1)当形参与成员变量同名时,可以通过this指针区分。 449 2)在类的非静态成员函数中返回对象本身。可以使用:return *this; 450 class person 451 { 452 public: 453 person(int a, string name) 454 { 455 this->a = a; 456 this->name = name; 457 } 458 person func() 459 { 460 return *this; 461 } 462 463 public: 464 int a; 465 string name; 466 }; 467 468 10.空对象指针访问成员的不同情况:由于( this 指针的存在) 469 1)当不访问成员变量的时候是允许的。 470 2)当访问成员变量时,函数通过this指针寻找成员变量,此时就会报错。 471 472 class ARE 473 { 474 public: 475 void print(){ 476 cout << "hello world !!1" << endl; 477 } 478 void get_func(){ 479 cout << ma << endl; 480 } 481 482 public: 483 int ma; 484 }; 485 486 int main(void) 487 { 488 ARE *a1 = NULL; 489 a1->print(); "可以访问,因为此时不访问成员变量" 490 a1->get_func(); "不可以访问,它队成员变量进行了访问" 491 return 0; 492 } 493 494 10.const 对成员函数的修饰。(int func() const {});注意:const 要写在小括号后面,大括号前面。(===》int func(const ARE * const this )) 495 1)const 修饰 this 指针指向的内存区域。成员函数体内不可改变类中任何变量,除非变量前面用 mutable 进行修饰。 496 mutable 对成员变量的修饰 497 1)用它修饰后,任何情况下都可以对变量进行修改。 498 11.const 修饰对象(常对象)。(不允许修改对象的属性) 499 1)常对象可访问 const 或非 const 变量,不能修改变量。除非变量前用 mutable 修饰。 500 2)常对象只能调用 const 修饰的常函数。 501 502 503 12.友元函数。(全局函数和成员函数两种) 504 1).friend 关键字只出现在声明处。 505 2).其他类、类成员函数、全局函数都可声明为友元。 506 3)友元函数不是类的成员,不带this指针。 507 4)友元函数可访问对象的任意成员属性,包括私有属性。 508 509 注意:注意: 510 1)友元关系不能被继承 511 2)友元关系是单向的,类A是类B的友元,但类B不一定是类A的友元。 512 3)友元关系不具有传递性。类B是类A的友元,类C是类B的友元,但类C不一定是类A的友元。 513 514 #include <iostream> 515 #include <string> 516 using namespace std; 517 class COp2; "注意:这里必须对第二个类进行声明" 518 class Myp1 519 { 520 public: 521 void printf(COp2 &); "这个类中的函数对另一个的成员变量进行操作" 522 }; 523 524 class COp2 525 { 526 friend void print_func(COp2 &c1); "全局函数 友元函数的声明" 527 friend void Myp1::printf(COp2 &); "类成员函数 友元函数的声明。注意这里只能声明" 528 friend class Myp1; "友元类的声明,此类中的所有函数都可以对声明的类中的变量进行访问" 529 public : 530 COp2(int a, string name) 531 { 532 this->a = a; 533 this->name = name; 534 } 535 private: 536 int a; 537 string name; 538 }; 539 540 void Myp1::printf(COp2 & c1) "成员函数的 友元函数的实现"" 541 { 542 c1.a += 1; 543 cout << c1.a << endl; 544 cout << c1.name << endl; 545 } 546 547 void print_func(COp2 &c1) "全局函数的 友元函数的实现" 548 { 549 cout << c1.a << endl; 550 cout << c1.name << endl; 551 } 552 void test() 553 { 554 COp2 c1(30,"bei jing"); 555 Myp1 m1; 556 m1.printf(c1); 557 } 558 559 int main(void) 560 { 561 COp2 c1(30, "bei jing"); 562 Myp1 m1; 563 m1.printf(c1); 564 system("pause"); 565 return 0; 566 } 567 568 569 第五天C++==================================++++++++++++++++++++++++++++++++++++++++= 570 571 1.友元函数案例: 572 1)电视机的状态 573 574 2)遥控调试 575 576 577 2.数组案例: 578 1)分文件( int 类型的数组) 579 2) 580 581 3.操作符重载: 582 1)本质:"函数的调用"。 583 函数的参数取决于两个因素: 584 1)运算符是一元还是二元。 585 2)运算符被定义为全局函数还是成员函数。 586 587 2)运算符重载的限制: 588 1)使用运算符重载不能改变预算符的优先级,不能改变运算符的参数个数。 589 1)不能重载的运算符:"."; "::"; ".*"; "? :"; "sizeof". 590 2)除"="号外,基类中重载的运算符都将被派生类继承。 591 3)特殊运算符: 592 1)“=”; “[]”; “()”; “->”;操作符只能通过成员函数重载 593 2)“<<”; “>>”; 只能进行友元函数重载 594 3)不要重载 “&&”; “||” ; 操作符,因为无法实现短路原则。 595 4.运算符重载建议: 596 1)所有的一元运算符, 建议使用:成员函数 597 2)“=”;“[]”;“”;“->”; 必须使用成员函数 598 3)+=,-=,*=,&=,!=,%=,^=, 建议使用:成员函数 599 4)其他二元运算符 建议使用:非成员函数 600 5.重载"="号操作符的步骤 601 1)先释放旧内存 602 2)返回一个引用,实现链式编程。 603 604 考试天================================================================================== 605 1.断言函数; assert(终止进程,抛出异常) 头文件:<assert.h> 606 2. strcpy 返回值为目标内存的地址;是为了实现链式编程。 607 int n = strlen(strcpy(des,src)); 链式编程。 608 3. 609 610 611 C++第六天========================================++++++++++++++++++++++++++++++++++++++++++++++++++= 612 "运算符重载" 613 1.前置++;前置--; 614 2.后置++;后置--; 615 要创建一个临时变量接收计算时的那个变量,记住要把临时变量返回去。 616 3.== 和 != 号的重载。 617 618 4.继承: 619 1)C++中的继承方式(public, private, protected)会影响子类的对外访问属性。判断某一句话,能否被访问 620 (三看原则 ) 621 1)看调用语句,这句话写在子类的内部、还是外部 622 2)看子类如何从父类继承(public, private, protected) 623 3)看父类中的访问级别(public, private, protected); 624 625 2)继承的使用情况: 626 1) protected 继承,只能在家族内部使用,不能在类的外部使用 627 2)项目开发中,通常都是 public 继承。 628 629 5.继承的类型兼容原则: 630 1)子类对象可以当作父类对象使用 631 2)子类对象可以直接赋值给父类对象 632 3)子类对象可以直接初始化父类对象 633 4)父类指针可以直接指向子类对象 634 5)父类引用可以直接引用子类对象。 635 636 6.继承与组合混搭情况下,构造和析构函数调用原则 637 1) 先调用父类构造函数,再调用组合类构造函数,最后调用自己的构造函数 638 2)先调用自己的析构函数,再调用组合类析构函数,最后调用父类析构函数, 639 3)先构造的对象后释放 640 641 7.不可以被继承的函数 642 1)基类中的构造函数和析构函数 643 2)基类中重载的运算符 = 号函数 644 645 8.继承中同名成员函数和变量的问题: 646 1)如果子类和父类中有相同的函数和变量名;子类调用时默认调用自身的函数和变量。 647 2)如果想调用父类的函数和变量,需要加上 "类名和域作用域符",显式调用。 648 649 9.继承中的静态成员特性: 650 1)基类中定义的静态成员,可被所有派生类共享 651 2)"静态成员的访问权限和普通成员一样";遵守派生类的访问控制 652 3)与函数重载一样,如果子类中重新定义了同名的静态函数,基类的函数将被隐藏 653 4)静态成员函数不能是虚函数。 654 655 9.继承中的函数重载现象; 656 1)"如果重新定义了基类中的重载函数,"只要函数名相同,继承的基类中的重载函数将被隐藏。 657 想要调用必须使用域作用符显式调用。 658 659 9.多继承的概念: 660 1)多继承可能会出现二义性; 解决办法:显示的调用不同类中的同名属性或方法 661 2)多继承的相同数据重复继承。解决办法:虚继承:virtual 既保持一份数据,也不会产生二义性。 662 663 10.菱形继承解决办法: 664 加上 virtual ,用虚继承。 665 11.多态 :分为:静态多态(编译时多态)和动态多态(运行时多态)。 666 面向对象编程的设计思路:"开闭原则(对修改关闭,对扩展开放。)" 667 C++的多态性是通过虚函数来实现的。(虚函数允许子类重新定义父类成员,这种做法叫做重写或覆盖)。 668 12.向上类型转换 669 670 13.虚函数:函数前加 virtual 修饰 671 1)virtual 关键字只能修饰成员函数 672 2)构造函数不能为虚函数 673 3)创建一个虚成员函数,只需在函数声明前加上 virtual 关键字。 674 4)如果一个函数在基类中被声明为 virtual ,那么它在所有派生类中都是 virtual ;它派生类的函数前可以加也可以不加 virtual 675 关键字;推荐加。 676 677 纯虚函数:virtual int func() = 0; 678 1)当一个类中有纯虚函数时,这个类为抽象类;"同时注意:不能实例化一个抽象类,编译时会报错"。 679 2)当继承一个抽象类时,必须实现所有的纯虚函数,否则抽象类派生的类也是一个抽象类。 680 681 14.建立公共接口的目的: 682 1)将子类中公共的操作抽象出来。可以通过一个公共接口操作呢一组类。这个公共接口不需要事先实现,即创建一个公共类。 683 684 15.模板方法:就是通过创建一个公共借口来实现的。 685 686 687 688 "===========================================================================================" 689 一.多态的思想: 690 1.封装:突破C函数的概念,。。。用类做函数参数的时候,可以调用对象的属性和对象的方法 691 2.继承:A B代码可以复用,可以使用前人的代码 692 3.多态。可以使用未来的代码,架构不用改变 693 二. 多态成立的条件: 694 1.要有继承 695 2.要有虚函数重写 696 3.用父类指针或父类引用 指向子类对象 697 698 "===========================================================================================" 699 700 C++第七天========================================++++++++++++++++++++++++++++++++++++++++++++++++++= 701 702 1.虚析构函数的特点和目的: 703 1)通过父类指针,把所有子类对象的析构函数都执行一边。 704 2)通过父类指针,释放所有的子类资源 705 706 2.函数的重写、重载、重定义 707 1)重载: 708 必须在同一个类中 709 重载是在编译期间根据 参数类型和个数决定函数调用的 710 子类无法重载父类的函数,父类同名函数将被覆盖 711 712 2)重写: 713 必须发生在父类与子类之间。(同时分为两类) 714 并且父类与子类的函数有完全相同的原型。 715 1)virtual 具有这个关键字的,发生重写时会产生多态。 716 2)无这个关键字的,叫重定义。 717 3)重定义:(非虚函数重写叫重定义) 718 父类与子类的有完全相同的函数原型,而且无 virtual 关键字修饰,此时叫做函数重定义。 719 720 3.子类中的 vptr 指针的分步初始化 721 1)当创建子类对象时,如果它具有 vptr 指针, 那么此时是分步初始化的。 722 2)当执行父类的构造函数时,子类的 vptr 指针会指向父类的虚函数表 723 当父类的构造函数执行完毕后,会把子类的 vptr 指针指向子类的虚函数表 724 3)结论:子类的vptr指针是分步初始化的。它在程序运行时会进行寻址操作,效率会降低。不建议将每个成员函数都声明为虚函数。 725 726 4.纯虚析构函数: 727 728 5.多态的案例: 729 1)思路: 730 1.定义一套自己的数据接口,打包放在结构体中,数据接口使用函数指针。 731 2.初始化模块接口。(将自己定义的接口与生产商实现的函数联系在一起。) 732 3. 733 734 735 6.函数指针 736 737 738 739 C++第八天================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 740 741 1.C++的模板(用于实现泛型编程) 742 1)模板关键字: template<class T,class T1>或 template<typename T, typename T1> T,T1代表数据类型; 743 2)定义的模板关键字当前行下一个函数或类有用。 744 3)分为 函数模板 和 类模板; 745 4)一旦声明了多个类型 T ,不管用或不用,都必须给它指定类型。 746 747 2.函数模板的编译机制:(原理) 748 1)函数模板具有自动推导功能 749 2)编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。 750 3)函数模板通过具体的类型产生不同的函数。 751 752 3.模板函数与普通函数的区别 753 1)函数模板不允许自动类型转换,必须严格匹配。 754 2)普通函数能自动进行类型转换 755 template<class T> 756 T ptint(T a, T b ){ } 757 758 int show(int a,int b){ } 759 void main(){ int a = 6; char b = 'c'; print(a,b);"error",两个变量类型必须相同,不允许进行自动转换 760 show(a, b);"YES" “普通函数能自动进行类型转换”}; 761 762 4.函数模板和普通函数一起调用规则 763 1)函数模板可以向普通函数一样被重载 764 2)如果函数模板与普通函数一样,C++编译器优先调用普通函数。 765 例如: template<class T> 766 void print(T a, T b ){ } 767 void print(int a, int b){ } 768 void main(){ int a = 4; int b = 9; print(a, b); "此时优先调用普通函数"} 769 3)如果函数模板能产生更好的匹配,则优先调用函数模板。 770 例如:template<class T,class T2> 771 void print(T a, T2 b ){ } 772 void print(int a, int b){ } 773 void main(){ int a = 4; char b = 'u'; print(a, b); "此时优先调用函数模板"} 774 4)可以通过空模板实参列表的语法限定编译器只能调用模板函数 775 例如:(此目录第二个函数例子) 776 void main(){ int a = 4; int b = 6; print<>(a, b); } 777 4.类模板: 778 1)只能显示的指定类型,不具有自动推导功能。 779 2)类模板做函数形参(必须显示指定模板类型) 780 3) 781 782 5.类模板与函数模板(有自动推导功能)的混合使用案例,有奇效。 783 template <class T1,class T2> 784 class Person 785 { 786 public: 787 Person(T1 name, T2 age) 788 { 789 this->mName = name; 790 this->mAge = age; 791 } 792 public: 793 T1 mName; 794 T2 mAge; 795 }; 796 //类模板做函数参数(指定类型) 797 void Dobusiness(Person<string, int> &p1) 798 { 799 cout << "name:" << p1.mName << " age:" << p1.mAge << endl; 800 } 801 //类模板与函数模板混合使用(使用函数模板的自动推导功能) 802 template <class T1, class T2> 803 void Dobusiness02(Person<T1, T2> &p1) 804 { 805 cout << "name:" << p1.mName << " age:" << p1.mAge << endl; 806 807 } 808 void test04() 809 { 810 Person<string, int > p1("jonh", 30); 811 Dobusiness(p1); 812 813 Person<string, int > p2("jonheqeq", 303); 814 Dobusiness02(p2); 815 } 816 817 6.类模板派生普通类和类模板类: 818 1)继承的话,应该继承一个具体的类,因为一个具体的类,编译器才知道分配多大的内存。 819 2)如果想继承类模板,应该给继承过程中的类模板显示指定类型。 820 例如(类模板派生普通类) 821 template <class T> 822 class MyClass{ private: "父类模板" 823 T age; }; 824 class SubClass : public Mycalss<int>{ "普通类,这里继承时,必须对类模板显示指定类型,编译器才知道给父类分配多大的内存" 825 public: 826 int a; 827 }; 828 例如(类模板派生子类模板) 829 template <class T> 830 class MyClass{ private: "父类模板" 831 T age; }; 832 template <class T> 833 class SubClass : public Mycalss<T>{ "子类类模板,这里继承时,可以用子类的类型指定父类的模板" 834 public: 835 T a; 836 }; 837 838 839 7.类模板分文件编写 840 C++文件的编译过程:先每个文件独立编译,然后由链接器将编译好的文件链接在一起(这一步就是寻找各种调用函数,和头文件的定义内容) 841 需要引入 .cpp 文件,原因: 842 1)C++编译机制的原因;(分文件编译导致的) 843 2)二次编译有关 844 解决办法文件,将他们放入: 845 类模板的声明与定义要放在一个文件中编写。创建一个.hpp将他们放入 846 847 848 "C++的类型转换" 849 850 8.静态类型转换:static_cast<>; 851 1)用于类层次结构中基类和派生类之间"指针或引用"的转换 852 1)上行转换:把派生类的指针或引用转换成基类的是安全的 Animal *ani = static_cast<Animal*>( dog ) ; 853 2)下行转换:把基类的指针或引用转换成派生类的是不安全的,因为没有动态类型检查。 Dog *dog = static_cast<Dog *>(ani); 854 3)总结一句就是:可以把大的转成小的; 不能把小的转成大的。 855 856 2)当两个类之间无继承关系时,不可以进行转换。 857 如:Teacher *t1 = static_cast<Teacher *>(dog); "error,因为两个类之间无关系" 858 859 3)基础数据类型之间的转换: 860 1)可以把高精度的类型转换成低精度的;double da; int a = static_cast<int>(da); 861 2)当把低 862 精度的类型转换成高精度时,安全性需要程序员保证。(原理和上一样) double da = static_cast<double>(a); 863 864 9.动态类型转换:dynamic_cast<>(类与类之间的转换) 865 dynamic_cast<> 具有类型检查功能,比 static_cast<>类型转换安全。 866 1)只能转换具有父子关系的"指针或引用". 867 上行转换:只能将子类指针转换成父类指针(可以大转小) 868 下行转换:不能将父类指针转换成子类指针(不能小转大); 869 例如: parent *parent = dynamic_cast<parent*>son; 870 871 10.数组模板: 872 "1)自定义类型做元素构建数组时,必须提供无参构造函数。" 873 874 875 C++第九天================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 876 877 1.常量转换:(const_cast<>)用来修改类型的 const 属性。 878 1)常量指针、引用 被转换成 非常量指针、引用,并且仍然指向原来的对象()互相转换,将非常量转换为常量。 879 例如u: 880 2)不能直接对非指针、非引用的变量直接使用 const_cast<> 操作符去移除它的 const 属性。 881 882 2.重新解释转换或强势类型转换。reinterpret_cast<>(不安全的转换,各种类型之间的强转) 883 884 3.异常的处理(需要了解 C 的异常处理 和C++的处理) 885 C的异常处理的缺陷。和特点 886 887 4.C++的异常特点 888 1)异常不能被忽略,而且异常可以跨函数,异常并不是简单的 int 型数字,最好有明确的意义。 889 2)C++提供的异常机制,具有跨函数和不可忽略的特点。 890 3)异常捕获的严格类型匹配。 891 892 893 5.栈解旋:异常被抛出后,从进入 try 起,到异常处理完毕,这期间在栈上创建的所有对象都将会自动析构;析构的顺序与创建的顺序相反。 894 895 6.异常接口声明: 896 1)可以在函数前声明抛出的异常类型,如果不声明,表示可以抛出各种类型的异常。 897 例如: void func() throw(int , double, char){ }可以抛出三种类型的异常。 898 void func() throw(){ }不可以抛出任何类型的异常。 899 void func() { } 可以抛出任何类型的异常。 900 2)"但是:" 901 C++的异常规范在不同编译器下可能执行效果会不同。(编译器的不同会导致结果不同) 902 VS下会忽略C++的异常规范,但是会发出警告,程序照常执行; QT会报错,终止程序运行。 903 通常情况下:如果一个函数抛出了它的异常接口声明所不允许抛出的类型,unexcepted 函数将被调用,该函数默认调用 terminate 函数中断程序。 904 905 7.异常变量的声明周期 906 1)异常也可以抛出一个类的对象。此时可以使用纯虚函数做接口,声明每种不同的异常对象(减少代码量)。 907 例如: class BaseException{ 908 public: 909 virtual void print_exception() = 0; 910 }; 911 class TargetException : public BaseException{ 912 public: 913 virtual void print_exception(){ 914 cout<<"目标 空间空指针异常" <<endl; 915 } 916 }; 917 class DestException : public BaseException{ 918 public: 919 virtual void print_Exception(){ 920 cout<<"源空间的空指针异常" << endl; 921 } 922 }; 923 void CopyString(char *dest, const char *source){ 924 if(dest == NULL){ 925 throw TargetException(); 926 } 927 if(source == NULL){ 928 throw DestException(); 929 } 930 memcpy(TargetException,DestException,strlen(source)+1 ); 931 } 932 int main(){ 933 const char *source = "nnakew"; 934 char dest[1024] = {0}; 935 try{ 936 CopyString(dest, source); 937 }catch(BaseException& ex ) 938 { 939 ex.print_exception(); 940 } 941 return 0; 942 } 943 944 945 946 8.C++标准异常类:系统头文件 <exception> 947 948 9.创建自己的异常类: 949 引入系统头文件,继承系统的标准 出错类;重载父类的 what 函数和虚析构函数。 950 951 10.标准输入输出操作 952 cin 是由缓冲区获取文件到 目标内存或变量,若缓冲区没有内容时,才需要从键盘上输入 953 cout 是有缓冲区输出文件到屏幕的,若缓冲区没有满时,或强制释放,它是不会在屏幕上输出内容的。 954 endl 的作用是刷新缓冲区,将内容输出到屏幕或文件里。 955 956 10.格式化输入输出操作 957 958 959 960 C++第十天(STL课程阶段)================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 961 962 1.STL六大组件 963 964 965 2.STL三大组件:容器、算法、迭代器 966 967 每一个容器都有自己的迭代器,迭代器用来遍历容器中的元素。 968 遍历:不重复的进行访问每个元素。 969 970 3.容器,迭代器,算法的初步使用。 971 1)先引入所需要的容器头文件和算法头文件 972 973 void Myprint( int val){ 算法中的回调函数,需要自己写, 974 cout<< val <<" "; 内容为自己所想打印的内容 975 } 976 void test(){ 977 vector<int > V; 978 for (int i = 0; i < 5; i++) 979 { 980 V.push_back(i+ 10); 981 } 982 vector<int>::iterator pBegin = V.begin(); 983 vector<int>::iterator pEnd = V.end(); 984 985 //直接使用迭代器进行容器的遍历 986 while (pBegin != pEnd) 987 { 988 cout << *pBegin << " "; 989 pBegin++; 990 } 991 cout << endl; 992 993 //模拟算法的最后一个回调函数的使用 994 pBegin = V.begin(); 995 while (pBegin != pEnd) 996 { 997 Myprint(*pBegin); 998 pBegin++; 999 } 1000 cout << endl; 1001 1002 //使用算法遍历容器,并且打印出来。注意:算法的最后一个参数为回调函数,需要自己写(所想要打印的内从容) 1003 for_each(V.begin(), V.end(), Myprint); 1004 cout << endl; 1005 } 1006 }; 1007 1008 2)容器的分类:序列式容器和关联式容器 1009 序列式容器:容器元素在容器中的位置由元素进入容器的时间和地点来决定。如:(vector,deque , list, stack, queue ) 1010 关联式容器:容器具有自己的规则,元素在容器中的位置由容器的规则来定。如(树状容器: set/mutilset; map/mutilmap ) 1011 3)每个容器都有自己的迭代器,由容器自己提供。 1012 1013 3.string 容器: 1014 1)从 const char * 到 string 有隐式转换;反之则没有。 1015 1016 3.下标操作符: [] 和 at()方法。 1017 两个的区别在于:1)[] 越界程序会直接挂掉, 2)at()方法越界程序会提供错误信息。 1018 1019 4.vector 容器: 1020 1)连续的内存空间 1021 2)单口 1022 3)会实现内存空间动态增长(当已有内存占满时);申请更大的空间,原来的空间析构,原来的迭代器会失效。 1023 4)它的内存空间不会随着 clear()方法清除数据而消失的,只有当容器生命周期结束时,它的容量才会释放; 1024 "所以当想在生命周期存在时减少或增大容器内存,可以使用 swap() 方法。"(如下) 1025 void test04(){ 1026 1027 vector<int> v; 1028 for (int i = 0; i < 100000; i++){ 1029 v.push_back(i); 1030 } 1031 1032 cout << "capacity:" << v.capacity() << endl; 1033 cout << "size:" << v.size() << endl; 1034 1035 v.resize(3); 1036 cout << "capacity:" << v.capacity() << endl; "注意容量与长度不一样," 1037 cout << "size:" << v.size() << endl; 1038 1039 //收缩内存 1040 //vector<int>(v).swap(v); 1041 1042 vector<int>(v).swap(v); //匿名对象 1043 1044 cout << "capacity:" << v.capacity() << endl; 1045 cout << "size:" << v.size() << endl; 1046 } 1047 5)reserve的使用:"预留空间",在此空间还没有初始化时是不允许访问。可以通过push_back()插入元素后,进行访问。 1048 6)resize的使用:开辟空间并且初始化,它的空间申请完后是可以访问的。 1049 重新指定容器的长度为num,若容器变长,则默认值或指定值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除,"只是长度变短,但是容器的容量没有变化,"。 1050 7)只要容器的容量产生变化,原来的迭代器就会失效。(如删除元素之后,原来的迭代器就不能用了)。 1051 1052 5.迭代器的注意事项: 1053 1)"(当内存由于动态增长,更换地址后,原来的迭代器就失效了,不可以再使用)。" 1054 2)只要能遍历容器中所有元素的,都是容器认可的迭代器。 1055 3)每个容器的迭代器不一样,有自己的独立迭代器。 1056 4) const_iterator 只读 1057 reverse_iterator 逆序迭代器 1058 iterator 最普通的正向迭代器 1059 5) 1060 6.迭代器的种类:5种。 1061 1)每个容器只提供一种 1062 2)都是由对应的容器自己提供的迭代器。(因为每个容器的实现机制不一样)。 1063 3)输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器 1064 1065 1066 6.vector 申请和释放空间的注意: 1067 尽量不要频繁申请和释放空间(太浪费时间);可以先预测一下所用空间的大小,直接用reserve()申请足够大的空间,在逐步初始化。 1068 1069 1070 7.随机迭代器:(vector容器支持随机访问) 1071 1072 8.deque 容器: 1073 1)是一种双向开口,动态以分段连续内存空间组合而成,随时可以增加一块新内存的容量器。 1074 2)可以在首尾两端分别作元素的删除和插入操作。 1075 3) 1076 1077 1078 C++第十一天(STL课程阶段)================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1079 1080 1.栈容器(stack) :元素先进后出,单口容器 1081 1)不提供迭代器;没有遍历功能 1082 2)以其他的容器作为底层,它相当于提供接口 1083 3)只有栈顶元素才可以被外界取用。所以想遍历容器,只能从栈顶开始,取出一个元素,就删除它,当遍历完后,容器也就没有元素了。 1084 1085 2.队列容器(queue):元素先进先出 1086 1)没有迭代器。不支持随机访问 1087 2)两个出口,一个只进,一个只出。 1088 3)和 stack 栈容器一样,遍历完后,容器的元素也被删除空。 1089 1090 3.( stack 和 queue 容器叫受限的线性表) 1091 1092 3.链表容器(list):双向循环链表 1093 1)采用动态存储分配,不会造成内存浪费和溢出。 1094 2)元素的插入和删除十分方便,修改指针即可。 1095 3)list容器提供自己的排序算法; 1096 4)list容器的排序(重点在回调函数的使用) 1097 5)swap()既可以交换两个链表的数据,也可以动态的伸缩内存。 1098 1099 1100 1101 4.set容器(红黑树,平衡二叉树的一种) 1102 1)它的元素既是键值又是实值,而且所有的元素都会自动排序。 1103 2)不允许两个元素有相同的键值。 1104 3)不允许通过迭代器修改元素的值。 1105 4)与list有某些相同的性质,当对容器中的元素进行插入和删除操作时,操作之前的所有迭代器,在完成操作完成后都是有效的,除了被删除的那个元素的迭代器 1106 1107 5.multiset容器: 1108 1)特性和set一样,唯一区别在于它允许键值重复。 1109 1110 6.算法的默认排序原则:都是由小到大;如果想从大到小,就需要自己建立一个回调函数 1111 1112 7.对组( pair ): 将一对值组合成一个值,这一对值可以具有不同的数据类型。两个值可以分别用对组的公有属性 first 和 second 访问。 1113 创建对组的两种方式:1)pair<string ,int> pair1("xiaobai",20); cout<< pair1.first <<endl; cout<< pair1.second <<endl; 1114 2)pair<string, int> pair2 = make_pair("xiaohong",23); cout<< pair2.first <<endl; cout<< pair2.second <<endl; 1115 3)pair<string, int> pair3 = pair2; cout<< pair3.first <<endl; cout<< pair3.second <<endl; 1116 1117 8.map 容器:(它的所有元素都是一个对组) 1118 1)键值对;键值不可以相同,实值可以相同。所有的元素都会根据键值自动排序。 1119 2)插入元素的四种方式:map<int , int> m; 1120 m.insert(pair<int, int>(1, 2)); 1121 m.insert(make_pair(2, 2)); 1122 m.insert(map<int, int>::value_type(3, 2)); 1123 m[4] = 4; 1124 1125 3)如果通过[] 访问一个不存在的key值,那么编译器会创建一个,实值为默认值。 1126 4)map的迭代器与普通容器不同。是一个对组迭代器。可以通过迭代器修改实值的值,不可以修改键值的值。 1127 5)map的迭代器与list迭代器有某些相同的性质,对容器元素进行插入和删除时,操作之前的所有迭代器在操作完成之后不会失效,当然那个被删除的元素迭代器除外。 1128 6)指定map 的排序规则:(因为它的元素为对组,所以排序规则需要自己写函数确定) 1129 1130 9.multimap容器: 1131 与map 容器操作类似,唯一不同在于它的键值可以相同。 1132 1133 10.如何判断容器支持随机访问(或提供随机迭代器): 1134 只需要看容器提供的迭代器能否 +2,+3 1135 vector<int>::iterator it = v.begin(); it = it+3;(可以,提供随机迭代器) 1136 queue<int>::iterator it = q.begin(); it = it+2;(不可以向后跳跃,不提供随机迭代器)。 1137 1138 11.STL中,所有的拷贝都是值寓意,所提供的内容必须是可以拷贝的。 1139 1)此时就涉及到深拷贝和浅拷贝的问题;当由指针存在,而且它指向堆内存时,用容器提供的拷贝只会复制指针的指向,并没有拷贝到指针所指向的数据。 1140 当生命周期结束,进行空间析构时,就会出现同一块内存二次析构,程序挂掉。 1141 2)必须自己重载一个深拷贝函数(和类的函数重载一样;(一般都是:一个拷贝函数和一个重载=号操作函数)。 1142 1143 12.STL使用 的时机 1144 1145 C++第十二天(STL课程阶段)================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1146 1147 1.容器中的 find()函数默认区分大小写,而且返回值为迭代器. 1148 1149 2.仿函数:重载函数调用操作符的类,其对象成为函数对象,也叫仿函数。 1150 1)仿函数是一个类,不是一个函数。重载了() 1151 2)仿函数重载了()操作符,使得函数对象可以像函数一样使用。 1152 3)重载的operator()需要一个参数的,这样的类对象称为 "一元仿函数"; 依次类推。 1153 1154 3.预定义函数对象:(实现了数据类型与算法的分离。) 1155 1)plus<int> p; 预定义好的函数对象,能实现不同类型数据的 + 运算。 1156 2) 1157 1158 3.函数没有类型,不能定义变量。函数对象有类型,因为他是一个类。 1159 1160 4.函数对象与普通函数的区别:(02) 1161 1)函数对象超出了普通函数的概念,可以保存函数的调用状态 1162 2)函数对象可以做参数和返回值 1163 1164 5.谓词: 1165 1)普通函数或重载的operator()"返回值为bool 类型"的函数对象(仿函数)。 1166 2)一个参数的叫一元谓词,以此类推。 1167 6.兰博打函数表达式: vector<int> v1; 1168 for_each(v1.begin(), v1.end(), [](int val){ cout<< val<<" "; }); 1169 for_each(v1.begin(), v1.end(), [](int val)->void{ cout<< val<<" "; }); 1170 1171 7.内建函数对象 1172 /* 1173 template<class T> T plus<T>//加法仿函数 1174 template<class T> T minus<T>//减法仿函数 1175 template<class T> T multiplies<T>//乘法仿函数 1176 template<class T> T divides<T>//除法仿函数 1177 template<class T> T modulus<T>//取模仿函数 1178 template<class T> T negate<T>//取反仿函数 1179 */ 1180 7.函数对象适配器: 1181 1). 让自己编写的函数对象继承基类,如果是一元函数对象需要继承unary_function, 1182 如果是二元函数对象继承binary_function 1183 2). 函数对象operator()函数后增加 const 1184 3). 使用bind2nd bind1st(将一个二元函数对象转变为一元函数对象), 1185 区别为: bind1st: 将参数绑定为函数对象的第一个参数 bind2nd: 将参数绑定为函数的第二个参数。 1186 1187 4)for_each():遍历打印容器内容。返回值为最后一个函数对象的类型。 1188 1189 6)取反适配器 1190 find_if():查找容器中有无所寻找的内容,返回值为查找类型的迭代器。 1191 对一元函数对象、谓词取反用 not1; 对二元函数对象、谓词取反用 not2; 1192 1193 7)给普通函数绑定参数(需要:先把一个普通的函数指针适配成函数对象) 1194 ptr_fun函数指针适配器:把一个普通函数指针适配为函数对象。 1195 void Myprint(int val1, int val2){ cout<< val1 + val2 <<" "; } 1196 void main() { for_each(v1.begin(), v1.end(), bind2nd(ptr_fun(Myprint), 200)); } 1197 8)成员函数适配器(mem_fun_ref:容器中存放的为对象实体。)(mem_fun:容器中存放的为对象指针。) 1198 vector<person> v1; person p1("aaa", 10); v1.push_back(p1); 1199 for_each(v1.begin(), v1.end(), mem_fun_ref(&person::showperosn)); 1200 vector<person*> v1; v1.push_back(&p1); 1201 for_each(v1.begin(), v1.end(), mem_fun(&person::showperosn)); 1202 1203 1204 8.算法: 1205 遍历算法: 1206 1)for_each算法:第三个参数,接受的函数类型。 1207 2)transform; 1208 1209 查找算法 1210 1)find(查找元素或对象) 与 find_if(查找指针所指向的内容) 1211 2)adjacent_find查找相邻重复元素 1212 3)binary_search(二分查找法),需要查找的容器元素有序排列 1213 4)count 与 count_if (统计容器中元素的个数) 1214 1215 排序算法 1216 1)合并两个有序序列(merge)两个序列必须是有序的。且顺序应该一样(或从小大,或从大到小,两种方式参数有点区别,默认从小到大,从大到小需要额外添加函数对象) 1217 2) sort算法(条件:容器必须支持随机访问) 1218 3)random_shuffle(打乱容器中的顺序) 1219 4)reverse (反转) 1220 5) 1221 拷贝和替换算法 1222 1)copy (拷贝) 1223 2)replace(替换)把所有要替换的值都替换成目标值。 1224 3)replace_if 1225 4) swap 1226 1227 算数生成算法 1228 1)accumulate (累加容器中元素) 1229 2)fill(填充) 1230 3) 1231 集合算法 1232 1)set_intersection (求交集) 注意它的返回值。 1233 2)set_union (求并集) 1234 3)set_difference (差集); 1235 1236 1237 C++第十三天(数据结构、算法课程阶段)================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1238 1.线性表:分类: 1239 1240 1)线性表顺序存储: 1241 1242 2)线性表线性存储: 1243 1)单向链表 1244 2)单向链表(企业链表) 1245 3)循环链表(企业链表单向) 1246 4)解决约瑟夫问题(用单项循环链表) 1247 5)双向链表 1248 6)受限的线性表 1249 1)栈的顺序存储( stack ) 1250 2)栈的链式存储( stack ) 1251 3)队列的顺序存储( queue ) 1252 4)队列的链式存储( queue ) 1253 5)栈的应用(1.就近匹配,用来测试编码的括号使用规范) 1254 6)栈的应用(2.中缀表达式转后缀表达式) 1255 7)(计算机)基于后缀表达式的计算; 1256 1257 1258 3.树的特点: 1259 1)非线性结构,有 1 个直接前驱,但可能有多个直接后继(1 :n); 1260 2)树的定义具有递归性,树中还有树。 1261 3)树可以为空,即节点为0。 1262 1263 4.树的一些专业名称 1264 1)节点的度:节点挂接的子树数(即一个节点有几个直接后继就有几度)。 1265 2)节点的层次:即根到该节点的层数(根节点算一层) 1266 3)树的度:所有 节点的度 中的最大值 1267 4)树的深度(或高度):指所有节点中的最大层数。 1268 5)节点数:一棵树中的所有节点个数(包括根节点)。 1269 6)叶子节点:即终端节点,没有后继。 1270 1271 5 二叉树 1272 1)二叉树的基本特征:(1 :2) 1273 每个节点最多只有两个子树 1274 左子树和右子树的位置不能颠倒 1275 2)二叉树的性质: 1276 1)在二叉树的第 i 层上,最多只有2^(i-1)个节点。(i > 0) 1277 2)深度为 k 的二叉树,最多只有 2^k -1 个节点。(k>0) 1278 3)对于任何一个二叉树,若度为2 的节点有n2个,则它的叶子数必为 n2+1个。 1279 4)满二叉树:深度为K,有 2^K-1 个节点。特点是:每层都充满了节点。 1280 6.完全二叉树: 1281 1)除最后一层外,每层的节点数都达到了最大值,且最后一层的节点尽力靠左。 1282 2)对于完全二叉树:若从上至下,从左至右编号,则编号为i的节点,其左孩子编号为 2i, 右孩子编号为 2i+1, 双亲的编号为 i/2;(i等于1时,为根除外)。 1283 1)左孩子右兄弟:可以将一颗多叉树转变为二叉树 1284 2)递归遍历(周游)二叉树 1285 3)非递归遍历二叉树(用栈的方法) 1286 4)遍历的三种方法: 1287 1)先序:先根再左再右 1288 2)中序:先左再根再右 1289 3)后序:先左再右再根 1290 5) 1291 1)已知二叉树的"先序"遍历和"后序"遍历序列"不能"唯一地确定这这棵树。 1292 2)已知二叉树的"先序"遍历和"中序"遍历"可以"唯一地确定这这棵树。 1293 1)先根据先序找到根节点,在根据中序确定根节点左右两边的节点 1294 2)再根据先序找到下一个根节点,再根据中序找到根节点左右两边的节点。 1295 3)"中序"、"后序"可以确定一棵树 1296 4)总体结论为:带"中序"的都可以确定一棵树,反之则确定不了 1297 1298 6)三种遍历的特点: 1299 1)先序遍历:可以确定每棵树的根节点。(特点: 它的输出序列中:第一个数为树的总根节点;剩下的子根节点,和中序遍历可以确定) 1300 2)中序遍历:可以确定根节点的左右子树。(它的特点为:输出序列中,根节点两边的为它的左右子树,和先序遍历配合可以确定一棵树) 1301 3)后续遍历:(它的特点;输出序列中,最后一个数为为树的总根节点。) 1302 1303 C++第十四天(数据结构、算法课程阶段)================================================++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1304 1305 1. 求二叉树叶子节点的数目(用全局变量写一遍) 1306 可以用静态变量和全局变量,还有形参参数。 1307 2. 求二叉树的高度(深度) 1308 1309 3.拷贝二叉树: 1310 1311 4.释放二叉树的节点。 1312 1313 5.井号法创建树。 1314 1315 6.排序算法 1316 1)新型冒泡排序(加标识量) 1317 2)新型选择排序(加标识量) 1318 3)插入排序: 1319 1.构建有序和无序序列,2.无序序列插入到有序序列中。 1320 2.应用场景:序列基本有序,元素较少。 1321 3.两步操作:元素拿出来,符合条件的元素后移。 1322 4)希尔排序: 1323 要点:增量:(len / 3 + 1);进行分组 1324 1325 缺点: 排序不稳定,相同大小的几个元素,排列完后前后位置可能会发生变化。 1326 2, 3, 4, 5, 5, 6, 5; 这几个5排序完后,可能与现在的前后顺序不同。 1327 1328 5)快排: 1329 要点:基准数(选取) 1330 一轮遍历下来,基准数的插入位置左边部分都比他小,右边部分都比它大。 1331 1332 6)归并排序:(将两个有序序列合并成一个有序序列) 1333 要点:1)先分组,将数据最后分成一个元素为一组的有序数列 1334 2)再两两合并排序; 1335 3)需要一个与本来元素相同大小的辅助空间。 1336 1337 7)堆排序: 1338 大顶堆:所有的根节点元素都比子节点两个元素大(完全二叉树)。 1339 小顶堆:………都比子节点元素小。 1340 1)先找出最后一个子根节点(len/2-1: len为数据的个数 。) 1341 2)从这个子根节点开始对堆进行数·初始化,形成(大顶堆或小顶堆) 1342 3)对初始化后的堆进行从第一个节点对最后一个节点交换(两两对应),每次交换完后,再对堆进行调整,(因为堆的结构可能发生变化)。 1343 1344 8)等号= 对排序效率的影响。 1345 1346 "C++实现链表:" 1347 1)老江的链表,宝哥的链表;有头节点,无头节点链表; 1348 2)与C做对比。 1349 1350 考试: 1351 一. 类中三种变量的初始化: 1352 1.static 静态成员变量时: (正确写法:在类外初始化:类型 类名::成员名) 1353 2.const 成员初始化; (正规写法:在类内用初始化列表初始化) 1354 3.const static 成员初始化: (正确写法:本地初始化;在类内定义的地方初始化) 1355 class person{ 1356 public: 1357 person(int num = 0) : Age(10) "初始化列表" 1358 { } 1359 void show() 1360 { 1361 cout<<"Age:"<<Age<<" ID"<<ID<<" size"<<size<<endl; 1362 } 1363 private: 1364 static int ID; "在类外初始化" 1365 const int Age; "初始化列表" 1366 const static int size = 100; "在本地初始化:即定义的地方" 1367 }; 1368 int person::ID = 30; 1369 1370 二.容器打印的三种方式 1371 1.回调函数 1372 2.回调对象 1373 3.兰博打表达式 1374 "=================================" 1375 4.C++11 的表达式: 1376 for(int val : arr){ "其中arr是一个数组。V是一个vector容器" 1377 v.push_back(val); 1378 } 1379 5.STL 求和表达式的第三个参数:累加初始值,容器的所有元素的和加上这个值为最后的求出值。 1380 1381 6.谓词;count_if() 的使用。 1382 7.绑定适配器: 1383 1384 三.用栈(两个)实现队列 1385 1.可以在一个类里面包含两个栈,用来实现队列的方式。当用这个类定义对象时,他就是一个队列。 1386 2.有意思;但是没想出来,两种方法做一遍,存博客 1387 1388 四.用归并的思想合并链表 1389 1.
五.迭代器的理解:
1.erase()函数的返回值,它的迭代器在循环遍历中的奇特之处;
2.循环遍历,it++放置不同的位置;
1)在正常的for循环位置
1)符合条件时erase()容器中的某个元素,但是没有接返回值
2)符合条件时erase()容器中的某个元素,接了返回值;
3)符合条件时,接了返回值,同时在下面接着 it--;看容器首位元素符合条件和不符合条件的两种情况。
2)不放在正常位置
1)放在不符合条件的情况下it++;
路之遥