概览:C++类的基础知识,包括构造函数、初始值列表、拷贝构造函数、重载赋值运算符、深拷贝与浅拷贝、static、const、new以及友元。
本文首发于我的个人博客www.colourso.top,欢迎来访。
代码全部运行于VS2019
为简化考虑,部分源码省略了
#include<iostream>
以及using namespace std
。博客后续会持续更新补充。
#include <iostream>
#include <sstream>
using namespace std;
enum Sex{MALE=0,FEMALE};
class Cat
{
public:
Cat() :name("unname"), age(0),sex(Sex::MALE){}
Cat(string name, int age, Sex sex) :
name(name), age(age), sex(sex) {}
//拷贝构造
Cat(const Cat& other);
//拷贝赋值运算符重载
Cat& operator=(const Cat& other);
~Cat() {}
string getName();
void setName(string str);
int getAge();
void setAge(int num);
Sex getSex();
static string getPetOwner();
static void setPrtOwner(string owner);
string toString();
private:
string name;
int age;
Sex sex;
static string petOwner;
};
string Cat::petOwner = "Colourso";
Cat::Cat(const Cat& other)
{
this->name = other.name;
this->age = other.age;
this->sex = other.sex;
}
Cat& Cat::operator=(const Cat& other)
{
this->name = other.name;
this->age = other.age;
this->sex = other.sex;
return *this;
}
string Cat::getName()
{
return this->name;
}
void Cat::setName(string str)
{
this->name = str;
}
int Cat::getAge()
{
return this->age;
}
void Cat::setAge(int num)
{
this->age = num;
}
Sex Cat::getSex()
{
return this->sex;
}
string Cat::getPetOwner()
{
return petOwner;
}
void Cat::setPrtOwner(string owner)
{
petOwner = owner;
}
string Cat::toString()
{
stringstream ss;
ss << "Name: ";
ss << this->name;
ss << ",age: ";
ss << this->age;
ss << ",sex: ";
ss << (this->sex ? "male" : "female");
ss << ". Owner: ";
ss << petOwner;
return ss.str();
}
int main()
{
Cat* a = new Cat("Kelly", 4, Sex::FEMALE);
cout << a->toString()<< endl;
Cat b("Bob",3,Sex::MALE);
cout << b.getName()<<" love his owner: "<<Cat::getPetOwner()<< endl;
if (b.getAge() < a->getAge())
b.setAge(a->getAge() + 2);
cout << b.toString() << endl;
delete a;
return 0;
}
class Dog
{
public:
Dog(string name)
{
this->name = name;
}
void walk()
{
cout <<"Dog "<< this->name <<" walk." << endl;
}
private:
string name;
};
struct Cat
{
string name;
Cat(string name)
{
this->name = name;
}
void walk()
{
cout << "Cat " << this->name << " walk." << endl;
}
};
int main()
{
Dog wang("meow");
wang.walk();//Dog meow walk.
Cat hua("wang");
hua.walk();//Cat wang walk.
return 0;
}
如上所示,在C++中,我们可以使用class
关键字或者struct
关键字进行类的定义。
两者唯一的区别在于struct
与class
的默认访问权限不同。
Student.h
#ifndef STUDENT_H__
#define STUDENT_H__
#include <iostream>
using namespace std;
class Student
{
public:
Student();
Student(string name, int age, bool sex);
void show();
private:
string name;
int age;
bool sex;
};
#endif
Student.cpp
#include "Student.h"
Student::Student()
{
}
Student::Student(string name, int age, bool sex)
{
this->name = name;
this->age = age;
this->sex = sex;
}
void Student::show()
{
cout << this->name << ": age:" << this->age << ", sex:" << (this->sex ? "male" : "female");
}
class Student
{
public:
string name;
protected:
bool sex;
public:
Student(){}
Student(string name, int age, bool sex)
{
this->name = name;
this->age = age;
this->sex = sex;
}
void ShowMessage()
{
cout << this->name << ": age:" << this->age << ",sex:" << (this->sex ? "男":"女" ) << endl;
}
private:
int age;
};
int main()
{
Student s("小明", 12, true);
cout << s.name << endl;//小明
//cout << s.age << endl; //错误:不可访问
s.ShowMessage();//小明: age:12,sex:男
return 0;
}
(如上所示的糟糕编码,只是为了展示类内的访问权限的界限是从一个界限开始到下一个界限截至的,例如public到private之间的内容权限都是public,同时一个访问说明符可以出现多次。)
class Student
{
public:
// 默认构造函数
Student() {}
//重载带参构造函数
Student(string name)
{
this->name = name;
}
string dis()
{
return this->name;
}
private:
string name;
};
class SuperStudent
{
public:
//默认构造函数
SuperStudent() {}
//带参构造函数
SuperStudent(int age)
{
this->age = age;
}
//构造函数初始值列表
SuperStudent(Student stu,int age) :stu(stu),age(age) {}
void show()
{
cout << this->stu.dis() << this->age << endl;
}
private:
Student stu;
int age;
};
int main()
{
Student s("小明");
SuperStudent s1(s, 12);
s1.show();//小明12
SuperStudent s2(12);
s2.show();//12
return 0;
}
构造函数是用于初始化类对象的非static数据成员,无论何时,只要类的对象被创建,那么就会执行构造函数。
class Student
{
public:
//默认构造函数
Student() {}
//构造函数初始值列表
Student(char str[], bool sex, int age) :m_sex(sex),m_age(age)
{
// m_sex = sex; //错误:表达式必须是可修改的左值
strcpy_s(m_name, str);
}
void show()
{
cout << m_name << ": sex: " << (m_sex ? "male" : "female") << ", age:" << m_age << endl;
}
private:
char m_name[20];
const bool m_sex = true;
int m_age;
};
int main()
{
char str[] = "Bob";
Student s(str, true, 12);
s.show();//Bob: sex: male, age:12
return 0;
}
class Foo
{
public:
//默认构造函数
Foo() {};
Foo(string str, int num) :str(str), num(num)
{ }
//拷贝构造函数
Foo(const Foo& foo) :str(foo.str), num(foo.num)
{
times++;
}
void show()
{
cout << this->str << "-" << this->num << "-" << times << endl;
}
static int times;
private:
string str;
int num;
};
int Foo::times = 0;
int main()
{
Foo f1("hello", 10); //直接初始化
f1.show(); //hello-10-0
Foo f2 = f1; //拷贝初始化
f2.show(); //hello-10-1
Foo f3(f1); //拷贝初始化,和上面那种类似
f3.show(); //hello-10-2
return 0;
}
Foo(const Foo& foo)
,即函数参数为一个const引用类型。=
定义变量时,即Foo f2 = f1
这种形式,或者是使用对象初始化的形式,即Foo f3(f1)
。从上述可以看出拷贝构造函数用于初始化非引用类型参数,这一特性导致它的参数必须是引用类型,否则将会无限循环了。
class Foo
{
public:
//默认构造函数
Foo() {};
Foo(string str, int num):str(str),num(num) {}
//拷贝赋值,重载等号
Foo& operator=(const Foo& foo)
{
this->str = foo.str;
this->num = foo.num;
return *this;
}
void show()
{
times++;
cout << this->str << "-" << this->num<<"-"<<times<<endl;
}
static int times;
private:
string str;
int num;
};
int Foo::times = 0;
int main()
{
Foo f1("hello",10); //直接初始化
f1.show(); //hello-10-1
Foo f2; //默认初始化
f2 = f1; //使用拷贝赋值运算符
f2.show(); //hello-10-2
return 0;
}
class Foo
{
public:
//默认构造函数
Foo() {};
Foo(string str, int num):str(str),num(num) {}
~Foo()
{
cout << "函数析构了" << endl;
}
private:
string str;
int num;
};
int main()
{
Foo f1("hello",10); //直接初始化
return 0;
}
//执行结果:函数析构了
~类名()
,波浪线与类名组成的,无返回值,不接受参数,故析构函数不能够重载,——一个类的析构函数唯一。class Student
{
public:
Student();
~Student();
private:
int age;
char* name;
};
Student::Student()
{
this->name = new char(20);
this->age = 0;
cout << "Student()" << endl;
}
Student::~Student()
{
cout << "~Student()" <<(int)name<< endl;
delete name;
cout << "Ok" << endl;
name = nullptr;
}
int main()
{
{// 花括号让s1和s2变成局部对象,方便测试
Student s1;
Student s2 = s1;
}//花括号结束,对象析构
cout << "hello" << endl;
return 0;
}
执行结果为引发异常,触发断点。
Student()
~Student()17464200
OK
~Student()17464200
当对一个对象进行拷贝的时候,编译器会自动调用拷贝构造函数
。而默认的拷贝构造函数就是简单的将原对象的成员一一赋值给新对象。这就是所谓的浅拷贝。
对于指针这种类型来说,原拷贝构造函数的动作就像char * a = new char(20)
,然后char* b = a
,也就是说a与b两个指针指向了同一块内存区域。当对象被析构的时候,同一块内存区域就被delete
了两次,因而触发异常。
而这种情况,一般是默认拷贝构造函数无法避免的。
因此对于类的成员含有指针的情况,一定要显示的写出拷贝构造函数,并且特殊针对指针类型进行处理,这就是深拷贝。
class Student
{
public:
Student();
~Student();
Student(const Student& other);
private:
int age;
char* name;
};
Student::Student()
{
this->name = new char(20);
this->age = 0;
cout << "Student()" << endl;
}
Student::Student(const Student& other)
{
this->age = other.age;
this->name = new char(20);
memcpy(this->name,other.name,strlen(other.name));
}
Student::~Student()
{
cout << "~Student()" << (int)name << endl;
delete name;
cout << "Ok" << endl;
name = nullptr;
}
int main()
{
{// 花括号让s1和s2变成局部对象,方便测试
Student s1;
Student s2 = s1;
}
cout << "hello" << endl;
return 0;
}
执行结果:
Student()
~Student()17149496
Ok
~Student()17149832
Ok
hello
参考链接:C++面试题之浅拷贝和深拷贝的区别
总结:浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
再说几句:
当对象中存在指针成员时,除了在复制对象时需要考虑自定义拷贝构造函数,还应该考虑以下两种情形:
1.当函数的参数为对象时,实参传递给形参的实际上是实参的一个拷贝对象,系统自动通过拷贝构造函数实现;
2.当函数的返回值为一个对象时,该对象实际上是函数内对象的一个拷贝,用于返回函数调用处。
3.浅拷贝带来问题的本质在于析构函数释放多次堆内存,使用std::shared_ptr,可以完美解决这个问题。
知乎大佬Milo Yip的回答:如何理解 C++ 中的深拷贝和浅拷贝? - Milo Yip的回答
事实上,所谓的浅拷贝和深拷贝各自代表不同的意义,各有所需。关键是要区分值语意(value semantics)和引用语意(reference semantics)。
对于值语意的对象,在x= y完成复制之后,y的状态改变不能影响到x,这特性称为独立性(independence)。使用深拷贝的方式可以完全复制一个独立于原来的对象。C++提供的(模板)大部分都是值语意的,如stad::basic string、 std:.vector 等。
有些对象会引用相同的对象,举个游戏中的例子。通常多个模型可以引用同一个材质,复制模型时并不会深度复制新一份材质。 如果需要改变个别模型的材质里的参数,才会手动把该模型的材质复制,独立于其他模型的材质。
class Student
{
public:
Student() :age(0) {}
Student(int age) :age(age) {}
~Student() {}
static int conut;
static int getConut()
{
return conut;
}
private:
int age;
static int nums;
};
int Student::conut = 12;
int Student::nums = 10;
int main()
{
Student s;
cout << s.conut << endl;
cout << Student::getConut() << endl;
//cout << s.nums << endl;//错误,无法访问private成员
return 0;
}
this指针是隐含的变量,表示当前对象。实际就是指向了对象的地址。
在类的成员函数,非static函数内,this隐含在其中。
const Class *
,但是this指针不能做++
操作,说明this指针是一个常指针,即Class * const
。const Class * const
这样。class Example
{
public:
Example() :a(10) {}
~Example() {}
Example& copy(const Example& other)
{
this->a = other.a;
return *this;
}//注意函数返回值为引用。
void showInfo()
{
cout << a << endl;
}
private:
int a;
};
class Example
{
public:
Example() :i_nom_n(11) {}
~Example() {}
void showInfo()
{
this->i_nom_n += 100;
cout << i_con_n << " " << i_nom_n << " " << i_sta_n << " " << i_con_sta_n << endl;
}
void showInfo2() const
{
//this->i_nom_n++;//错误,表达式必须是可修改的左值
cout << i_con_n << " " << i_nom_n << " " << i_sta_n << " " << i_con_sta_n << endl;
}
private:
const int i_con_n = 10;//可在此处赋值或者初始值列表进行初始化
int i_nom_n;//可在此处赋值,为默认值
static int i_sta_n;//必须在类外初始化
const static int i_con_sta_n;//可在此处赋值或者类外初始化
};
int Example::i_sta_n = 12;
const int Example::i_con_sta_n = 13;
int main()
{
Example ex;
ex.showInfo2();
ex.showInfo();
return 0;
}
const Class * const
这样。class A
{
public:
A() :a(0), b(0) {}
A(int a, int b) :a(a), b(b) {}
~A() {}
private:
int a;
int b;
friend class B;
friend void friendfun(A an, B bn);
};
class B
{
public:
B() :c(0) {}
B(int c) :c(c) {}
void info(A an)
{
cout << an.a << " " << an.b <<" "<<this->c<< endl;
}
private:
int c;
friend void friendfun(A an, B bn);
};
void friendfun(A an,B bn)
{
cout << an.a << " " << an.b << " " << bn.c << endl;
}
int main()
{
A an(1, 2);
B bn(3);
bn.info(an);
friendfun(an,bn);
return 0;
}
class A
{
public:
A() :a(0), b(0) {}
A(int a, int b) :a(a), b(b) {}
~A(){}
void showInfo()
{
cout << a << " " << b << endl;
}
private:
int a;
int b;
};
int main()
{
A a1(5, 5);
a1.showInfo(); //5 5
cout << sizeof(a1) << endl; //8
A* a2 = new A(5, 5);
a2->showInfo(); //5 5
cout << sizeof(a2) << endl; //4
cout << sizeof(*a2) << endl;//8
delete a2;
return 0;
}
C++创建对象实例有两种方式:
ClassName obj(param);
ClassName * obj = new ClassName(param);
这两种方式还是有较大的区别。
ClassName obj(param);
这种方式创建的对象,内存是分配到栈中的。由编译器默认调用构造与析构函数。ClassName * obj = new ClassName(param);
,这种方式创建的对象位于堆上,new返回的是一个对象指针,这个指针指向一个对象的地址。->
来调用对应的成员。参考链接:C++创建对象的两种方法
未完,等待后续慢慢补充。
本文首发于我的个人博客www.colourso.top
原文:https://www.cnblogs.com/colourso/p/12693078.html