概述

这系列的文章我主要是总结一下C++的难点,方便我以后查阅。

访问修饰符 protected和private的区别

刚刚接触类的时候,觉得这个没有区别,但是学习了继承之后才意识到他们的区别

私有(private)成员
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。

保护(protected)成员
保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的

构造函数&析构函数

构造和析构是一对函数,一个负责初始化,一个负责销毁

类的构造函数是初始化对象的特殊函数

主要概括一下几点

  1. 它会在每次创建类的新对象时执行。
  2. 构造函数的名称与类的名称是完全相同的,
  3. 不会返回任何类型,也不会返回 void
  4. 如果你没写构造函数也可以,系统会帮你自动生成一个
  5. 不能用常规的方法调用它(不能显式调用),一般情况下都是你在创建对象的时候系统帮你调用;但是创建对象数组的时候是可以显式调用的;如果你调用时候需要传入参数,那么在创建的时候把参数写到对象后面就好了;例如
classA objectA(<参数1,参数2....>);
  1. 构造函数不能是虚函数
  2. 构造函数不能取他的地址
  3. 构造函数可以被重载和缺省

类的析构函数是删除所创建对象的特殊函数

主要概括一下他和构造函数不同,没提到的都和构造一样

  1. 析构函数名为:类名在前面加了个波浪号(~)。
  2. 自动调用的三种情况,
    • 一个动态分配的对象被删除的时候,即使用delete删除对象时,也会自动调用
    • 程序运行结束时
    • 编译器生成运行的对象不在需要的时候
  3. 析构函数不能重载和带参数,每个类只能有一个析构函数
  4. 析构函数可以是虚函数
  5. 构造函数只能在创建对象数组的时候可以被显示调用,但是析构函数可以用下面的方法手动调用,一般情况下我们不显示调用析构函数,都是系统自动调用
对象名.类名::析构函数名();//析构函数名 ~类名
  1. 在调用顺序上看,先构造函数才能析构函数
  2. 析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

如果你不写构造函数和析构函数,系统会默认 帮你写一个默认构造函数和析构函数

拷贝构造函数

拷贝构造函数的特点

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象。

  • 复制对象把它作为参数传递给函数。

  • 复制对象,并从函数返回这个对象。

如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个自定义的拷贝构造函数(我们叫这类为深拷贝构造函数)。拷贝构造函数的最常见形式如下:

类名(类名 &obj) {
   // 构造函数的主体
}
/*
传入的参数&obj 是对复制对象的引用
 一般可以加一个const来修饰一下 防止传入参数被修改
函数体里写的即是逐个拷贝(非static)数据成员,
*/

系统帮我们调用拷贝构造函数的情况如下:

  1. 用一个类的对象出初始化另一个对象 ,classA obj2(obj1);
  2. 用一个对象去赋值一个对象,classA obj2=obj1;这里即使我们定义了很拷贝,系统还是会调用默认的浅拷贝。值得一提的是如果两个对象是已经存在的,我们在使用=去复制调用的是= 的重载函数。
  3. 传入的参数是对象和返回的值是对象时,会调用

深拷贝和浅拷贝

系统默认提供的是浅拷贝构造函数,赋值后,这两个对象拥有同一个资源空间,但是我们的程序里有指针时,在析构的时候所占用的空间就会被两次返还,出现指针悬空的现象,面对这种情况,我们务必自行写一个自定义的深拷贝构造函数
深拷贝构造函数里面要包

  1. 逐个拷贝(非static)数据成员
  2. 复制对象的空间和资源(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;
}

运行结果1
仔细观察,a,b对象的地址完全一样的,就说明浅拷贝是直接赋值的,连地址也一起给了,没有分配空间;
所以我们务必添加深拷贝

	A(const A&t)
	{
		a = t.a;
		p = new int(*t.p);
	}

添加之后就完全运行了,没的问题,p,b的地址也不一样了,删除p ,b.a的值没有变化;
运行结果2

用构造初始化表的方法去初始化

格式为

<类名>::<构造函数>(<参数列表>):变量名1(<初值1>),变量名2(<初值2>)...
{}//如果你就仅初始化的化 函数体可空,内联构造函数可以不要写类名

实例:

Circle::Circle(float r):radius(r)

this指针

在我目前理解来看,个人认为类就是一个特殊的结构体,成员函数其实在每个对象里都是公用的;如果一个类声明了多个对象,那么成员函如何直到我操作那个对象的数据成员呢,这里就有this指针来登场了
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。所以在写成员函数的时候可以“this->”成员函数来访问数据成员
当然友元函数是没有this指针的,只有成员函数才有的;静态函数也没有。

静态成员 static

静态数据成员

我们可以使用 static 关键字来把类成员定义为静态的。

  1. 当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
  2. 静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零(相当入在这个类的对象里把定义了了个全局变量)。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它赋值,如下:
<数据类型><类名>::<静态数据成员变量名>=<值>;

我们可以通过把静态变量写到构造函数里,统计创建了多少个对象

#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)(参数);

指向类内静态成成员

静态成员由于可以和对象分离,唯一的存在公用的全局中,所以指向这类成员也就简化了,和上述不同的就一点

  1. 可以省去对作用域的描述
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;
} 

本文由 越行勤 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://blog.yxqin.top/2020/10/14/c一类和对象
最后更新于:  311  天前,内容可能已经不具有时效性,请谨慎参考。

Q.E.D.


努力学习的小菜鸟