其他分享
首页 > 其他分享> > primer 类初探

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