C++面向对象编程思想
一、C语言和c++的函数重载(同名不同参),相互调用,对象(参数)传递与引用。
1. 类外定义函数,可用“类名::函数名”进行编写,在类内需要声明该函数
Class stu{
//成员
//属性
Private://没有private就默认public
Int age;
Char* name;
//方法engji
Public:
Void init();
}
Void stu::init(){ //stu::表明该函数为类内部方法 }
int main(){
stu s;
return 0;
}
2. 结构体成员按对齐分配大小,取决于最大的
例如:
Struct stu{
double a; //8位
int i; //加8位(原本只有4位)等于16位
char c; //由于Int后4位没有占用,char直接占用,16不改变
char c2; // 同上
}
sizeof(stu) = 16
3. 对象的创建,this指针指向调用的对象,对象的引用
java用new创建对象时的内存分配。
Stu s; //java中使用这语句时,定义标识符s,没有实际空间,只有new才分配空间创建对象
Stu stu; //在栈中申请空间,不能有括号,在程序结束后自动执行析构函数销毁对象
Stu stu1 = new Stu(); //在堆中申请空间
Stu *pstu = &stu1; //new创建类对象需要指针接收,一处初始化,多处使用
Stu *stu2 = new Stu();
delete pstu; //删除对象new创建类对象使用完需delete销毁
4. 类的定义与封装
class Stu{
int age;
char* name;
public: //公有的方法
void init(int age, char* name){} //初始化方法
}
void Stu::init(int age, char* name){ //Stu::指明了该函数为类内部方法
this->age = age;
this->name = name;
}
int main(int argc, char* argv[]){
Stu stu1;
stu1.init(19, "ye");
}
5.类方法(函数)构造,无返回值也无void,可以无参数;constructor, destructor
class Stu{
int age;
char* name;
public: //公有的
//实例函数,但不是构造函数
void init(int age, char* name){} //初始化对象属性值的函数
/*构造函数必须同时满足下面三个条件:
1、函数名和类名相同
2、在函数名的前面没有返回值类型的声明,void也没有
3、在函数中不能使用return语句返回一个值
4.构造函数在创建对象时自动执行,一般不能显式地直接调用*/
Stu();//可以无参数,用来初始化对象(默认被构造)
Stu(int age); //函数重载
Stu(int age=0, char* name);//默认参数
Stu(int* num);
}
析构函数:对象消亡时,自动被调用,用来释放对象(或构造函数请求的)占用的空间。
特点:
(1) 名字与类名相同
(2) 在前面需要加上"~"
(3) 无参数,无返回值
(4) 一个类最多只有一个析构函数
(5) 不显示定义析构函数会调用缺省析构函数
class Test{
int id;
public:
Test(int i){
id = i;
}
~Test(){
cout<<"ID: "<<id<<" destruction function is invoked!"<<endl;
};
};
int main(){
Test t0(0); //栈中分配
Test t1[3]{1,1,1}; //栈中分配,数组型对象
Test *t2 = new Test(2); //堆中分配
delete t2;
Test *t3 = new Test[3]{3,3,3};//堆中分配
delete []t3;
cout<<"------End of Main-------"<<endl;
return 0;
}
copy construction,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。
在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。
所谓浅拷贝,就是说编译器提供的默认的拷贝构造函数和赋值运算符重载函数,仅仅是将对象a中各个数据成员的值拷贝给对象b中对应的数据成员(这里假设a、b为同一个类的两个对象,且用a拷贝出b或用a来给b赋值),而不做其它任何事。
当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
<![if !supportLists]>l <![endif]>一个对象以值传递的方式传入函数体
<![if !supportLists]>l <![endif]>一个对象以值传递的方式从函数返回
<![if !supportLists]>l <![endif]>一个对象需要通过另外一个对象进行初始化。
<![if !supportLists]>l <![endif]>
1 #include<iostream> 2 3 using namespace std; 4 5 class CExample 6 7 { 8 9 private: 10 11 int a; 12 13 public: 14 15 //构造函数 16 17 CExample(int b) 18 19 { 20 21 a=b; 22 23 printf("constructor is called\n"); 24 25 } 26 27 //自定义的拷贝构造函数,深拷贝 28 29 CExample(const CExample & c) 30 31 { 32 33 a=c.a; 34 35 printf("copy constructor is called\n"); 36 37 } 38 39 //析构函数 40 41 ~CExample() 42 43 { 44 45 cout<<"destructor is called\n"; 46 47 } 48 49 void Show() 50 51 { 52 53 cout<<a<<endl; 54 55 } 56 57 }; 58 59 60 61 int main() 62 63 { 64 65 CExample A(100); 66 67 CExample B=A; 68 69 B.Show(); 70 71 return 0; 72 73 } 74 75
二、变量与常量基本使用
6.static local variable使用
1 /* static local variable 2 3 静态局部变量,不会分配在传统的栈区,不会被释放,效果相当于全局变量,但不会被函数外的所访问 4 5 仅在声明它的文件中可见,同一工程的其它文件中不可见,可解决不同文件的变量/函数重名问题; 6 7 */ 8 9 int global; //全局变量在程序开始时(main之前)被创建,结束时释放 10 11 12 13 void fun(){ 14 15 static int i = 0; //在第一次执行时被创建,程序完全结束后才释放,并不是函数退出释放当且仅当第一次执行才初始化,之后的值相当于保留上次结果 16 17 int j; 18 19 int k; 20 21 cout << i++ << endl; 22 23 cout << &j << &k << &i << endl; //地址与j,k不在同一区域 24 25 } 26 27 28 29 class Test{ 30 31 int id; 32 33 static int j; //类中对象所共有的 34 35 public: 36 37 Test(int i){ 38 39 id = i; 40 41 } 42 43 ~Test(){ 44 45 cout<<"ID: "<<id<<" destruction function is invoked!"<<endl; 46 47 }; 48 49 static void a_func(); //静态函数,可以直接通过类调用函数 50 51 }; 52 53 54 55 /*类似全局变量的方式对类内的局部变量定义,共享使用,相当于对所有对象初始化,无论这个类的对象定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。 56 57 */ 58 59 //即静态数据成员是该类的所有对象所共有的 60 61 int Test::j = 10; 62 63 64 65 int main(){ 66 67 for(int i = 0; i <= 10; i++) 68 69 fun(); //i输出1,2,3,4…… 70 71 Test::a_func(); //static型可直接调用,而不需要创建对象 72 73 return 0; 74 75 } 76 77
全局变量与命名空间namespace
所谓namespace,是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。
在类中:加上类名对函数和方法进行限定
1 class A //声明A类 2 3 { public: 4 5 void funl();//声明A类中的funl函数 6 7 private: 8 9 int i; }; 10 11 void A::funl() //定义A类中的funl函数 12 13 {…………}
自定义名:
namespace name1 // 指定命名中间 { int a; double b; }
当定义全局变量的时候如果变量名与名空间std内的变量名冲突,例如定义全局变量count, max等编译器会报“变量不明确的错误”,通过用::符号来区别
1 // C2872.cpp 2 // compile with: cl /EHsc C2872.cpp 3 namespace A { 4 int i; 5 } 6 7 using namespace A; 8 int i; 9 int main() { 10 ::i++; // ok, uses i from global namespace 11 A::i++; // ok, uses i from namespace A 12 i++; // C2872 ambiguous: ::i or A::i? 13 // To fix this issue, use the fully qualified name 14 // for the intended variable. 15 }
7.const 常量的使用
1 /* const 2 3 常量 4 5 */ 6 7 #include<iostream> 8 9 #include<cstring> 10 11 using namespace std; //命名空间,类作用域 12 13 14 15 const int i = 0; //常量不允许赋值,所以必须初始化,其作用域不会被改变 16 17 const int v[] = {1,2,3}; 18 19 20 21 void f1(const int i){ 22 23 i++; // Illegal compile 参数传递const值不能被改变 24 25 } 26 27 28 29 const int* const w(){ //返回值要求这个指针以及这个指针所指向的对象均为常量 30 31 static int i; //静态的局部变量,有效 32 33 return &i; 34 35 } 36 37 38 39 const int g(){}; // 返回const值,约定了函数框架里的原变量不会被修改 40 41 42 43 int main(){ 44 45 strcpy(s1, s2); // char * strcpy(char *s1, const char *s2); 46 47 //含有const为输入,无则为输出,且 s2不会被修改,称作指针参数 48 49 return 0; 50 51 } 52 53 54 55 /* const常量,保证只有一个对象,在全局作用域里声明的const变量是定义该对象的文件的局部变量,此变量只存在于那个文件中,不能被其它文件访问。 56 57 */ 58 59 class Schedule() { 60 61 Schedule(); 62 63 static Schedule* self; 64 65 public: 66 67 static Schedule* get_instance(); 68 69 } 70 71 72 73 Schedule* Schedule::self = NULL; 74 75 76 77 int main(){ 78 79 Schedule* s = Schedule::get_instance(); 80 81 return 0; 82 83 } 84 85 86 87 /* const常量在类中使用,const引用是指向const对象的引用, 88 89 */
1 #include<stdlib.h> 2 3 4 5 class Fred() { 6 7 const int size; // 不能直接初始化,需要在构造函数里 8 9 public: 10 11 Fred(int sz); 12 13 void print(); 14 15 } 16 17 18 19 Fred::Fred(int sz) : size(sz) {} //在构造函数里,const必定已经初始化了 20 21 void Fred::print() { 22 23 cout << size << endl; 24 25 } 26 27 28 29 int main(){ 30 31 Fred a(1), b(2), c(3); 32 33 a.print(), b.print(), c.print(); 34 35 return 0; 36 37 } 38 39
8.运算符重载
博客:大整数运算符重载:https://www.cnblogs.com/frankying/p/8544337.html
赋值运算符重载函数不能被继承
1 #include<cstdio> 2 3 #include<cstring> 4 5 #include<vector> 6 7 #include<algorithm> 8 9 #include<iostream> 10 11 using namespace std; 12 13 14 15 struct BigInteger { 16 17 static const int BASE = 100000000; 18 19 static const int WIDTH = 8; 20 21 vector<int> s; 22 23 24 25 BigInteger(long long num = 0) { *this = num; } // 构造函数 26 27 BigInteger operator = (long long num) { // 赋值运算符 28 29 s.clear(); 30 31 do { 32 33 s.push_back(num % BASE); 34 35 num /= BASE; 36 37 } while (num > 0); 38 39 return *this; 40 41 } 42 43 BigInteger operator = (const string& str) { // 赋值运算符 44 45 s.clear(); 46 47 int x, len = (str.length() - 1) / WIDTH + 1; 48 49 for (int i = 0; i < len; i++) { 50 51 int end = str.length() - i*WIDTH; 52 53 int start = max(0, end - WIDTH); 54 55 sscanf(str.substr(start, end-start).c_str(), "%d", &x); 56 57 s.push_back(x); 58 59 } 60 61 return *this; 62 63 } 64 65 BigInteger operator + (const BigInteger& b) const { 66 67 BigInteger c; 68 69 c.s.clear(); 70 71 for (int i = 0, g = 0; ; i++) { 72 73 if (g == 0 && i >= s.size() && i >= b.s.size()) break; 74 75 int x = g; 76 77 if (i < s.size()) x += s[i]; 78 79 if (i < b.s.size()) x += b.s[i]; 80 81 c.s.push_back(x % BASE); 82 83 g = x / BASE; 84 85 } 86 87 return c; 88 89 } 90 91 BigInteger operator - (const BigInteger& b) const { 92 93 BigInteger c; 94 95 c.s.clear(); 96 97 for (int i = 0, g = 0; ; i++) { 98 99 if (g == 0 && i >= s.size() && i >= b.s.size()) break; 100 101 int x = g; 102 103 if (i < s.size()) x += s[i]; 104 105 if (i < b.s.size()) { 106 107 if (s[i] < b.s[i]) { 108 109 x += BASE; 110 111 g = -1; 112 113 } 114 115 x -= b.s[i]; 116 117 } 118 119 c.s.push_back(x % BASE); 120 121 } 122 123 return c; 124 125 } 126 127 BigInteger operator += (const BigInteger& b) { 128 129 *this = *this + b; 130 131 return *this; 132 133 } 134 135 136 137 bool operator < (const BigInteger& b) const { 138 139 if (s.size() != b.s.size()) 140 141 return s.size() < b.s.size(); 142 143 for (int i = s.size()-1; i >= 0; i--) //从低位开始比 144 145 if (s[i] != b.s[i]) 146 147 return s[i] < b.s[i]; 148 149 return false; // 相等 150 151 } 152 153 bool operator > (const BigInteger& b) const { return b < *this; } 154 155 bool operator <= (const BigInteger& b) const { return !(b < *this); } 156 157 bool operator >= (const BigInteger& b) const { return !(*this < b); } 158 159 bool operator != (const BigInteger& b) const { return (*this < b || b < *this); } 160 161 bool operator == (const BigInteger& b) const { return (!(*this < b) && !(b < *this)); } 162 163 }; 164 165 166 167 ostream& operator << (ostream &out, const BigInteger& x) { 168 169 out << x.s.back(); 170 171 for (int i = x.s.size()-2; i >= 0; i--) { 172 173 char buf[20]; 174 175 sprintf(buf, "%08d", x.s[i]); 176 177 for (int j = 0; j < strlen(buf); j++) 178 179 out << buf[j]; 180 181 } 182 183 return out; 184 185 } 186 187 188 189 istream& operator >> (istream &in, BigInteger& x) { 190 191 string s; 192 193 if (!(in >> s)) return in; 194 195 x = s; 196 197 return in; 198 199 } 200 201 202 203 #include<set> 204 205 #include<map> 206 207 set<BigInteger> s; 208 209 map<BigInteger, int> m; 210 211 212 213 int main() { 214 215 BigInteger y; 216 217 BigInteger x = y; 218 219 BigInteger z = 123; 220 221 222 223 BigInteger a, b; 224 225 cin >> a >> b; 226 227 cout << a - b << "\n"; 228 229 //cout << BigInteger::BASE << "\n"; 230 231 return 0; 232 233 }
9.malloc, free, new delete
/* new delete使用
*/
#include<stdlib.h>
int main(){
new <=> malloc + 构造construction; //返回是对象类型的指针,(自由存储区)动态内存分配,malloc从堆中分配,返回值需要强制类型转换
delete <=> 析构deconstruct + free;
/*使用new操作符来分配对象内存时会经历三个步骤:
第一步:调用operator new函数(对于数组是operatornew[])
*分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。
第三步:对象构造完成后,返回一个指向该对象的指针。
使用delete操作符来释放对象内存时会经历两个步骤:
第一步:调用对象的析构函数。
第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。
总之来说,new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构。而mall *oc则不会。*/
return 0;
}
10.inline函数
1 /* 在线化函数,给编译器一个提示,要求它试着把所有对fac()的调用在线化 2 3 一个inline函数必须在需要用它的每个编译文件里做完全一样的定义 4 5 为了避免错误,一般把const(默认具有内部连接)和inline放在头文件里定义 6 7 */ 8 9 inline int fac(int n){ 10 11 return (n<2)?1:n*fac(n-1); 12 13 } 14 15 16 17 // extern关键字,在一个程序里,一个对象只能定义一次,但可以有多个声明(类型必须完全一样),file2.c文件里的extern指明了x只是一个声明,而在file1.c中被定义了 18 19 // file1.c: 20 21 int x; 22 23 int f(){} 24 25 26 27 // file2.c: 28 29 extern int x; 30 31 int f(); 32 33 void g(){ x= f(); } 34 35
11.default parameter默认参数
通常情况下,函数在调用时,形参从实参那里取得值。对于多次调用用一函数同一实参时,C++给出了更简单的处理办法。给形参以默认值,这样就不用从实参那里取值了。
1 float volume(float length, float weight = 4, float high = 5) 2 3 { 4 5 return length*weight*high; 6 7 } 8 9 int main() 10 11 { 12 13 float v = volume(10); 14 15 float v1 = volume(10,20); 16 17 float v2 = volume(10,20,30); 18 19 cout<<v<<endl; 20 21 cout<<v1<<endl; 22 23 cout<<v2<<endl; 24 25 return 0;
规则
1.默认的顺序,是从右向左,不能跳跃。
2.函数声明和定义一体时,默认认参数在定义(声明)处。声明在前,定义在后,默认参数在声明处。
3.一个函数,不能既作重载,又作默认参数的函数。当你少写一个参数时,系统无法确认是重载还是默认参数
12. 宏定义与头文件定义
(1)简单的宏定义:
#define <宏名> <字符串>
例: #define PI 3.1415926
(2) 带参数的宏定义
#define <宏名> (<参数表>) <宏体>
例: #define A(x) x
1)文件包含
可以把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。
(2)条件编译
预处理器根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,通常把排除在外的语句转换成空行。
(3)宏展开
预处理器将源程序文件中出现的对宏的引用展开成相应的宏 定义,即本文所说的#define的功能,由预处理器来完成。
经过预处理器处理的源程序与之前的源程序有所有不同,在这个阶段所进行的工作只是纯粹的替换与展开,没有任何计算功能,所以在学习#define命令时只要能真正理解这一点,这样才不会对此命令引起误解并误用。
define中的三个特殊符号:#,##,#@
#define Conn(x,y) x##y
#define ToChar(x) #@x
#define ToString(x) #x
(1)x##y表示什么?表示x连接y,举例说:
int n = Conn(123,456); /* 结果就是n=123456;*/
char* str = Conn("asdf", "adf"); /*结果就是 str = "asdfadf";*/
(2)再来看#@x,其实就是给x加上单引号,结果返回是一个const char。举例说:
char a = ToChar(1);结果就是a='1';
做个越界试验char a = ToChar(123);结果就错了;
但是如果你的参数超过四个字符,编译器就给给你报错了!
error C2015: too many characters in constant :P
(3)最后看看#x,估计你也明白了,他是给x加双引号
char* str = ToString(123132);就成了str="123132";
头文件中用到的 #ifndef/#define/#endif 来防止该头文件被重复引用
#ifndef __struct_h__ // if not define #define __struct_h__ // define class Struct{ }; void func(); #endif
三、继承
1 #include<stdlib.h> 2 3 public class B { 4 5 int i; 6 7 char *b; 8 9 public: 10 11 B(); 12 13 void fun(); 14 15 } 16 17 18 19 class T : public B { // T继承B,子类自动拥有父类的属性和行为 20 21 22 23 void T::fun(){ // 子类重载父类 24 25 B::fun(); // 继承父类的行为 26 27 /* 28 29 * 子类特性行为 30 31 */ 32 33 } 34 35 }
四、多态
将派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用,向上转型指针或引用,而非传值。
抽象类可以有成员变量,接口不能有。
1. 虚基类
2.纯虚函数与抽象类:
1 #include<stdlib.h> 2 /* 我们把包含纯虚函数的类称之为抽象类 3 对于抽象类来说,C++是不允许它去实例化对象的。也就是说,抽象类无法实例化对象。 4 抽象类只用来继承 5 */ 6 class T : public B { // T继承B,子类自动拥有父类的属性和行为 7 public: 8 virtual void fun2(); // virtual func,函数体可以有内容{。。。} 9 10 virtual void fun() = 0; // 纯虚函数 11 12 } 13 14 int main(){ 15 16 return 0; 17 }