primer 类初探
作者:互联网
1 不要返回局部变量的引用或者指针; 2 因为局部变量在函数调用完就消亡了,此时引用的对象或指针指的内存都被清空了 3 这时候再使用就可能出问题; 4 5 没有名字的对象叫临时变量; 6 7 函数返回值为引用时此时为左值,其他类型为右值; 8 这意味着可以对函数调用赋值;而右值是常量不能被赋值; 9 如: 10 int &test(){} 11 test() = 10; 12 13 返回数组指针的时候; 14 可以用别名 15 如: 16 typedef int p[10]; 17 p* func(int i) //此时函数返回值指向10个元素的数组指针 18 19 如果不用别名,正常的是如int (*p) [10] = &a; 20 数组维度要在函数后面; 21 如 int (*func(int i)) [10] //表示函数的返回值是指针,指向一个int [10 的数组 22 c++11为我们提供后置返回值,比较贴近逻辑; 23 auto func(int i) -> int (*) [10] // 24 25 26 main函数不能重载 27 重载函数,形参数量或者类型有所不同 28 形参为顶层const时,此时顶层const无法与普通形参区分开 29 如 void test(const int a); 30 void test(int a);//test(10)此时编译器无法区分; 31 底层const 则是重载 32 如 void test(int &a); 33 void test(const int &a)//此时可以通过判断1绑定的是普通变量,2绑定的是常量 34 本质上不一样;重载形参类型看的是本质而不是表面 35 36 默认参数匹配规则 37 默认参数全部必须靠右,不能乱序; 38 如: 39 void test(int a = 1, int b = 2, string s = "hello"){} 40 如果想要覆盖s,则前面两个参数必须填上; 41 调用test(1,2); 42 一旦某个参数启动默认,后面必须全部都是默认,否则报错; 43 如test(,2,)//从第一个参数默认,后面还传2进来,故报错; 44 函数调用前包含一系列工作:要先保存寄存器,并在返回时恢复;可能要拷贝实参等等; 45 如果是简单功能的函数我们可以声明成内联函数,这样就可以直接展开省去函数调用,而又保留了函数的泛型,封装性 46 47 48 constexpr函数与constexpr指针 49 被constexpr修饰的对象相当于顶级const; 50 例如constexpr int a = 10;//a是一个常量 51 constexpr int * p = &r;//p是一个常指针等价于 int *const p = &r; 52 53 constexpr函数它的返回值不一定是常量表达式,取决于函数逻辑是怎么样返回的; 54 基于上述原因,constexpr函数一般要求形参类型是字面值类型,而且函数有且只有一条return语句; 55 56 由于内联函数和constexpr函数和其他函数不同,Ibanez都是比较短的函数 57 由于只有函数声明,没有定义是不能展开的,基于分离式编译,所以内联函数和constexpr函数一般定义在头文件//这才是有用的; 58 59 assert预处理宏; 60 用法: 61 assert(expr) 62 如果expr为真,则什么也不做,如果expr为假,则输出信息并终止程序 63 assert宏定义在cassert头文件,预处理名字由预处理编译器而非编译器管理 64 所以我们能直接使用assert名字而不是std::assert,也不需要为assert使用using说明; 65 这就是预处理编译器的强大;但是宏名字必须唯一。 66 67 可以引用#NODEBUG宏来跳过断言 68 用法: 69 #define NDEBUG 70 #include<bits/stdc++.h> 71 using namespace std; 72 73 inline void test(int a ) 74 { 75 cout << a << "\n"; 76 } 77 78 int main() 79 { 80 int b = 9; 81 test(b); 82 assert(0);//如果没有NDEBUG宏这里会抛出断言,NDBUG宏必须在assert头文件前!!! 83 return 0; 84 } 85 86 也可以自定义断言; 87 void print(const int ia[], size_t size) 88 { 89 #ifndef NDEBUG 90 cerr << __func_ << "array size is:" << size << "\n"; 91 #endif 92 //__func__是一个存放函数名的内置变量,它里面是链接器链接函数时需要找的名字; 93 //__FILE__ 存放文件名的字符串字面值; 94 //__LINE__存放当前行的整型字面值 95 //__TIME__存放文件编译时间的字符串字面值 96 //__DATE__存放文件编译日期的字符串字面值; 97 } 98 99 100 函数指针 101 bool mycmp(const string &s1, const string &s2); 102 103 用指针代替函数名字即可 104 bool (*p)(const string &, const string &); 105 (*p)是一个指针,指向一个返回值为bool的函数 106 函数名被使用时自动转变成函数指针; 107 bool b1 = mycmp("hello", "h"); 108 bool b2 = p("1", "2"); 109 110 和数组一样函数返回值不能是数组,函数返回值也不能是函数,但是可以是指针 111 返回值是函数指针; 112 113 using F = int (int, int);//F是函数类型; 114 using PF = int (*) (int, int);//PF是函数指针; 115 116 PF f(int);//正确,f是一个函数返回值为函数指针; 117 F *f(int);//正确,等价上面式子 118 也可以用强大尾置返回类型,这种比较符合逻辑 119 auto f(int) -> int (*) (int, int); 120 121 122 123 类: 124 125 成员函数的声明必须在类内,定义可以在类外也可以在类内; 126 127 this指针与常量 128 调用机制: 129 有个类 130 class test{ 131 public: 132 int a; 133 int print(){return a;} 134 }; 135 test mytest; 136 当调用mytest.print();时等价于 test::print(&mytest) const {return this -> a;} 137 这里this是一个隐式形参,编译器负责把对象地址传给隐式this; 138 this总是指向这个对象,所以this是一个常指针; 139 140 this指针默认是 test *const,常指针,这意味着它不能绑定一个常对象; 141 这样的话,如果对象是常对象,我们就不能用this指针,即什么调用也干不了,但是我们必须要调用啊 142 所以约定在函数参数列表之后用const表示this是一个指向常量的指针 143 即int print() const {}; 144 这样的话隐式的this就变成了const test *const指针了; 145 这样我们就能愉快的绑定常对象成员了,这也就是为什么常对象成员只能调用常成员函数和对象!!! 146 147 穿插volatile变量和mutable变量 148 volatile变量:为了避免编译器优化,对经常使用的对象放到寄存器,如果该对象是指针,则指针的内容会一直是寄存器里的内容 149 而不是内存的内容,因为该指针的内容有可能会变,这时候需要给对象加上volatile关键字告诉编译器不要对它优化! 150 151 mutable变量:配合常成员函数使用。 152 如:我们想要在常成员函数中改变一个值,但是此时this是双const,如果我们把函数参数列表后的const去掉,那么我们就不能泛型绑定对象了 153 此时我们只要把想要改变的变量加上mutable关键字,告诉编译器我们暂时把这个变量和this的底层Const去掉,这样this就能在这时修改了; 154 155 这里补充一点是,常成员函数绑定的this指针在调用类成员变量时能知道该this类型是cons type *const; 156 而在调用函数时,函数是类型共享一份的,所以不清楚此时this绑定的对象是不是常对象,所以要在函数后面加const把this也变成双const; 157 158 所以常对象可以随便调用类成员变量和常成员函数; 159 普通对象可以随便调用成员函数; 160 最后总结:常量对象,常量对象的引用或指针都只能调用常成员函数; 161 162 类的作用域下,变量的顺序可以随意摆放,而被成员函数使用; 163 原因是编译器在处理类时,先编译成员变量,然后再编译成员函数;成员变量和函数其实是分离的; 164 165 类内声明函数,类外定义函数粗腰保持一直,如果是由const属性也必须包含 166 如: 167 double A::print() const 168 { 169 cout << a << "\n" 170 } 171 解释:这是一个A类的常成员函数,返回值为double,其中a是成员变量,可以直接隐式调用this - > a; 172 因为函数默认会有个隐式形参this指针; 所以a就是this - > a; 不是什么参数都没就能调用; 173 174 返回this对象的函数 175 176 type & type::add(const type &obj) 177 { 178 a += obj.a 179 b += obj.b; 180 return *this; 181 } 182 这样做的好处是在当前对象修改,因为是引用所以省去了返回值的拷贝; 183 184 对自定义的类进行读入读出; 185 istream &read(istream &is, type &obj) 186 { 187 is >> obj.a >> obj; 188 return is; 189 } 190 //这样我们就能读入一个type对象了,也可以read(cin,obj) >> othertype; 191 同理 192 ostream &print(ostream &os, type &obj) 193 { 194 os << obj.a << obj.b; 195 return os; 196 } 197 注意:IO类属于不能被拷贝的类型,所以只能用引用,因此也只能返回引用,如果不是引用会产生拷贝; 198 199 200 构造函数:用于初始化类对象的数据成员; 201 构造函数名字和类名一致,而且没有返回值; 202 构造函数不能被声明成cosnt,因为这样this指针就不能修改成员变量,也就无法对它们赋值了; 203 204 默认构造函数无须任何实参,而且只有当类没有任何构造函数时,编译器才会生成默认构造函数; 205 如果是类内成员变量有初始值,则初始值不变,如果没有赋值而且是内置类型,则按照内置类型规则进行初始化; 206 207 class type{ 208 type() = default;//告诉编译器需要默认构造函数 209 type(const std::string &obj1, int32_t obj2) : s(obj1), a(obj2) {}//构造函数初始化列表 210 }; 211 类外定义构造函数 212 type::type(int obj){a = obj};//这里解释一下第一个type::表示是type类,第二个type是构造函数的名字与l类名一样; 213 214 215 拷贝、赋值和析构 216 拷贝:初始化变量以及以值的方式传递(传参数)或者返回一个值,就会发生拷贝; 217 如: int a(10);void test(type obj); return obj; 218 赋值:使用赋值运算符"="时会发生赋值; 219 析构:对象销毁时编译器会自动调用析构函数; 220 221 如果我们不定义这些,编译器会默认替我们生成:构造、拷贝、赋值、析构函数; 222 223 访问控制与封装 224 常说的多态、继承、封装 225 封装就是使用访问说明符 public: private: protect: 226 public:可以被该类中的函数、子类的函数、友元函数访问,也可以由该类的对象访问; 227 protected:可以被该类中的函数、子类的函数、友元函数访问,但不可以由该类的对象访问; 228 private:可以被该类中的函数、友元函数访问,但不可以由子类的函数、该类的对象、访问。 229 230 struct 和 class唯一区别就是struct默认访问控制是public,class默认访问控制是private;(什么控制符都没声明的情况下) 231 232 类内初始化只用 =赋值或者用{}初始化而不用() 233 因为怕出现 234 class Widget 235 { 236 private: 237 typedef int x; 238 int z(x);//此时z被当成一个函数而不是初始化!!! 239 }; 240 241 一个类在未完全定义前,不能使用它,但可以定义它的指针和引用; 242 因为未完全定义,编译器不知道这个类有哪些成员,以及需要分配多少内存 243 因为指针变量是存储定制,默认四字节,所以只要不调用就没影响 244 而引用指的是传参或返回值,不是type &a = b这种; 245 声明是表示存在,而定义是给声明做具体解释,需要知道成员和内存信息; 246 247 class type{ 248 type *p; 249 type& test();//只能声明不能定义; 250 }; 251 252 友元类 253 class type 254 { 255 friend class type2; 256 }; 257 友元没有传递性 258 上面type2作为type的友元类 259 假设type2里有一个友元类type3; 260 则type3不是type的友元类; 261 262 其他类的函数声明成友元函数 263 class type2{ 264 void pirnt(){} 265 }; 266 class type{ 267 friend class type2::print(); 268 };
标签:const,函数,int,对象,初探,test,primer,指针 来源: https://www.cnblogs.com/matt-su/p/16247505.html