概述
这系列的文章我主要是总结一下C++的难点,方便我以后查阅。
访问修饰符 protected和private的区别
刚刚接触类的时候,觉得这个没有区别,但是学习了继承之后才意识到他们的区别
私有(private)成员
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
保护(protected)成员
保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的
构造函数&析构函数
构造和析构是一对函数,一个负责初始化,一个负责销毁
类的构造函数是初始化对象的特殊函数
主要概括一下几点
- 它会在每次创建类的新对象时执行。
- 构造函数的名称与类的名称是完全相同的,
- 不会返回任何类型,也不会返回 void
- 如果你没写构造函数也可以,系统会帮你自动生成一个
- 不能用常规的方法调用它(不能显式调用),一般情况下都是你在创建对象的时候系统帮你调用;但是创建对象数组的时候是可以显式调用的;如果你调用时候需要传入参数,那么在创建的时候把参数写到对象后面就好了;例如
classA objectA(<参数1,参数2....>);
- 构造函数不能是虚函数
- 构造函数不能取他的地址
- 构造函数可以被重载和缺省
类的析构函数是删除所创建对象的特殊函数
主要概括一下他和构造函数不同,没提到的都和构造一样
- 析构函数名为:类名在前面加了个波浪号(~)。
- 自动调用的三种情况,
- 一个动态分配的对象被删除的时候,即使用delete删除对象时,也会自动调用
- 程序运行结束时
- 编译器生成运行的对象不在需要的时候
- 析构函数不能重载和带参数,每个类只能有一个析构函数
- 析构函数可以是虚函数
- 构造函数只能在创建对象数组的时候可以被显示调用,但是析构函数可以用下面的方法手动调用,一般情况下我们不显示调用析构函数,都是系统自动调用
对象名.类名::析构函数名();//析构函数名 ~类名
- 在调用顺序上看,先构造函数才能析构函数
- 析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
如果你不写构造函数和析构函数,系统会默认 帮你写一个默认构造函数和析构函数
拷贝构造函数
拷贝构造函数的特点
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
-
通过使用另一个同类型的对象来初始化新创建的对象。
-
复制对象把它作为参数传递给函数。
-
复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个自定义的拷贝构造函数(我们叫这类为深拷贝构造函数)。拷贝构造函数的最常见形式如下:
类名(类名 &obj) {
// 构造函数的主体
}
/*
传入的参数&obj 是对复制对象的引用
一般可以加一个const来修饰一下 防止传入参数被修改
函数体里写的即是逐个拷贝(非static)数据成员,
*/
系统帮我们调用拷贝构造函数的情况如下:
- 用一个类的对象出初始化另一个对象 ,classA obj2(obj1);
- 用一个对象去赋值一个
新
对象,classA obj2=obj1;这里即使我们定义了很拷贝,系统还是会调用默认的浅拷贝。值得一提的是如果两个对象是已经存在的,我们在使用=
去复制调用的是= 的重载函数。 - 传入的参数是对象和返回的值是对象时,会调用
深拷贝和浅拷贝
系统默认提供的是浅拷贝构造函数,赋值后,这两个对象拥有同一个资源空间,但是我们的程序里有指针时,在析构的时候所占用的空间就会被两次返还,出现指针悬空的现象,面对这种情况,我们务必自行写一个自定义的深拷贝构造函数
深拷贝构造函数里面要包
- 逐个拷贝(非static)数据成员
- 复制对象的空间和资源(new)
下面的例子就是浅拷贝 我直接销毁了对象a,由于出现指针悬空现象,再去输出b对象,我们发现,b的a就为零了,地址还是那个地址,证明浅拷贝对指针的操作仅仅就是创建了个新的指针,而且还是指向拷贝目标的资源(复制原指针的值而已);
#include<iostream>
class A
{
int a;
int* p;
public:
A(int a)
{
a = a;
p = new int(a);
}
~A() { delete p; }
void show()
{
std::cout << "a= " << a << std::endl;
std::cout << "*p= " << *p << std::endl;
std::cout << "p= " << p << std::endl;
}
};
int main()
{
A *p =new A(5);
A b(*p);//拷贝p
p->show();
std::cout << "-----" << std::endl;
b.show();
std::cout<<"delete后"<< std::endl;
delete p;//我直接删除了p
b.show();
std::cin.get();
return 0;
}
仔细观察,a,b对象的地址完全一样的,就说明浅拷贝是直接赋值的,连地址也一起给了,没有分配空间;
所以我们务必添加深拷贝
A(const A&t)
{
a = t.a;
p = new int(*t.p);
}
添加之后就完全运行了,没的问题,p,b的地址也不一样了,删除p ,b.a的值没有变化;
用构造初始化表的方法去初始化
格式为
<类名>::<构造函数>(<参数列表>):变量名1(<初值1>),变量名2(<初值2>)...
{}//如果你就仅初始化的化 函数体可空,内联构造函数可以不要写类名
实例:
Circle::Circle(float r):radius(r)
this指针
在我目前理解来看,个人认为类就是一个特殊的结构体,成员函数其实在每个对象里都是公用的;如果一个类声明了多个对象,那么成员函如何直到我操作那个对象的数据成员呢,这里就有this指针来登场了
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。所以在写成员函数的时候可以“this->”成员函数来访问数据成员
当然友元函数是没有this指针的,只有成员函数才有的;静态函数也没有。
静态成员 static
静态数据成员
我们可以使用 static 关键字来把类成员定义为静态的。
- 当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
- 静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零(相当入在这个类的对象里把定义了了个全局变量)。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它赋值,如下:
<数据类型><类名>::<静态数据成员变量名>=<值>;
我们可以通过把静态变量写到构造函数里,统计创建了多少个对象
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 输出对象的总数
cout << "Total objects: " << Box::objectCount << endl;
return 0;
}
静态函数的特性
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。
1.静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
2. 静态成员函数有一个类范围,他们不能访问类的 this 指针。
3. 由于静态函数没有this指针,静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。 ,当然你要访问非静态的数据成员,可以传递这个对象的引用。
4. 静态函数不能是虚函数
类外指向类内成员的指针
我们可以直接在类外声明一个指针指向类内的成员,当然这个无法破坏类的封装性,我们只能指向具有公共属性的成员哦;这里感觉声明有点绕(主要是考虑优先级,* 的优先级要高一些,而且是向右结合) ,特此,做一下笔记。
指向数据成员
//类型符 类名::*指针名=对类数据成员的描述
//例如
class A
{
public:
int i,*p;
A(){i=10;p=&i;}
};
int A::*p=&A::i;//想这样的复杂一些声明都是先 类型符 指明作用域
void main()
{
A aa,bb;
(bb.*p)++;//* 比++ 优先级高 要加(),这是类外指针来访问
-- *aa.p;//类内的指针在用的时候把 对象名.指针名看出一个整体
}
最后结果 aa.i=11,bb.i=11;
指向成员函数
类型符 (类名::*指针名)(参数列表)=&类名::函数成员;//()是为了优先级
//调用的时候和上面差不多 记得要把指针解引用完后加()
//如 :
(类名.*p)(参数);
指向类内静态成成员
静态成员由于可以和对象分离,唯一的存在公用的全局中,所以指向这类成员也就简化了,和上述不同的就一点
- 可以省去对作用域的描述
class A
{
public:
static viod set(int k)(....);
};
void (*f)(int)=&A::set;//这里省去了第一个描述作用域的::
void main()
{
(*f)(6);
}
类的聚集
一句话概括一下:
一个类的对象可以做为另一个类的数据成员
因此会产生构造和析构的问题,所以要特别注意一下
- 我们需要在写构造函数的时候,参数不要忘记了给包含对象初始化(我们可以调用他的类的构造函数,当然也是隐式调用)
- 构造函数调用顺序是先客人,再自己,析构与之相反;
下面写几个应用
student
#include<string>
#include<iostream>
class Student
{
private:
char* name;
int reg_num;
float math,eng,computer;
public:
Student(const char* n = "noname", int r = 0,float m = 0,float e = 0,float c = 0)
: reg_num(r), math(m), eng(e), computer(c)
{
name = new char[strlen(n)+1];
strcpy(name,n);
}
float sum();
float average();
void printf();
int get_reg_num();
void set_stu_inf();
};
float Student::sum()
{
return math + eng + computer;
}
float Student::average()
{
return (math + eng + computer)/ 3;
}
void Student::printf()
{
std::cout << "学号为 "<<reg_num <<std::endl;
std::cout << "姓名为 " << name << std::endl;
std::cout << "数学成绩" << math << std::endl;
std::cout << "英语成绩" << eng << std::endl;
std::cout << "计算机成绩" << computer<< std::endl;
std::cout << "总成绩 " << sum() << std::endl;
std::cout << "平均分" << average()<< std::endl;
}
int Student::get_reg_num()
{
return reg_num;
}
void Student::set_stu_inf()
{
char buf[50];
std::cout << "请输入学号\n";
std::cin >> reg_num;
std::cout << "请输入姓名\n";
std::cin >>buf;
std::cout << "请输入数学成绩\n";
std::cin >> math;
std::cout << "请输入英语成绩\n";
std::cin >> eng;
std::cout << "计算机成绩\n";
std::cin >> computer;
delete[] name;
name = new char[strlen(buf) + 1];
strcpy(name,buf);
}
int main()
{
int n=0;
std::cout << "请输入班上人数n:";
std::cin >> n;
//Student a[n];
Student* b;
b = new Student[n];
for (int i = 0; i < n; i++)
{
std::cout << "请输入第" << i + 1 << "学生的信息" << std::endl;
b[i].set_stu_inf();
}
int max=0;
float sum=0;
for (int i = 0; i < n; i++)
{
std::cout << "第" << i+1 << "学生的信息为" << std::endl;
b[i].printf();
if(b[max].sum()<b[i].sum())
max = i;
sum = b[i].average() + sum;
}
std::cout << "班上最高总分为" << b[max].sum()<<std::endl;
std::cout << "班上平均为" << sum / n<<std::endl;
char ch;
while(1)
{
int i,s;
std::cout << "请输入要查询的学生的学号";
std::cin >> s;
for ( i = 0; i < n; i++)
{
if (s == b[i].get_reg_num())
b[i].printf();
}
if(i==n)
{std::cout << "查询的学生不存在"<<std::endl;}
std::cout << "如果继续查询请输入 y"<<std::endl;
std::cin>>ch;
if(!(ch=='y'))
break;
}
std::cin.get();
return 0;
}