/******************************************************************************************************************/
一、C++类的引入
与C相比,
1.编译使用g++代替 gcc,执行在linux中还是一样的
2.c++里面也有struct并对其进行了扩展,struct中的函数可以直接使用其成员,并可在struct中直接实现,
例:
struct person {
char *name;
int age;
char *work;
void printInfo(void)
{
printf("name = %s, age = %d, work = %s ", name, age, work);
}
};
int main(int argc, char **argv)
{
struct person persons[] = {
{"zhangsan", 10, "teacher"},
{"lisi", 16, "doctor"},
};
persons[0].printInfo();
persons[1].printInfo();
return 0;
}
3.这样的struct可以使用class代替,注意权限问题,class还有新的特性,
例:
class person {
public:
//注意权限问题
char *name;
int age;
char *work;
void printInfo(void)
{
printf("name = %s, age = %d, work = %s ", name, age, work);
}
};
/******************************************************************************************************************/
二、C++基础知识_访问控制
C++有三种访问权限private,protected,public
1.默认权限是private
2.对象的创建
类 对象
例:Person mPer;
3.访问本类中的属性,使用this->
由于c和c++里面有就近原则,所以这样就可以使用同名
访问父类的属性,直接使用该属性名即可(或者使用 父类名::属性名 的方式来访问)
/******************************************************************************************************************/
三、C++基础知识_程序结构
1.类中的成员函数在类中可不定义只是声明,实现放在类外面(类名 :: 函数名)
例:
class Person {
private:
char *name;
int age;
char *work;
public:
void setName(char *name);
int setAge(int age);
void printInfo(void);
};
void Person::setName(char *name)
{
this->name = name;
}
int Person::setAge(int age)
{
if (age < 0 || age > 150)
{
this->age = 0;
return -1;
}
this->age = age;
return 0;
}
void Person::printInfo(void)
{
printf("name = %s, age = %d, work = %s ", name, age, work);
}
int main(int argc, char **argv)
{
Person per;
//per.name = "zhangsan";
per.setName("zhangsan");
per.setAge(200);
per.printInfo();
return 0;
}
2. 实现放在类外面,这样就可以优化整个程序结构,
把类的定义(函数成员只是声明)放在头文件中
使用者包含这个头文件,就知道怎么用了(不去关心具体实现)
具体实现放在具体实现文件里,包含对应头文件。
Linux下的编译方式
person: main.o person.o
g++ -o $@ $^ //$@表示person(目标),$^表示所有的依赖
%.o : %.cpp
g++ -c -o $@ $< //$<表示第一个依赖
3.为防止出现函数名一致(函数名重复),导致不知道具体使用哪一个函数,引入命名空间namespace,
1).用法:
在定义和声明的地方,用namespace包含起来,例
namespace A
{
}
2).调用的时候使用A::函数名
注意使用A中的类创建对象也要加上A,即A::Person per;
per.setName();
3).如果命名空间之间的类名没有冲突,
类创建对象是可以不使用A::Person per;
使用using声明,即using A::Person;(一般放在文件前面)
文件前面代表的是全局命名空间,所以
/* 把A::Person放入global namespace, 以后可以使用Person来表示A::Person */
4).如果命名空间之间的类名没有冲突,
也可以使用using编译,即using namespace A;(一般放在文件前面)
把A空间里面所有的内容都导进来
出现冲突的地方必须指明函数所在的命名空间(A::函数名)
4.C++打印使用cout<<endl; 代替printf,
例:
std::cout<<"name = "<<name<<" age = "<<age<<" work = "<<work<<std::endl;
注意
1).需包含头文件
#include <iostream>
2).由于使用了命名空间,要指明cout的命名空间为标准命名空间里的
std::cout<<"name = "<<name<<" age = "<<age<<" work = "<<work<<std::endl;
或在文件前面指明using namespace std;
/******************************************************************************************************************/
四、C++基础知识_重载_指针_引用
1.重载
函数名一样,参数不一样
2.指针
与C语言一样
3.引用(类似内存地址,使用和变量名一样)
C++引入引用就是为了避免使用指针(防止指针使用不恰当),其实就是操作同一块内存
例:
int a=100;
int &b=a;//b就是a的引用
b是a的别名,操作b也就是操作a
定义:
int add_one_ref(int &b)
{
b = b+1;
return b;
}
使用:cout<<add_one_ref(a)<<endl;
注意,引用定义的时候就必须初始化,即赋值,同时引用是变量的别名,所以只能使用变量来赋值
/******************************************************************************************************************/
五、C++基础知识_构造函数
1.构造函数:在类中,和类名名字相同的函数,可以带参数也可不带
1).带参数的,创建对象时可传入,
Person per("zhangsan", 16);
2).无传入参数的,
Person per2;
//Person per2();//不使用这个,这个表示的是函数名为per2的函数的声明
2.不想写很多的重载的构造函数的情况
Person(char *name, int age, char *work = "none")
{//表示如果没有传入第三个参数,则第三个参数为"none"
cout <<"Pserson(char*, int)"<<endl;
this->name = name;
this->age = age;
this->work = work;
}
Person per("zhangsan", 16);//导致this->work ="none"
3.除Person per("zhangsan", 16);方式外,创建对象的方式
使用指针的方式:
Person *per4 = new Person;
//这两种创建对象的方式一样,都是调用无参构造函数
Person *per5 = new Person();
//这两种创建对象的方式一样,都是调用无参构造函数
Person *per6 = new Person[2];
//per6是个对象数组,数组成员是对象
Person *per7 = new Person("lisi", 18, "student");
Person *per8 = new Person("wangwu", 18);
per.printInfo();
per7->printInfo();
per8->printInfo();
//动态创建的对象(new出来的对象),整个程序(进程)执行完后会自动释放所有对象占用的内存空间(包括子函数中创建的对象),如果要手动释放,可以用delete命令:
delete per4;
delete per5;
delete []per6;
//对象数组的删除
delete per7;
delete per8;
//不是new的对象,局部变量:函数执行完会自动销毁,全局变量:一直存在
4.分配堆空间,使用new代替malloc
分配:
this->name = new char[strlen(name) + 1];
strcpy(this->name, name);
释放:
delete this->name;
linux中,free命令查看空闲内存
5.析构函数(只能有一个)
释放对象时,对象所占据的内存释放了,如果对象的函数成员中又new了对象,同时又没有对函数成员中new的对象delete,那么就会造成内存泄漏。
比较方便解决这个问题,就是引入析构函数,析构函数中加入对函数成员中new的对象的delete操作,这样delete对象时就释放了函数成员中new的对象(delete销毁对象的同时就会调用析构函数)
例:
~Person()
{
if (this->name)
delete this->name;
if (this->work)
delete this->work;
}
注意使用new创建的对象,用完要用delete释放
6.默认拷贝构造函数
拷贝构造函数和赋值函数的区别:拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。
String a("A");
String b("B");
String c = a; // 调用了拷贝构造函数,最好写成c(a);
c = b; // 调用了赋值函数
拷贝构造函数分为深拷贝和浅拷贝,深浅拷贝:https://blog.csdn.net/libin66/article/details/53140284
无参数时,默认构造函数
有对象作为参数时,默认拷贝构造函数(进行值的拷贝,即浅拷贝) //C++类有四个默认函数---构造函数、拷贝构造函数(浅拷贝)、析构函数、赋值函数(浅拷贝):https://blog.csdn.net/anye3000/article/details/6570445)
Person per("zhangsan", 18);
Person per2(per);// 有对象参数, per是对象
默认拷贝构造函数,会改变其中的值,则在释放的时候,同一个值对应的堆空间会被释放两次,所以要定义自己的拷贝构造函数
Person(Person &per)
{
cout <<"Pserson(Person &)"<<endl;
this->age = per.age;
this->name = new char[strlen(per.name) + 1];//重新分配空间,如果是默认拷贝构造函数,就会进行值的拷贝,即这个指针的值等于传进来对象成员指针的值,导致不同对象指向同一个空间,从而导致内存释放时会释放两次
strcpy(this->name, per.name);
this->work = new char[strlen(per.work) + 1];
strcpy(this->work, per.work);
}
7.对象的构造顺序
1).创建对象首先是全局变量(在main函数执行之前),然后碰到哪个就创建哪个(不管是否静态),即按运行中定义对象的顺序调用构造函数。
由于静态对象的生存周期,退出函数不会被销毁,同时再次进入函数由于已经创建了也不会再次创建,即只定义一次
2).类中的数据成员是对象的情况,
I、先调用成员对象的构造函数,再调用自己的构造函数。
调用成员对象的无参构造函数,无需添加什么会自动调用
II、调用成员对象(数据成员是对象)的有参构造函数的方法
在自己类的构造函数后面加上:号,例:
class Student {
private:
Person father;//定义顺序
Person mother;
int student_id;
public:
Student()
{
cout<<"Student()"<<endl;
}
Student(int id, char *father, char *mother, int father_age = 40, int mother_age = 39) : mother(mother, mother_age), father(father, father_age)
{//加上:号就会去调用father对象的有参构造函数,修改:号后面的对象顺序不影响构造顺序,构造顺序按照定义的顺序,即先father,再mother,最后自己的
cout<<"Student(int id, char *father, char *mother, int father_age = 40, int mother_age = 39)"<<endl;
}
~Student()
{
cout<<"~Student()"<<endl;
}
};
III、析构函数的调用顺序
和构造函数的调用顺序相反
/******************************************************************************************************************/
六、C++基础知识_静态成员_友员
静态成员是属于类的(放在类的类名空间中),只占一份空间,不属于对象
1.类中定义
private:
static int cnt; //静态成员
public:
static int getCount(void)
{ //静态成员用于访问静态成员变量
//静态成员函数不能访问非静态成员变量(还没定义分配)
return cnt;
}
2.使用
一般使用成员函数访问:Person::getCount()
如果是公有属性,也可以Person::cnt;
3.class是struct的进化,所以还需要分配空间
int Person::cnt = 0; /* 定义和初始化 不再需要加static*/
//同时要放在main之前(放在main内会编译出错),因为要在创建对象之前就定义和初始化
4.也可以在类外定义静态成员函数
public:
static int getCount(void);//类内部声明
int Person::getCount(void)
{ //类外部定义,不再需要加static
return cnt;
}
5.也可以使用类的实例化对象调用静态成员函数
cout << "person number = "<<Person::getCount()<<endl;
cout << "person number = "<<p[0].getCount()<<endl;//类的实例化对象调用静态成员函数
cout << "person number = "<<p[1].getCount()<<endl;
6.友元
1.Point(int x, int y) : x(x), y(y) {}
等价于
Point(int x, int y){this->x=x,this->y=y}
2.类的友元函数(该函数一般会操作该类)可以直接访问该类的私有成员
1)首先在类中声明是友元函数
class Point
{
private:
int x;
int y;
public:
Point() {}
Point(int x, int y) : x(x), y(y) {}
int getX(){ return x; }
int getY(){ return y; }
void setX(int x){ this->x = x; }
void setY(int y){ this->y = y; }
void printInfo()
{
cout<<"("<<x<<", "<<y<<")"<<endl;
}
friend Point add(Point &p1, Point &p2);
};
2)友元函数就可以直接使用类的私有成员了
Point add(Point &p1, Point &p2)
{
Point n;
n.x = p1.x+p2.x;
n.y = p1.y+p2.y;
return n;
}
/******************************************************************************************************************/
七、C++基础知识_运算符重载_类外函数
1.加减乘除运算的重载
例如+号的重载
1).定义:
Point operator+(Point &p1, Point &p2)
{
cout<<"Point operator+(Point &p1, Point &p2)"<<endl;
Point n;
n.x = p1.x+p2.x;
n.y = p1.y+p2.y;
return n;
}
2).使用
Point p1(1, 2);
Point p2(2, 3);
Point sum = p1+p2;
2.++p,p++的重载
1).定义:
/* Point p(1,2); ++p; */
Point operator++(Point &p)
{
cout<<"++p"<<endl;
p.x += 1;
p.y += 1;
return p;
}
/* Point p(1,2); p++; */
Point operator++(Point &p, int a)
{//参数不同实现重载
cout<<"p++"<<endl;
Point n;
n = p;
p.x += 1;
p.y += 1;
return n;
}
2).使用
Point p1(1, 2);
Point p2(2, 3);
Point n = ++p1;
n.printInfo();
p1.printInfo();
cout << "******************"<<endl;
Point m = p2++;
m.printInfo();
p2.printInfo();
4.返回值是对象时,返回时会创建临时对象的问题(临时对象是局部变量用完又会被销毁)
/* Point p(1,2); ++p; */
Point operator++(Point &p)
{
cout<<"++p"<<endl;
p.x += 1;
p.y += 1;
return p;//返回引用,返回类型又是对象,系统就会自动创建临时对象,用完销毁(整个过程会调用构造函数与析构函数,浪费时间)
}
改进:
/* Point p(1,2); ++p; */
Point& operator++(Point &p)
{//使用引用则不会再去创建
cout<<"++p"<<endl;
p.x += 1;
p.y += 1;
return p;//使用引用则不会再去创建
}
Point operator++(Point &p, int a)
{//参数不同实现重载
cout<<"p++"<<endl; //endl表示换行
Point n;
n = p;
p.x += 1;
p.y += 1;
return n;//已经创建了n(已经有临时变量了)则不会再去创建临时对象
}
5.使用返回对象还是引用,取决于是否影响程序执行结果
引用返回效率高,在不影响运算结果则效率优先,即使用引用
6.<<的重载(<<也是运算符)
1)定义
ostream& operator<<(ostream &o, Point p)
{
cout<<"("<<p.x<<", "<<p.y<<")";//不需要换行
return o;
}
2)使用
cout<<m<<" "<<n<<endl;
//<<m表示执行<<m,并将结果输出到ostream
//<<m<<n表示把先执行<<m得到的ostream与n一起作为<<的参数,从而得到新的ostream引用,即<<n执行的结果追加到之前执行的<<m的结果后面
//cout<<m<<" "<<n<<endl,表示cout使用 执行<<m<<" "<<n<<endl的返回值,即<<m<<" "<<n<<endl的返回值输出到cout
7.运算符重载函数可直接调用
operator++(p1,0);//后一个参数随便传入值,只是为了分辨
operator<<(cout,p1);
/******************************************************************************************************************/
八、C++基础知识_运算符重载_成员函数
1.类中定义重载函数
Point operator+(Point &p)
{
cout<<"operator+"<<endl;
Point n;
n.x = this->x + p.x; //类中所以可以这么用 p.x
n.y = this->y + p.y;
return n;
}
/* Point p(1,2); ++p; */
Point& operator++(void)
{
cout<<"operator++(void)"<<endl;
this->x += 1;
this->y += 1;
return *this; //返回别名,变量名
}
/* Point p(1,2); p++; */
Point operator++(int a)
{
cout<<"operator++(int a)"<<endl;
Point n;
n = *this;
this->x += 1;
this->y += 1;
return n;
}
2.使用
Point p1(1, 2);
Point p2(2, 3);
Point m, n;
m = p1 + p2; /* m = p1.operator+(p2); */
cout<<"add p1,p2 = "<<m<<endl;
cout<<"begin"<<endl;
m = ++p1; /* m = p1.operator++(); */
cout<<"m = "<<m<<" "<<"p1 = "<<p1<<endl;
cout << "******************"<<endl;
n = p1++; /* m = p1.operator++(0); */
cout<<"n = "<<n<<" "<<"p1 = "<<p1<<endl;
cout<<"end"<<endl;
3.重载等号
对象拷贝时,可能会导致两个对象中的指针指向同一块内存,就有可能导致同一块内存被释放两次(或者覆盖导致,导致原有的内存泄漏)
重载等号可解决这个问题
1.类中定义
Person& operator=(const Person& p)
{
cout << "operator=(const Person& p)"<<endl;
if (this == &p)
return *this;
this->age = p.age;
if (this->name)
{
delete this->name;
}
if (this->work)
{
delete this->work;
}
this->name = new char[strlen(p.name) + 1];
strcpy(this->name, p.name);
this->work = new char[strlen(p.work) + 1];
strcpy(this->work, p.work);
return *this;
}
2.使用
const Person p1("zhangsan", 10);
cout<<"Person p2 = p1" <<endl;
Person p2 = p1;
//调用拷贝构造函数,没有调用重载等号函数
//注意拷贝构造函数的参数要加上const,来兼容只读的const参数(const对象作为参数)const对象只能调用const函数(函数声明后加上const,表示这个函数不会有修改的操作,不会修改类中的变量)
Person p3;
cout<<"p3=p1"<<endl;
p3 = p1;//调用重载等号函数
cout<<"end"<<endl;
//p3=(p2=p) 再次赋值的情况,所以重载函数需要返回值