C/C++的学习
一.总结:
C语言的应用十分广泛、灵活性强,同时它也比较基础,很适合一些刚入门的
小白学习,若有了C语言的基础,再去学习其他的语言也会轻松很多。我也是一名
小白,真正入门技术也才半年的时间,这半年的时间里我学习了很多关于技术方面
的知识,也自己尝试或与他人合作设计完成过一些小的项目(日后也会更新到我的
博客中)......
这段时间,我主要在学习C++,由于C语言的基础部分和C++的极其类似,所以
我在学习C++的同时,相当于复习了一遍C语言。我也重新整理了一份笔记,希望我
也可以给大家带来一点帮助。
笔记中有错误的地方,欢迎指出,欢迎大家讨论!
C++ 面向类和对象 编程思维
类和对象:
C++面向对象有三大特性:封装、继承、多态;
封装:
1.将属性和行为作为一个整体,用来表现生活中的事物;
2.将属性和行为加以权限控制;
封装意义一:在设计类的时候,属性和行为写在一起,一起用来表现事物;
语法:
class 类名
{
访问权限
行为
属性
}
案例:设计一个圆类,并求圆的周长
//案例:设计一个圆类,并求圆的周长
class Circle //圆类
{
public: //访问权限(公共权限)
double caclucateZC() //行为(用函数表示行为),这里的行为是计算周长
{
return 2 * PI * m_r;
}
int m_r; //属性(用变量表示属性),这里的案例只需要半径这个属性
};
int main()
{
Circle C1; //实例化(通过一个类创建一个对象的过程,这里c1就是一个对象)
c1.m_r = 10; //给对象的属性赋值
cout << c1.calculataZC() << endl; //调用对象C1中的行为
}
类中的属性和行为,统一称为成员;属性也被称为成员属性或成员变量;行为也被成为成员函数或成员方法;
封装意义二:类在设计时,可以把属性和行为放在不同权限下,加以控制;
访问权限:
public
:公共权限(成员类内外都可以访问)
protected
:保护权限(成员类内可以访问,成员类外不可以访问;且儿子可以访问父亲中的保护内容)
private
:私有权限(成员类内可以访问,成员类外不可以访问;且儿子不可以访问父亲中的私有内容)
struct
和 class
的唯一区别就是:默认访问权限的不同;
struct
的默认访问权限是:public
公共权限;
class
的默认访问权限是:private
私有权限;
成员属性设为私有的优点:
1.将所有成员属性设为私有,可以自己控制读写权限;
2.对于写权限,我们可以检测数据的有效性;
一般情况下,成员属性被设为私有后,可以在行为那里提供对外的接口;(而行为的权限一般设为:公共权限)
把类分文件编写:.h
放类中行为和属性的声明;.cpp
放类中行为的实现;
//假设有一个点类,属性有横坐标和纵坐标,行为有设置点的横坐标和纵坐标
//未分文见编写前:
class Point
{
public:
void SetX(int X) //设置点的横坐标
{
m_x = X;
}
void SetY(int Y) //设置点的纵坐标
{
m_y = Y;
}
int m_x; //点的横坐标
int m_y; //点的纵坐标
};
//分文件编写后:
//.h文件:放类中行为和属性的声明
class Point
{
public:
void SetX(int X); //设置点的横坐标
void SetY(int Y); //设置点的纵坐标
int m_x; //点的横坐标
int m_y; //点的纵坐标
};
//.cpp文件:放类中行为的实现,同时要加上作用域
void Point::SetX(int X) //设置点的横坐标
{
m_x = X;
}
void Point::SetY(int Y) //设置点的纵坐标
{
m_y = Y;
}
//最后一般会有三个文件:main.cpp、Point.cpp、Point.h,而在main.cpp和Point.cpp中要 #include "Point.h"
对象的初始化和清理:构造函数和析构函数
构造函数和析构函数编译器会自动调用,无需手动调用;这两个函数都是必须实现的函数,如果我们没有提供,编译器会提供一共空实现的构造函数和析构函数;
构造函数语法:
类名()
{
....
}
注意事项:
1.构造函数没有返回值,也不用 void
;
2.构造函数的名称必须与类名相同;
3.构造函数可以有参数,因此可以发生函数重载;
4.程序在创建对象时,会自动调用构造函数,无须手动调用,而且只会在创建对象的时候调用一次;
析构函数语法:
~类名()
{
....
}
注意事项:
1.析构函数没有返回值,也不用 void
;
2.析构函数的名称必须与类名相同,并且在函数名称前面加上符号~;
3.析构函数不可以有参数,因此不可以发生函数重载;
4.程序在对象销毁时,会自动调用析构函数,无须手动调用,而且只会在对象销毁的时候调用一次;
构造函数的分类和调用:
两种分类方式:
1.按参数分:I.无参构造(默认构造);II.有参构造;
2.按类型分:I.普通构造(除了拷贝构造以外的构造函数都可以称为普通构造);II.拷贝构造;
三种调用方式:1.括号法;2.显示法;3.隐式转换法
注意事项:
1.调用无参构造函数的时候,不要加();
2.不要利用拷贝构造函数初始化匿名对象;
//假设已存在一个类Person
//分类方式:无参构造(默认构造)、有参构造、拷贝构造
Person() //无参构造(默认构造)
{
....
}
Person(int a) //有参构造
{
....
}
Person(const Person & p) //拷贝构造,相当于我复制了一份一模一样的Person
{ //将这个传进来的Person(即p)的所有属性拷到我这个Person上来
....
}
//调用方式:括号法、显示法、隐式转换法
//括号法(常用):
Person p1; //调用无参构造(默认构造)
Person p2(10); //调用有参构造
Person p3(p2); //调用拷贝构造
//显示法:
Person p2 = Person(10); //调用有参构造
Person p3 = Person(p2); //调用拷贝构造
//隐式转换法:
Person p2 = 10; //转换成 Person p2 = Person(10);
Person p3 = p2; //转换成 Person p3 = Person(p2);
拷贝构造函数调用时机:
三种情况:
1.使用一个已创建完毕的对象来初始化一个新对象;(最常用的情况)
2.值传递的方式给函数参数传值;
3.以值传递的方式返回局部对象;
构造函数调用规则:
默认情况下,C++编译器至少给一个类添加了三个函数:
1.默认构造函数(无参,函数体为空);
2.默认析构函数(无参,函数体为空);
3.默认拷贝构造函数,对属性进行值拷贝(这里默认提供的拷贝构造函数做的是浅拷贝);
调用规则:
1.如果用户定义了有参构造函数,则编译器不再提供默认构造函数,但是还是会提供默认拷贝构造函数;
2.如果用户定义了拷贝构造函数,则编译器不再提供其他构造函数;
3.析构函数与构造函数无关,编译器仍然会提供默认的析构函数(除非自己写了新的析构函数);
总结:如果按照 无参构造->有参构造->拷贝构造 的顺序从上往下看,则我们提供了其中某个位置的构造函数,那它上面的函数编译器不再提供,下面的函数编译器还是会提供;
深拷贝与浅拷贝(面试经典问题)
浅拷贝:简单的赋值拷贝操作(编译器给我们做的简单拷贝工作);
深拷贝:在堆区重新申请空间,进行拷贝操作;
析构代码,作用就是将堆区开辟的数据做释放操作,而浅拷贝带来的问题就是堆区的内存重复释放,浅拷贝带来的问题需要靠深拷贝来解决;如果有属性在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题;
//假设有一个人类,他的属性有年龄和身高
class Person
{
public:
Person() //无参构造
{
cout << "Person 无参构造函数的调用" << endl;
}
Person(int age,int height) //有参构造
{
cout << "Person 有参构造函数的调用" << endl;
m_Age = age;
m_Heigjt = new int(height);
}
Person(const Person & p) //拷贝构造
{
cout << "Person 拷贝构造函数的调用" << endl;
m_Age = p.m_Age;
m_Height = p.m_Height; //浅拷贝(编译器给我们做的简单拷贝工作)
m_Heigjt = new int(*p.m_Height); //深拷贝
}
~Person() //析构函数,将堆区开辟的数据释放
{
if(m_Height!=NULL)
{
delete m_Height;
m_Height = NULL;
}
}
int m_Age;
int * m_Height;
};
void test_1() //测试函数
{
Person p1(18,175);
Person p2(p1);
}
根据上面的代码,如果我们在 main
函数里调用了测试函数,它会创建两个对象(创建第一个对象时,调用有参构造;创建第二个对象时,调用拷贝构造),且这两个对象都是存放在栈区的。当我们这个测试函数的所有代码执行完毕后,编译器会自动帮我们释放这两个对象,但释放对象时,会调用析构函数,如果我们没有自己写拷贝构造函数,编译器做的是浅拷贝,即 p1
里面所有的属性都只是做一个简单复制到 p2
上,但 m_Height
是一个指针,存储的是栈区某一块内存的地址(假设是 0x0011
这个位置),复制到 p2
上,p2
的 m_Height
指针也指向了相同的地方(0x0011
),调用了析构函数的话,实际上对同一块内存做了两次释放的操作(p1
一次、p2
一次),这样的操作是非法的,这也就是浅拷贝带来的问题。
初始化列表:C++提供了初始化列表语法,可以用来初始化属性;
语法:
构造函数的名称() : 属性1(值1) , 属性2(值2) , ....
{
....
}
假设有一个人类,属性有三个:int m_A; int m_B; int m_C;
//假设有一个人类,属性有三个:int m_A; int m_B; int m_C;
Person() : m_A(10) , m_B(20) , m_C(30)
{
.... //这样子写初始化列表,相当于写死了,m_A、m_B、m_C,只能分别等于10、20、30
}
Person(int a,int b,int c) : m_A(a) , m_B(b) , m_C(c)
{
.... //这样子写初始化列表更灵活,a、b、c可以后期传入,达到用不同的值初始化m_A、m_B、m_C
}
C++还在学习中O(∩_∩)O,之后还会更新笔记!