【C++从0到1】C++入门(下篇)类入门篇


前言

类和对象入门最后一篇啦!!!


一、构造函数再理解

1.1构造函数体内赋值

class Date
{
public:

	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
		//_year =1; 这就是重复赋值
	}
};

以日期类为例,我们在定义对象的时候会调用上述构造函数,调用完构造函数之后,对象中的成员变量都有一个初始值,但是不能将这个行为作为构造函数的初始化,构造函数体内的语句只能够称作为赋值,原因是因为:构造函数体内是可以多次给一个成员变量赋值的,而初始化是只有一次的。


1.2初始化列表

看完上面就有一个问题了,那么我们的对象当中的成员变量初始化是在哪里完成的呢? 答案:初始化列表

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
};

上面的代码就是我们在初始化列表当中对成员变量进行初始化,使用方法就是以 :作为开始,成员变量后加入括号,括号内部放初始值或者表达式 初始值可以是传参的变量,表达式也可以是调用一个函数,如下:

class Stack
{
public:
	Stack()
		:a((int*)malloc(sizeof(int)))//这里的初始化!!!!
		,_size(0)
		,_capacity(1)
	{}
private:
	int* a;
	int _size;
	int _capacity;
};
int main()
{
	Stack s1;
	return 0;
}

上述的就是,但是其实我们并不推荐在构造函数内部做有危险的事(malloc空间),因为如果malloc失败之后,这个对象是否存在都是一个不知道的事,所以我们推荐再多一个InitStack()之类的函数
在这里插入图片描述
在这里插入图片描述


1.3 只能在初始化列表初始化的变量

注意:有以下几个成员变量只能在初始化列表当中初始化

1.引用成员变量
2.const成员变量
3.自定义类型成员(该类没有默认构造函数,即不传参没办法创建该对象)

class B
{
public:
	B(int b)
	{
		cout << "B(int b)";
	}
};
class A
{
public:
	A(int a,int b)
	:_a(a)//& 需要一个变量,所以这里不能给常数哟 int& a = a
	,_b(b)//这个可以给变量和常数,const修饰的变量为常变量,const int _b=b
	,_bb(1)//这里可以理解为 B _bb(1)
	{}
private:
	int& _a;//我是引用对象
	const int _b;//我是const对象
	B _bb;//我是没有默认构造函数的对象
};
int main()
{
	A a(1,2);
	return 0;
}

以上就是几种情况的说明。
说明:我们在c++当中,推荐使用用初始化列表对成员变量进行初始化。原因:不管你有没有写,都是会先走一遍初始化列表,自定义对象就是在这个过程调用构造函数的,我们可以把这个地方当做成员变量的定义的地方,用类创建对象则为对象实例化的地方


1.4explicit关键字

这个关键字的作用就是防止我们通过隐式类型转换创建对象。
因为对于单参数的构造函数,带有类型转换的功能。
我们通常在赋值的时候喜欢用一个值给一个对象赋值,编译器在这个时候是会用我们的值先构造一个对象,再用这个构造出来的对象给原对象赋值。
explicit相当于打断了用一个值构造对象这条路!

//}
class B
{
public:
	explicit B(int b)
	{
		cout << "B(int b)";
	}
};

int main()
{
	B b(1);
	B bb = 1;//error
	//若无explicit 则1会变成 B bb = B(1);类似这种形式

	return 0;
}

验证上述是否正确,下述代码进行验证:
实验现象为无法正常调用则成功,由于编译器会将1进行构造后面拷贝构造给bb进行优化成一次构造函数,所以我们将构造函数不允许隐式类型转换,若拦截成功则说明正确。
在这里插入图片描述

class B
{
public:
	B(const B& b)
	{
		cout << "B(const B& b)\n";
	}
	explicit B(int b)
	{
		cout << "B(int b)\n";
	}
};

int main()
{
	B b(1);
	B bb = 1;

	return 0;
}

c++11支持的新语法:

