重拾C++第一天
1.面向对象编程的三大特点:封装、继承、多态
2.C++中若不指定类中成员的访问权限默认就是private的(class默认是private的,struct默认是public的)。
3.C++规范中类的名字的首字母应该大写。
4.C++中的this是个指针,指向当前类或对象。(注意C++中this是指针,通过this->member访问成员,而Java中的this不是指针,通过this.member访问成员)
5.C++中的就近原则
当传参数名和类的成员名是一样的话,使用的就是参数名,因为比较近。
6.C++中类中"public:"下面的都是public属性的成员,而Java中的类的每个函数前都要加权限修饰。
7.C++和Javac程序对比
/*This_test.java*/ class Person { String name; public void setName(String name) { this.name = name; System.out.println("name="+this.name); } }; /*这里写不写";"都行*/ /*注意是这个类中包含main()和注意格式*/ public class This_test { public static void main(String args[]) { /* 1.这里直接Person person;来定义对象不行,必须要new Person(),若是有参构造函数()里还需要加参数。 * 2.java中没有指针,C++中的char * 这里要换成String。 * 3.C++中的this是指针,通过this->访问成员,Java中通过.来访问成员。 */ Person person = new Person();//new Person("mmmm"); C++中只有定义指针时才是这样的,eg Person *p1 = new Person("Zhangshan", 10); person.setName("ZhangShan"); } };
/*cpp_this.cpp*/ #include <stdio.h> class Person { const char *name; public: void setName(const char *name) { this->name = name; /*the type of this is "Person* const"*/ printf("name=%s\n", this->name); } }; /这里像结构体一样需要有“;”,但是namespace的{}后写不写都行/ int main() { Person person1; /*使用无参构造函数或有参Person person1("Zhangsan"); 这里不需要像Java语言必须去new一个对象*/ person1.setName("LiangLiang"); }
8.在类外实现类的函数,需要像C语言中声明格式一样在类中声明,然后在类外通过:“返回类型 类名::函数名(参数)”,通过命名空间引用也是使用"::"
9.没有使用命名空间的话,一个源文件要使用一个类,只需要包含这个类的声明的头文件即可。
10.using(非using namespace)后面跟的是对象,而不是命名空间,eg: using P1;报错,using P1::Person;正确(namespace P1{...})。
using P1::Person;表示把P1::Person放到global namespace中,然后就可以直接通过Person代表P1::Person
还可以吧using放到具体函数的内部,eg: 在函数内部使用using P1::Person;就是将P1::Person放到函数内部的local namespace中,
那么此时这个using只在此函数内部有效。
11.还有一种用法:using namespace P1; 表示把P1命名空间中的所有类和函数都导入进来。
using是一个一个地导入,using namespace是把整个命名空间的内容都导入进来。
12.若两个命名空间都定义了同样的函数,而且使用中使用using namespace同时导入了这两命名空间,只要不使用这个函数,也是没有问题的,
也就是说只有使用时链接有冲突了才编译报错。
13.#include <iostream>并且指定std命名空间后就可以使用cout<<进行打印,
using namespace std; cout<<XXX 或 std::cout<<XXX
14.引用,int& b = a; b是a的引用,也称为是a的别名,和a使用相同的地址。引用相当于一个常指针,在定义时必须初始化,eg. int &b; 编译是会报错的。
被引用的对象一定是要有实体的,例如作为非函数参数时int &b = 1; 编译时也会报错的。
15.当时const引用可以使用常量进行初始化,b放入符号表,没有实体,但是在编译时发现有强制取其地址,有会给它分配地址空间
const int &b = 1;
int *p = &b; //error: invalid conversion from ‘const int*’ to ‘int*’
int *p = (int *)&b; //ok
16.构造函数
与类同名,无返回类型,eg Person([参数])
Person per("ZhangSan", 10); //定义一个对象并使用2个参数的构造函数
Person per; //定义一个对象并使用无参数的构造函数
Person per(); //定义一个函数,返回Person ############################################
Person *p1 = new Person();
Person *p2 = new Person; //和上面一样都会调用无参构造函数来初始化对象
Person *p3 = new Person[4]; //指向一个数组,都调用无参构造函数。
Person *p4 = new Person("Zhangshan", 19);
使用完后,delete p1; delete []p3; 注意销毁的也是一个数组。
若是使用new获取的Person对象,没有使用delete释放,就算是整个进程执行结束,也不会调用析构函数,此时操作系统会帮我们释放分配的内存,可以使用free -m查看"free"栏。
17.析构函数
格式:~类名() 无论有多少个构造函数都只能有1个析构函数,而且这个析构函数还是没有参数的。
18.函数内部定义的对象在函数执行完后就会调用析构函数进行释放,但是函数内部new出来的对象是需要手动delete进行释放的。
19.下面代码,若name=NULL, 程序打印完“name= ”就死了!!!!为什么 ?????
cout << "name= " << name << ", age= " << age << ", work= " << work << endl;
20.拷贝构造函数和默认拷贝构造函数
C++编译器会为我们提供默认的什么都不做的无参构造函数和析构函数和默认的仅仅是值拷贝的拷贝构造函数。
一旦用户自己提供了构造函数,编译器提供的无参构造函数就不复存在了。因此定义了有参构造函数有时候还需要定义一个无参构造函数。
eg:定义自己的拷贝构造函数:
Person(Person &per) { cout << "Person(Person &per)" <<endl; this->name = new char[strlen(per.name) + 1]; memcpy(this->name, per.name, strlen(per.name)); this->name[strlen(per.name)] = '\0'; this->age = per.age; this->work = new char[strlen(per.work) + 1]; memcpy(this->work, per.work, strlen(per.work)); this->work[strlen(per.work)] = '\0'; }
Person per2(per1); 会触发调用拷贝构造函数。
注意:之后再执行per2 = per1;时就不会调用拷贝构造函数了,此时应该重载=
21.函数内的静态对象
函数func()内部的 static Person per("ZhangSan", 10); 在函数退出后不会调用其析构函数,第二次及以后进入func()也不会再次调用其构造函数,类似C中的static局部变量。
在main()退出后才会调用其析构函数。
22.全局对象
全局对象会在main()执行入口(执行main()中第一条指令之前)被构造。
33.类中包含其它类的成员的构造函数调用逻辑
会首先调用成员对象的构造函数,然后才调用自己的构造函数。若想在自己的构造函数中构造对象成员,那么需要使用到初始化列表,使用初始化列表传参调用对象成员的构造函数。
此时的构造顺序是先调用初始化列表中的构造函数,然后才会调用自己的构造函数。注意成员对象构造函数调用次序与在初始化列表中出现的先后次序无关,只与在Person类中定义的
次序有关,先定义的先调用。eg:
class { Father father; Mother mother; public: Person(char *name, int age, char *father_name, int father_age, char *mother_name, int mother_age):Father(father_name, father_age), Mother(mother_name, mother_age) { ... } }
析构函数的调用顺序:和构造函数的调用次序刚好相反。
34.参考代码:https://github.com/weidongshan/cpp_projects.git
35.默认参数
eg:
构造函数:Person(char *name, int age, char *work = "none");
定义参数:Person per("ZhangSan", 10);
36.C++代码中使用strlen()还是#include <string.h>
37.类的静态成员
static成员变量和方法是属于类的,而不是属于某个具体的对象的。可以使用"类名::"来直接引用。
class Person {
static int count; //这里只是声明类中有这个静态成员,并没有定义,也没有初始化。
}
在类外且main()之外:
int Person::count = 0; //定义并初始化(其前面不需要再加static修饰),定义成全局的目的是想在所有对象被构造之前初始化count值。它看起来像全局变量,
实际上不是的,它是存在于类的命名空间里面的。
静态成员函数中只能访问静态成员变量,不能访问非静态成员变量,因为非静态成员变量只有类实例化对象后才存在,可能在调用静态成员方法时还没有实例化,就算是实例化了也不知道是哪个实例化对象的。
38.初始化列表
初始化列表除了上面给对象成员进行初始化外还可以对非对象成员变量进行初始化
eg:
class Point { int x; int y; public: Point() {} //什么都不做的无参构造函数 Point(int x, int y):x(x), y(y) {} /*在初始化列表中对成员变量x和y进行初始化了,所以此例中构造函数就不需要做什么了,函数体为空*/ }
39.友元
友元可以访问类的私有属性。
若Point类设置add()函数为其友元函数,可以直接在类的内部用friend关键字声明add函数,
eg:
class Point { ... public: ... friend Point add(Point &p1, Point &p2); /*friend加函数签名*/ } 此后add()就可以像类的成员函数一样直接访问类的私有成员变量了: Point add(Point &p1, Point &p2) { Point n; n.x = p1.x + p2.x; n.y = p1.y + p2.y; return n; }
若一个非类的成员函数中直接操作了类的私有成员,就需要将此函数设置为类的友元。若不想设置成友元,就需要使用getX() setX()类似的方法操作类的私有成员变量。
40.运算符重载
int a = 1, b = 2; int c = a + b; 从某种意义上说,“+”也是一个函数,只不过是编译器内部实现的,既然是函数,那么就可以重载它,于是诞生了运算符重载。
eg:
Point operator+(Point &p1, Point &p2) { /*这里只是重载了"+",若是普通变量相加,由于参数匹配,使用的还是普通的"+"*/ Point n; n.x = p1.x + p2.x; n.y = p1.y + p2.y; return n; } 疑问: Point add(Point &p1, Point &p2) { Point n; n.x = p1.x + p2.x; n.y = p1.y + p2.y; return n; //使用新定义的对象接收的话也不会产生新临时对象(没有调用任何构造函数)。 } 要么不提供拷贝构造函数,要么就要提供成下面样子的拷贝构造函数,必须要有const修饰才行 Point(const Point &p) { this->x = p.x; this->y = p.y; cout <<"Point(const Point &p)"<<endl; }
需要const修饰的原因:
若是拷贝构造函数中没有加const修饰,那么在定义对象的时候使用const Point p2 = p1; 将会报错,因为const Point p2
无法转换成像拷贝构造函数中的参数const Point &p那样的可读可写的引用。
对于上面函数:
Point n = add(p1, p2); //不会在return n的时候产生临时对象
n = add(p1, p2); //会在return n的时候产生临时对象
应该是编译器做了优化,新定义的对象进行接收的话,add()中Point n使用的这个对象n就是函数外新定义的Point n. 这样才解释的通。
41.对前++和后++运算符重载(++p和p++)
函数签名中通过另增加一个参数来区分重载的是前++还是后++
eg: ++p
Point operator++(Point &p);
eg: p++
Point operator++(Point &p, int a); //但是一般在函数内部不使用arg2
b = ++a; 等效于 a=a+1; b=a; ===>对象加1后再返回这个对象
b = a++; 等效于 b=a; a=a+1; ===>返回这个对象后再让对象加1
在修改过程中发现在operator++()返回时会产生临时变量导致多出一次拷贝构造函数和析构函数被调用,浪费时间。优化方法:
对应前++可以将operator++()改为返回类的引用。对于后++则不可以。
也就是说优先采用前++更高效。
42.不能返回函数内部定义的临时变量(对象)的引用,因为函数执行完之后临时变量(对象)就被销毁了。
43.对运算符<<重载
cout<<m; cout<<n; cout<<m<<n<<endl;都行,说明cout<<返回的一定是cout的引用,所以才能一直这样<<下去。
ostream& operator<<(ostream &o, Point p) {
cout<<"("<<p.x<<","<<p.y<<")"<<endl;
return o;
}
44.既然重载运算符是函数,那么也就可以直接调用
eg:
operator+(pointer1, pointer2);
operator++(pointer1); //调用前++
operator++(pointer1, 0);//调用后++,arg2仅仅是为了在函数签名上与前++不同,可以任意传。
operator<<(cout, p1);
#include <iostream> using namespace std; class Point { int x; int y; public: Point() { cout <<"Point()"<<endl;} Point(int x, int y) : x(x), y(y) {cout <<"Point(int x, int y)"<<endl;} Point(const Point &p) { cout <<"Point(const Point &p)"<<endl; this->x = p.x; this->y = p.y; } ~Point() {cout <<"~Point()"<<endl;} int getX() {return x;} int getY() {return y;} void setX(int x) {this->x = x;} void setY(int y) {this->y = y;} friend Point add(Point &p1, Point &p2); friend Point operator+(Point &p1, Point &p2); friend Point& operator++(Point &p); friend Point operator++(Point &p, int a); friend ostream& operator<<(ostream &o, Point &p); }; Point add(Point &p1, Point &p2) { Point n; n.x = p1.x + p2.x; n.y = p1.y + p2.y; return n; } Point operator+(Point &p1, Point &p2) { Point n; n.x = p1.x + p2.x; n.y = p1.y + p2.y; return n; } /* b = ++a; ==> a = a + 1; b = a; */ Point& operator++(Point &p) { cout <<"++p"<<endl; p.x++; p.y++; return p; } /* b = a++; ==> b = a; a = a + 1; */ Point operator++(Point &p, int a) { cout <<"p++"<<endl; Point tmp = p; p.x++; p.y++; return tmp; } ostream& operator<<(ostream &o, Point &p) { cout <<"(" << p.x <<", " << p.y <<")" << endl; return o; } int main() { Point p1(1, 2); Point p2(5, 8); Point m, n; cout <<p1<<p2<< endl; operator++(p1); operator++(p2, 0); operator<<(cout, p1); operator<<(cout, p2); m = ++p1; cout <<m<< endl; n = p2++; cout <<n<< endl; return 0; }
45.类的成员函数重载运算符
上面介绍的重载运算符重载的都是类外部函数的,函数中直接使用类的私有成员,使用友元。
(1)类内部重载+
在类的外部重载加:Point operator+(Point &p1, Point &p2);
在类的内部重载加:Point operator+(Point &p); //这里只需要1个参数了,因为调用时通常是 对象名.operator+, 本身就已经能提供一个参数了。
eg:
Point operator+(Point &p) {
Point n;
n.x = this->x + p.x;
n.y = this->y + p.y;
return n;
}
使用:
Point m = p1 + p2; /*等效于:Point m = p1.operator+(p2);*/
(2)类内部重载前++
在类的外部重载前++:Point& operator++(Point &p);
在类的内部重载前++:Point& operator++(void); //就不需要参数了
Point& operator++(void) {
this->x++;
this->y++;
return *this;
}
使用:
Point m = ++p; /*等效于:Point m = p.operator++();*/
(3)类内部重载后++
在类的外部重载后++:Point operator++(Point &p, int a);
在类的内部重载后++:Point operator++(int a); //同样根据参数判断是前++还是后++
Point& operator++(int a) {
Point tmp = *this;
this->x++;
this->y++;
return tmp;
}
使用:
Point m = p++; /*等效于:Point m = p.operator++(0);参数任意传*/
比如后++的执行推测编译器的行为:
发现++作用的对象不是普通编译器默认支持的类型,于是查找类中有没有重载++运算符,查找类外有没有重载++运算符,若是在类的内部找到了
就直接调用,若是在类的外部找到了,就以此对象为参数进行调用。类内和类外的是等效的,同时定义会冲突。
#include <iostream> using namespace std; class Point { int x; int y; public: Point() { cout <<"Point()"<<endl;} Point(int x, int y) : x(x), y(y) {cout <<"Point(int x, int y)"<<endl;} Point(const Point &p) { cout <<"Point(const Point &p)"<<endl; this->x = p.x; this->y = p.y; } ~Point() {cout <<"~Point()"<<endl;} int getX() {return x;} int getY() {return y;} void setX(int x) {this->x = x;} void setY(int y) {this->y = y;} Point add(Point &p) { Point tmp; tmp.x = this->x + p.x; tmp.y = this->y + p.y; return tmp; } Point operator+(Point &p) { Point n; n.x = this->x + p.x; n.y = this->y + p.y; return n; } /* b = ++a; ==> a = a + 1; b = a; */ Point& operator++(void) { cout <<"++p"<<endl; this->x++; this->y++; return *this; } /* b = a++; ==> b = a; a = a + 1; */ const Point operator++(int a) { //here with const or not all ok cout <<"p++"<<endl; Point tmp = *this; this->x++; this->y++; return tmp; } friend ostream& operator<<(ostream &o, const Point &p); }; ostream& operator<<(ostream &o, const Point &p) { //因为表达式n++返回的是无名临时对象,只能用const引用指向 cout <<"(" << p.x <<", " << p.y <<")" << endl; return o; } int main() { Point p1(1, 2); Point p2(5, 8); Point m = p1.add(p2); cout << m << endl; Point n = p1 + p2; cout << ++n << endl; cout << n++ << endl; //因为表达式n++返回的是无名临时对象,只能用const引用指向 return 0; }
46.重载=
a. operator=()和拷贝构造函数调用的时机的区别:
Person p2 = p1; 时调用的是拷贝构造函数
p2 = p1; 时调用的是重载的operator=(),若是没有提供重载=,也是可以执行的,此时应该也是简单的值拷贝
在类外重载=时编译报错:constructor.cpp:56:61: error: ‘Person& operator=(const Person&, const Person&)’ must be a nonstatic member function
也就是说对=的重载函数必须作为类的成员函数在类的内部进行重载,不允许在类的外部进行重载!!
参考C++官网:http://www.cplusplus.com/doc/tutorial/templates/
eg在类内部重载=的例子:
Person& operator=(Person &p) { cout << "Person& operator=(Person &p)" << endl; if (this->name == NULL) { this->name = new char[strlen(p.name) + 1]; } memcpy(this->name, p.name, strlen(p.name) + 1); if (this->work == NULL) { this->work = new char[strlen(p.work) + 1]; } memcpy(this->work, p.work, strlen(p.work) + 1); this->age = p.age; return *this; }
47.const对象只能调用const函数
eg:
const对象: const Person p1("zhangshan", 14);
const函数: void printInfo(void) const //也即是在函数签名后面加上const, 用于表明不会修改对象的成员属性。
48.const引用
Point & p = n++; 错
const Point & p = n++; 对
因为表达式n++返回的是无名临时对象,只能用const引用指向.
49.可以直接使用new Stu而不用加(), 这样是调用无参构造函数,eg: Stu *s1 = new Stu;
50. 函数调用前加"::",表示调用的是全局函数,不是类自己的成员函数,下面是调用系统调用open()的例子。
int fd = ::open("./tmp.txt", O_RDWR);
51. C中使用#define,typedef对类型起别名,C++中使用using起别名,如 using ServiceMap = std::map<std::string, Service>; ServiceMap mNameToService;
posted on 2019-03-24 12:26 Hello-World3 阅读(176) 评论(0) 编辑 收藏 举报