class B
{
public:
	B(const B& b)
	{
		cout << "B(const B& b)\n";
	}
	explicit B(int b,int c)
	{
		cout << "B(int b,int c)\n";
	}
};

int main()
{
	B b(1,2);
	B bb = {1,2};//2个参数的隐式构造,若无explicit则正常运行

	return 0;
}

二、static C/C++

回忆一下C语言当中的static的多种用途

static对象(静态对象)就是生命周期变了!!其他和普通对象一样!!

1.static修饰局部变量,改变了局部变量的生命周期(本质上是改变了变量的存储类型),从栈到静态区里面(全局变量和static修饰的静态变量都在静态区)
2.extern可以声 明外部符号,但被static修饰的全局变量在其他源文件中extern也用不了(实质上是把链接改变了)并不是修改作用域,改变的是链接属性,全局变量具有外部连接属性,static修饰后就变成了内部连接属性,其他源文件就不能连接到这个静态的全局变量了。
3.static修饰函数也是改变链接属性,只能在自己的源文件使用了 static限制了文件域,static成员变量在对象生成之前生成 static可以修饰全局变量,局部变量,函数如果没有被static修饰可以用 extern int Add(int,int);

上面概括一下从全局变量,局部变量,函数来理解static的作用。那么c++同时具备上述特征的同时,又多了以下这些


2.1static成员

static修饰的成员变量称之为静态成员变量;用static修饰的成员函数,称为静态成员函数,注意:静态的成员便来那个一定要在类外进行初始化。原因:静态成员变量不是属于某个具体的对象,是属于类的,所以类内部的构造函数不能对static的成员变量进行初始化。

class B
{
public:
	static int GetStatica()//静态成员函数
	{
		return a;
	}
private:
	static int a;//私有静态成员变量
};
int B::a = 1;

int main()
{
	//cout << B::a;//外部无法访问,访问时需要突破类域和访问限定符
	cout << B::GetStatica();
	return 0;
}

2.2 特性

  1. 静态成员为所有类对象所共享,不属于某个具体的实例
  2. 静态成员变量必须在类外定义定义时不添加static关键字
  3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值,意味着有时候需要突破访问限定符。

三、C++11成员初始化新用法

class Stack
{
public:
	Stack()
	{
		a = nullptr;
		_size = 1;
		_capacity = 1;
	}
private:
	int* a= (int*)malloc(sizeof(int));//在这里进行给缺省参数
	int _size = 0;
	int _capacity = 0;
};
int main()
{
	Stack s;
	
	return 0;
}

在这里插入图片描述
可以看到,在还没有构造函数体内,已经在初始化列表当中进行初始化,当然,构造函数体内可以进行成员变量的再赋值。
再次说明了初始化只有一次,赋值可以多次。在这里插入图片描述


四、友元

友元:友元函数和友元类
友元的作用是突破了封装的方式,但是友元会增加耦合度,突破了封装,不适宜多用,以下给出一个常用的使用场景。
就是一个打印类的运算符重载,放在类内部实现会被cout占住this指针位,放到类外面无法访问类的私有成员变量,友元打破了限定符的作用!!

class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& out,const Date& d)
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}
int main()
{
	Date d1;
	//cout << d1;我们常用的使用,想对一个对象进行它的打印但是这里cout占据了我们的this指针
	//所以我们只能够使用全局operator<<,但是却访问不到私有的成员变量
	//两种解决方案,Date内部提供访问成员变量的函数,第二种就是用我们现在要的友元
	cout << d1;

	return 0;
}

一个函数可以是多个类的友元函数,但是记得前置声明。

class B;
class A;
int Sub(const A& a, const B& b);

class A
{
	//注意把class B的声明放在前面
	friend int Sub(const A& a, const B& b);
private:
	int a=1;
};
class B
{
	friend int Sub(const A& a, const B& b);
private:
	int b=1;
};
int Sub(const A& a,const B& b)
{
	return a.a - b.b;
}
int main()
{
	A a;
	B b;
	cout<<Sub(a,b);
	return 0;
}

1.友元不是类的成员函数,没有this指针,所以也无法给this指针加上const
2.友元可以访问类的保护变量和私有变量和公有变量
3.友元函数可以在类的任意地方声明,不受到类的访问限定符限制
4.一个函数可以是多个类的友元函数
5.友元函数的调用和普通函数的调用相同


五、内部类

当一个类A定义在另一个类B的内部,则将A称之为内部类。注意此时这个内部类是一个独立的类,他并不属于外部类的,但是在外部类可以定义这个类的对象(默认已经突破类域了),但是不能通过定义外部类的对象来调用内部类的成员变量或者函数。
并且B类默认是A类的友元,B可以访问A的所有变量和函数,但是B不能对A有操作。
比如家中的点时工,在你家做卫生工作,他可能会用到你家的拖把,抹布,但是你却不会动他家的东西,内部类就是一个帮外部类干活的这样一个存在。

特性:

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
  3. sizeof(外部类)=外部类,和内部类没有任何关系。

剑指offer64 求1+2+3+…+n
这道题就我们改成有类部类来解
在这里插入图片描述

class Solution {
public:
    private:
    //这个类相当于成员函数
    class A
    {
        public:
        A()
        {
            ret += sub;
            sub++;
        }
      
    };
    public:
    static int sub;
    static int ret;
    
    int Sum_Solution(int n) {
        A a[n];
       return ret;
    }
};
int Solution::sub = 1;
int Solution::ret = 0;

题中,A类是帮我们进行累次加法运算的 工具类,当然只是一个说法,我们可以利用这个类,帮我们Solution类达到1+…+n的操作。其中A类可以直接访问Solution的成员变量。


六、有趣的面试题/知识点

6.1构造函数和拷贝构造在一次过程中连续调用传参的时候会被弄成一次构造函数

返回值立马当作参数传递,当成拷贝构造会优化
或者返回值当作拷贝构造会优化

class Date
{
public:
	//d1(d2)
	Date(const Date& d)
	{
		cout << "Date(const Date& d)\n";
	}

	Date()
	{
		cout << "Date()\n";
	}
private:
};
Date func(Date d)
{
	Date d2(d);
	return d2;
}
int main()
{
	Date d2 = func(Date());
	return 0;
}

在这里插入图片描述
一般人可能就会认为是以上几种,但是我们程序一跑,却要比我们想的少了两个。在这里插入图片描述
原因在这里插入图片描述
注意,func对象return的时候不是用d2return,因为d2再func的栈帧,一般返回值是4/8个字节由寄存器返回,大的就在main栈帧当中开辟一个新的空间。
在这里插入图片描述


6.2 下面函数谁先调用构造函数

class A
{
public:
	A()
	{
		cout << "A()\n";
	}
	~A()
	{
		cout << "~A()\n";
	}
};
class B
{
public:
	B()
	{
		cout << "B()\n";
	}
	~B()
	{
		cout << "~B()\n";
	}
};
class C
{
public:
	C()
	{
		cout << "C()\n";
	}
	~C()
	{
		cout << "~C()\n";
	}
};
class D
{
public:
	D()
	{
		cout << "D()\n";
	}
	~D()
	{
		cout << "~D()\n";
	}
};
class E
{
public:
	E()
	{
		cout << "E()\n";
	}
	~E()
	{
		cout << "~E()\n";
	}
};
void fun()
{
	B b;
	C c;
	static D d;
}
void fun2()
{
	static E e;
}
A aa;
int main()
{
	fun();
	fun2();
	return 0;
}

给定如上的一组函数,你能否快速判断出谁先构造谁先析构呢。
在这里插入图片描述

结论1:先构造的后析构
结论2:可以想象成每个域当中局部变量有一个栈,然后全部域的全局变量和静态变量共享一个栈先构造的后面才被拿出来析构

局部域当中的都会比静态的先析构,定义在静态库当中的也是先定义后析构,如同栈。在这里插入图片描述


总结

下一章节讲述c/c++当中的内存模型,看到这里还不给一个三连吗❤

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

^jhao^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值