从c开始学c++
博主是一个大一刚刚放暑假的大学生,大学我们只学习了c语言,现在这么卷只学c语言肯定不够,所以博主打算从零开始恶补c++顺便写文章记录一下我是如何从c过渡到c++的,另外博主这个暑假还想记录一些算法基础内容欢迎关注哦。
别跑!!!下面有贴心的全文目录!!!想要md版本笔记的私信我
- 1.学习基础的c++语法
- 2.掌握c++的面向对象编程的思维
- 3.记录一些从c到c++的快速过渡方法
- 4.掌握vs code编辑代码和使用md记笔记
那么现在我们开始我们的从c到c++的过渡之旅吧!
Day 1.快速学习c++的基本语言知识和框架
vscode的c++/c语言的环境配置
因为小白之前从来没有接触过vscode所以要重新配置c++环境
这里建议参考这个博主的文章:(40条消息) VsCode安装和配置c/c++环境(超完整,小白专用)_黄化的多多的博客-优快云博客_vscode配置c/c++环境十分的nice,跟着配置好了我们就可以开始学习c++了!!!因为vscode以后要用所以提前用用也好~~
头文件方面:
万能头文件
#include <bits/stdc++.h>//万能头文件(狂喜)
会了上面那一个下面都不用记
#include <iostream.h>//cin,cout文件
#include <algorihm.h>//算法函数文件
#include <math.h>//数学函数文件
#include <string.h>//字符串文件
#include<list>// 链表
#include<map>//图
#include<queue>//队列
#include<vector>//迭代器
#include<stack>//栈
//注意有一些不可以加.h如果头文件报错可以尝试去掉.h
注意有一些不可以加.h如果头文件报错可以尝试去掉.h
世界上没有全是优点的东西,如果这个万能的头文件真的可以成为一个无敌的存在那怎么会存在下面的那些东西呢???对吧所以下面我们来讲讲他的优缺点:
万能头文件的优缺点:
优点:
1、在竞赛中节约时间
2、减少了编写所有必要头文件的工作量
3、对于使用的每个函数,不用记住GNU C++的所有STL
缺点:
1、不属于GNU C++库的标准头文件,在部分情况下可能会失败
2、使用它将包含许多不必要的东西,并增加编译时间
3、这个头文件不是C++标准的一部分,因此是不可移植的,应该避免
4、编译器每次编译翻译单元时都必须实际读取和分析每个包含的头文件,应该避免
c++的基础语法:(类比c语言快速记忆)
首先写一个c++程序:
#include <bits/stdc++.h>
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
作为已经学习完c语言的我们来对比的看看那些是我们不会的东西
using namespace std
输入输出方式
回车
命名空间:namespace
这个命名空间就是一个空间里面可以定义一些例如int char等变量的地方,当我们自己定义一个命名空间的时候我们就可以调用这个空间里定义的变量
例如
#include <iostream>
namespace core
{
int rand = 2;
int a=1,b=2;
int add(int a,int b)
{
return a+b;
}
}
using namespace core;
using namespace std;
int main()
{
cout<<add(a,b)<<endl;
}
这个时候我们可以发现我们在main函数里面并没有定义a,b变量但我们使用了这个语句:
using namespace core;
我么就可以使用上面 namespace core{} 中定义的函数和变量
注意 :这里我们在使用core 空间中定义的变量的时候我们要使用标准空间std这里面定义着cout等关,不然就会出现下面的报错
E:\c++\c++3.cpp|16|error: ‘cout’ was not declared in this scope|
E:\c++\c++3.cpp|16|error: ‘endl’ was not declared in this scope|因为这样他就不知道cout是什么意思了
另一种使用命名空间变量的方法:
using core::rand;
这样可以引用core空间的rand元素而不是把所有元素都引入了!可以避免命名的污染
c++中的输入输出
我们都知道在c语言里面我们常常使用:
int a;
scanf("%d",&a);
printf("%d",a);
来进行输入输出,但是在c++里面爽的来了~~
我们不需要记忆什么%d,%lf什么的我们只需要cin,cout.就可以了。
介绍cin,cout(理论可跳过
cin和cout简介
cin – 标准输入流对象。 cout – 标准输出流对象。
大白话就是:cin是和输入有关的对象,cout是和输出有关的对象。
然后上面在c语言中的那句话在c++中应该是
int a;
cin >> a; // 输入a
cout << a;// 输出a
-
转义字符 含义 ASCll码值 \a 警报 7 \b 退格 8 \f 换页 12 \n 换行 10 \r 回车 13 \t 水平制表符 9 \v 垂直制表符 11 \ \ 92 \’ ’ 39 " " 34 ? ? 63 \0 0 0
在c语言中的一些转移符也可以在这个c++中使用
例如可以这样用
#include <iostream>
using namespace std;
int main()
{
cout<<"the\n";
cout<<"the\n";
}
或者可以这样用
#include <iostream>
using namespace std;
int main()
{
cout<<"the"<<'\n';
cout<<"the"<<'\n';
}
这两个的输出结果都是一样的也可以改成<<endl,一般我们都用<<endl来表示回车
这样cin和cout的使用就不需要%d,%lf,之类的了~
注意:cout可以输出字符型数组但其他的数组不行
例如:字符数组是可以用这种方式输出的,因为数组名即代表首地址,数组是一块连续的内存空间,所以能直接输出.如果是其他类型的数组则输出的是一个地址.根本区别在于没有终止符,因此其他数组要是直接能cout就无法停止下来。
int ab[10] = {1,2,3,6,7}; char a[6] = {'h','a','p','p','y','\0'}; char ac[] = "happy"; cout<<ac<<endl; cout<<ac<<endl; cout<<ab<<endl;
然后一个写两个数的加法就可以这样写:
#include <iostream>
using namespace std;
int main()
{
int a,b;
cin>>a>>b;
cout<<a+b;
}
各种c++的输入输出:
#include<iostream>
using namespace std;
int main()
{
int a = 1;
float b = 2.1;
double c= 2.111;
char arr[10] = { 0 };
char d[] = "hello world";
cin >> arr;
cout << arr << endl;
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << d << endl;
return 0;
}
注意:
这里我们还要注意下cin的特点,他和C语言中的gets有些像,gets是遇到换行符停止,而cin是以遇到空格,tab或者换行符作为分隔符的,hello world会被空格符分隔开来。cout出来就是hello。
c与c++的不同之缺省函数
先来一段代码:
#include<iostream>
using namespace std;
void func(int a = 0)
{
cout << a << endl;
}
int main()
{
func(5);
func();
return 0;
}
来看看这两个func()分别的结果是啥:
答案是5和0
这就要说到缺省函数了,如果你没有传入这个变量的话你就可以在定义函数的时候写上
void func(int a = 0)
这样你不传入变量的话就是a=0了~~~~
这就是所谓的缺省函数
半缺省参数
void func(int a, int b, int c = 2)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
注意:
1、半缺省参数必须从右往左依次给出,不能间隔着给。
//错误示例之间隔着给
void func(int a, int b = 2, int c)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
2、缺省参数不能在函数声明和定义中同时出现
//错误示例
void func(int a, int b, int c = 3);
void func(int a, int b, int c = 2)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
因为:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用哪个缺省值。
3、缺省值必须是常量或者全局变量。
//正确示例之可以使用全局变量
int x = 3;//全局变量
void func(int a, int b = 2, int c = x)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
函数重载
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
这是在c语言中不支持的!!!
#include <iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
int main()
{
cout << Add(0,1) << endl;//打印0+1的结果
cout << Add(1.1,2.2) << endl;//打印1.1+2.2的结果
return 0;
}
注意:若仅仅只有返回值不同,其他都相同,则不构成函数重载。例如:
short Add(short left, short right)
{
return left+right;
}
int Add(short left, short right)
{
return left+right;
}
这种函数重载的使用可以解决不同类型的数据的传入问题~~
引用:& 引用变量名(对象名) = 引用实体;
也就是起别名不是像指针一样指向一个地址然后来修改这个数的值啊什么的这个东西就是起一个小名,当你叫他的时候他就过来了~~编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
#include<iostream>
using namespace std;
int main()
{
int a = 2;
int &b = a; //相当于给a起了一个别名为b
cout << a << endl;//输出结果:2
cout << b << endl;//输出结果:2
b = 3; //改变b也就相当于改变了a
cout << b << endl;//输出结果:3
cout << a << endl;//输出结果:3
}
你妈妈喊你小名的时候你要过来喊你大名的时候你也要过来,&b就是相当于你的小名让你干啥你不干啥你不作死
注意
1.引用在定义时必须初始化及int &b=a;
2.一个变量可以有多个引用
3.引用一旦引用了一个实体,就不能再引用其他实体
3.的错误示例:
int a = 10;
int& b = a;
int c = 20;
vector容器的使用(相当于数组)
[vector的简单使用](https://blog.youkuaiyun.com/xiaopikadi/article/details/108991180?ops_request_misc=%7B%22request%5Fid%22%3A%22165839381116782350834928%22%2C%22scm%22%3A%2220140713.130102334…%22%7D&request_id=165839381116782350834928&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_click~default-1-108991180-null-null.142v33new_blog_pos_by_title,185v2control&utm_term= vector<>&spm=1018.2226.3001.4187)
异或
定义:异或:位运算符,两数相"异",则为真(1)。简单的描述其作用就是0 ^ 1=1,0 ^ 0=0,1 ^ 1=0。参加运算的两个二进制位为同号,则结果为0,异号则为1。
数学定义:
异或运算一般指异或。异或(xor)是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。其运算法则为:
a⊕b=(¬a∧b)∨(a∧¬b)
使用异或来进行两个数的值的交换:
int a;
int b;
a = a ^ b;
b = a ^ b;
a = a ^ b
注意:这里的异或的两个值是不可以相等的所以不建议这样写。
异或相关题:
怎么找这两种数???
第一题:
int t=0;
for(int i=0;i<n;i++)
{
t=t^array[i];
}
cout<<t<<endl;
第二题:
int t=0;
for(int i=0;i<n;i++)
{
t=t^array[i];
}
//最后的t=一种数^另一种数
int rightone = t & (~t + 1);//提取t的第一位1
int t1 = 0;
for(int i=0;i<n;i++)
{
if(rightone & array[i] == 0)//也可以是rightone但不能是1
{
t1^=array[i];
}
}
cout<<t1<<" "<<t1^t<<endl//t1就是两个数中的一个数
分析:
关系运算符的重载(比较器)
关系运算符用于比较两个数据的大小,关系运算的结果是布尔值。
C++语言中有六个关系运算符:>、>一、<、<一、=一、!一,这六个关系运算符都可以进行重载。
在具体应用中、关系运算符都要成对重载。例如,重载“>=”运算符,同时应该重载“<一“心算符。
当成对重载关系运算衬时,可以把一个运算符的比较工作委托给另外一个已经实现的运算、如,已经重载了“>=“运算符,那么在重载“<=”运算符时直接调用“>=”运算符重载函数即可。
关系运算的结果是布尔值(如排序,)
题目描述:
设计一个钟表类Clock,具有hour、minute和second三个私有数据成员,具有相应的构造函数和设置时间的函数setTime和显示时间的函数showTime。重载运算符”>”和”<”为成员函数,以实现比较两个钟表对象时间的大小(时间早的定义为小);
#include<iostream>
using namespace std;
class Clock
{
public:
Clock(int h = 0, int m = 0, int s = 0)
{
hour = h; minute = m; second = s;
}
void setTime(int h, int m, int s);
void showTime();
bool operator >( Clock&);
bool operator <( Clock&);
private:
int hour;
int minute;
int second;
};
void Clock::setTime(int h, int m, int s)
{
hour = h;
minute = m;
second = s;
}
void Clock::showTime()
{
cout << "现在时间:" << hour << ":" << minute << ":" << second << endl;
}
bool Clock::operator>( Clock& c)
{
if (this->hour > c.hour)
return this->hour > c.hour;
else if (this->hour == c.hour)
return this->minute > c.minute;
else if (this->hour == c.hour && this->minute == c.minute)
return this->second > c.second;
else
return false;//切记,如果都不符合则返回false(即 c大)
}
bool Clock::operator<( Clock& c)
{
return c > *this;//直接利用重载的大于运算符;
}
int main()
{
Clock c(19, 58, 23),c1;
c.showTime();
int h, m, s;
cout << "请输入 小时,分,秒:";
while (cin >> h >> m >> s)
{
c1.setTime(h, m, s);
if (c > c1)
cout << "c大" << endl;
if (c < c1)
cout << "c1大" << endl;
}
return 0;
}
比较器
比较器:c++中的重载比较运算符
通过控制返回值来控制哪个参数在前!
负数第一个在前,正数第二个在前,0无所谓
C++中new的使用:
定义:
new其实就是告诉计算机开辟一段新的空间,但是和一般的声明不同的是,new开辟的空间在堆上,而一般声明的变量存放在栈上。
作用:
当在局部函数中new出一段新的空间,该段空间在局部函数调用结束后仍然能够使用,可以用来向主函数传递参数。
new的使用格式:
new出来的是一段空间的首地址。所以一般需要用指针来存放这段地址。
运算符的重载
概念:对已有运算符进行定义赋予其另一种功能
加号运算符重载
对于对象的话他就不知道如何进行相加运算当然我们也可以通过成员函数来实现两个对象的属性相加返回新的对象
如果不用这种成员函数的方法怎么搞呢??
编译器给起了一个通用的名称operator+
只要使用这个函数名来写一个函数的话我们就可以直接使用+
了
#include<iostream>
class person
{
public:
int m_a;
int m_b;
person operator+ (person &p)
{
person temp;
temp.m_a = m_a + p.m_a;
temp.m_b = m_b + p.m_b;
return temp;
}
};
//或者可以通过全局函数
person operator+ (person &p1, person &p2)
{
person temp;
temp.m_a = p1.m_a + p2.m_a;
temp.m_b = p1.m_b + p2.m_b;
return temp;
}
这样的话我们就可以直接调用加号来实现两个元素的属性的相加了
这里就相当于
p1+p2
如果用成员函数写的话就相当于p1.operator+(p2)
p1+p2
如果用全局函数写的话就相当于operator+(p1,p2)
这里这两种写法都可以被系统简化了
而像桶排序里面的比较也是用的这种运算符的重载
左移运算符重载(对象属性的直接打印):
通常不会利用cout来调用左移运算符只能利用全局函数来重载左移运算符
#include<iostream>
using namespace std;
class person
{
public:
int m_a;
int m_b;
};
//左移运算符的重载
ostream& operator<< (ostream &cout , person &p)
{
cout << p.m_a << p.m_b;
return cout;
}
int main()
{
person p;
p.m_a = 10;
p.m_b = 11;
cout << p << endl<<p;
system("pause");
}
这里要注意的是我们的cout是一个ostream
的类型(输出流),然后我们不可以使用成员函数来进行重载因为就相当于p在左边了就不好用了
c++面向对象编程
C++面向对象编程的特性:封装、继承、多态。
封装
意义:
1.将属性和行为作为一个整体来表现生活中的事物
2.将属性和行为设以权限加以控制
C++中的类
在C语言中我还没有学到过关于类的定义当然好像也可以通过class来定义类的但是我还没学,,,,所以我们来直接学习c++的类(他和c语言好像是一样的!)把这个c++的类是我在学习二叉树数据结构的时候发现的我本来想用类似于链表的结构体的定义方式也就是用结构体和结构体指针来定义但是突然发现别人都是用类
类也是十分重要的东西是c++的灵魂学c++学的就是类!别的可以不会但是类不能不会
**对象:**万事万物都是对象!
例如:设计一个圆类,求圆的周长?
#include<iostream>
using namespace std;
const double PI = 3.14;
//圆类
class circle//就像结构体的定义 circle 为名称就像结构体一样可以自定义
{
//访问权限
public://公共权限
//属性
int circle_r;//圆的半径
//行为
double get_c ()//获取圆的周长
{
return 2 * PI * circle_r;
}
};
int main(){
//通过圆类来创建一个具体的圆、这个圆就是对象
circle cl;//创建圆这个对象cl为对象名可自定义
cin>>cl.circle_r;//和结构体一样我们可以通过.的方式来访问这个圆中的东西
cout<<"圆的周长为"<<cl.get_c()<<endl;
}
废话:
类中的行为:称为成员函数或者成员行为(就是那个函数)
类中的属性:称为成员属性或者成员方法(就是那个定义的那几个变量)
是不是感觉和结构体十分的相似,不同点就是这个里面可以创建函数也就是>行为然后在主函数中我们不能直接调用要进行创造对象的操作才能访问类中>的内容!这就是面向对象变成的第一步
结构体和类的区别:
1.struct 默认权限为公有 class 默认权限为私有
2.struct可以包含成员函数但我们经常不这么做
3.不包含访问修饰符
点我去看看更详细的
访问权限:
1.public 公共权限 成员类内外都可以访问
2.protected 保护权限 类内可以访问,外不可以 儿子可访问保护内容
3.private 私有权限 类内可以访问,外不可以 儿子不可访问·私有内容
class core{
public:
//***
private:
//***
protected:
//***
}
成员属性设置为私有好处
优点1: 可以自己控制私有权限
优点2: 对于写权限可以检测数据的有效性
人话:
优点1,说的就是我们建立一个private的属性里面的成员我们可以在类中再建立一个public属性以便于外界可以访问到这个属性而属性的读写权限可以在public中我们规定
优点2,说的就是我们可以在public中定义行为来检测数据是否越界。比如你让他输入一个年龄的话,我们就可以判断他是否输了个1000岁!
例题代码:
#include<iostream>
using namespace std;
class Person {
public:
//姓名设置可读可写
void setName(string name) {
m_Name = name;
}
string getName()
{
return m_Name;
}
//获取年龄
int getAge() {
return m_Age;
}
//设置年龄
void setAge(int age) {
if (age < 0 || age > 150) {
cout << "你个老妖精!" << endl;
return;
}
m_Age = age;
}
//情人设置为只写
void setLover(string lover) {
m_Lover = lover;
}
// 属性
private:
string m_Name; //可读可写 姓名
int m_Age; //只读 年龄
string m_Lover; //只写 爱人
};
int main() {
Person p;
//姓名设置
p.setName("张三");
cout << "姓名: " << p.getName() << endl;
//年龄设置
p.setAge(50);
cout << "年龄: " << p.getAge() << endl;
//情人设置
p.setLover("苍井");
//cout << "情人: " << p.m_Lover << endl; //只写属性,不可以读取
return 0;
}
对象的初始化和清理:
介绍:
对象的初始化和清理也是两个非常重要的安全问题 一个对象或者变量没有初始状态,对其使用后果是未知同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供,编译器提供的构造函数和析构函数是空实现(啥也没有)。
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数 类名(){}
1.没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造函数,无须手动调用,而且只会调用一次
析构函数 ~类名(){}
1.没有返回值也不写void
2.函数名称与类名相同,在名称前加上符号~
3.析构函数不可以有参数,因此不可以发生重载
4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
构造函数的分类和调用:
两种分类方式:
按参数分为:有参和无参构造。 类名(int a)就是有参
按类型分为:普通和拷贝构造。 类名(const core &p)就是拷贝
三种调用方式:
括号法、显示法、隐式转换法
代码+解释:
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
// 将传入的人身上的所有属性,拷贝到我身上
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
//2、构造函数的调用
//调用无参构造函数
void test01() {
Person p; //调用无参构造函数
}
//调用有参的构造函数
void test02() {
//2.1 括号法,常用
Person p1(10);
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
//Person p2();
//2.2 显式法
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10)单独写就是匿名对象 当前行结束之后,马上析构
//2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
//注意2:不能利用 拷贝构造函数初始化匿名对象编译器认为是对象声明
//Person p5(p4);
}
int main() {
test01();
test02();
return 0;
}
什么时候调用拷贝构造函数?
1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式给函数参数传值
3.以值方式返回局部对象
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person man(100); //p对象已经创建完毕
Person newman(man); //调用拷贝构造函数 最常用的使用实际
//Person newman3;
//newman3 = man; //不是调用拷贝构造函数,赋值操作
}
//2. 值传递的方式给函数参数传值
void doWork(Person p1) {}//这里的p不会改变不过是令p1=p来调用的
void test02() {
Person p; //无参构造函数
doWork(p);//相当于Person p1 = p;来调用拷贝构造函数
}
//3. 以值方式返回局部对象
void doWork2()
{
Person p1;
cout << (int *)&p1 << endl;
return p1;//这里是返回的值不是p1对象
}
void test03(){
Person p = doWork2();
cout <<(int*)&p << endl;
}
c++给你提供的默认函数的规则:
提供构造析构和拷贝构造
你写了构造就不给你提供默认构造,但好心的c++还会给你提供拷贝构造函数
你写了拷贝构造函数c++就啥也不管你了!
深拷贝和浅拷贝(坑)
深拷贝:在堆区重新申请空间进行拷贝工作(我自己写个函数再自己申请个空间然后让这个值指向自己的独立空间)
浅拷贝:简单的赋值拷贝工作(编译器写的简单拷贝函数)
问题:
比如说我们在构造函数中用new一个数据这个数据就是在堆区的是一直存在的
我们要在析构代码中使用delete把这个堆区空间删除掉
这里我们就要注意了如果我们有多个对象的时候如果进行的是浅拷贝的话我们的堆区的数据只有一个而多个对象共用一个堆区的数据
这里就会造成空间的重复释放,这个时候我们就要进行深拷贝才可以避免这个问题。
浅拷贝代码(编译器会给你所以可能会出错):
Person(const Person& p) {
m_height =p.m_height;
}
深拷贝代码:
Person(const Person& p) {
m_height = new int(*p.m_height);
}
就是自己再扩个堆区
析构释放堆区内存代码:
~Person() {
if (m_height != NULL)
{
delete m_height;
}
}
初始化列表(除了构造函数的另一种构造方式)
看看格式
代码:
class node {
public:
//初始化列表:
node (int a,int b, int c):a(a),b(b),c(c){ //注意冒号位置
}
int a;
int b;
int c;
};
类对象作为类成员
类的成员可以是一个对象,也就是对象成员
注意事项:
1.先构造对象成员在构造自身对象
2.先析构自身对象再析构对象成员(与构造相反)
关于调用就相当于把这个对象成员在自身对象里给了他一个名字,以至于在这个对象里面用我们起的这个名字可以调用另一个对象里的东西可以采用你起的这个名字.那个对象里的成员来调用包括构造函数也可以调用这时候就相当于隐性构造的方式
看代码吧(有注释的地方是难点):
#include<iostream>
using namespace std;
class Phone
{
public:
Phone(string name)
{
Phone_Name = name;
}
string Phone_Name;
};
class Person
{
public:
Person(string name, string p_Name) :Name(name), Phone_name(p_Name){}
//这里Phone_name(p_Name)就相当于Phone_name=p_Name也就是Phone=p_Name也就是调用了Phone(p_Name)的构造函数
void pl()
{
cout << Name << " 使用" << Phone_name.Phone_Name << endl;
}
string Name;
Phone Phone_name;
};
void test01()
{
Person p("张三" , "iphone 14 pro max");
p.pl();
}
int main() {
test01();
return 0;
}
静态成员
就是在成员前加static
静态成员分为静态成员变量和静态成员函数
静态成员变量
特点:
- 所有对象用一个数据(字面意思)
- 一定要类内声明、类外初始化(important)
- 在编译阶段分配内存
- 静态成员也是有访问权限的
类内声明类外初始化例子:
class node
{
public:
static int a;
}
int node :: a = 10;//一定要进行类外初始化
int main(){
}
成员变量的两种访问方式:
//通过类名访问
node :: a
//通过对象访问
node.p1;
p1.a;
静态成员函数
- 共享一个函数
- 只能访问静态成员变量
- 像静态成员变量一样有两种访问方式
C++对象模型this指针
成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
(静态成员变量成员函数静态成员函数都不占内存)
注意:
- 空对象占用内存空间为1(为了区分空对象占内存位置每个空对象有一个独一无二地址)
- 非静态成员变量是成员的内存大小
this指针
this指针指向被调用的成员函数所属的对象
-
this指针是隐含每一个非静态成员函数内的一种指针
-
this指针不需要定义,直接使用
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分(不建议使用可以换形参名)
- *在类的非静态成员函数中返回对象本身,可使用return this
同名情况:
Person(int age)
{
//当形参和成员变量同名时,可用this指针来区分
this->age = age;//让编译器知道哪个this是哪个this
}
返回对象本身的情况
//比如我调用了一个函数然后我们后面的函数还需要调用这个对象也就是让这个函数返回这个对象本身应该怎么操作
node& func1(node p)
{
a += p.a;
//返回对象本身
return *this;
}
//主函数里
p2.func1(p1).func1(p1).func1(p1);
要点:
这里要注意要返回对象的引用才是返回对象本身返回类名的引用也是一种返回对象本身的方法 如果改成
node func1(node p)
的话就相当于新创建了一个对象就像拷贝构造函数一样返回的是一个值而不是对象本身
空成员指针访问成员函数
空指针是可以调用成员函数的但是要注意有没有用到this指针!但是你知道的this指针是隐藏起来的
比如有一个成员变量在成员函数中用到了那大概率也会用到this指针这时候要注意
空指针调用示例:
//主函数里
person * p = NULL;//
p -> func()//这里是可以直接访问的只要里面没有this*指针
当然这时候你要说了,我哪知道这有没有this指针!!!这时候我们可以进行个判断来判断他是不是有this指针
//在函数里加上
if (this == NULL)
{
return;
}
//this指针不能为空为空就返回不进行操作就完事了!
const修饰的常函数和常对象
常函数:
-
成员函数+const
注意``const`在成员函数的后面 -
常函数内不可以修改成员属性(都常函数了你还想修改属性???)
常对象:
- 声明对象前
+const
(注意是声明前) - 常对象只能调用常函数
在每一个成员函数中都有一个this指针,this指针的本质是指针常量,指针常量的指向是不可修改的(this是
class_name * const this
.而当成员函数后面加上const 时修饰的是this指针相当于const class_name * const this
也就是把指针的值也规定为const所以这个时候指针修饰的值也不可以修改了)
友元
我们知道访问权限有很多种有private的有public的但是!!问题出现了!!如果你的家是你的私有的话你有你家的钥匙,你单身一个人过的挺好但是突然你有女朋友了!!!女朋友来你家你不能让她在public的走廊里站着不动吧,如果你真这样,活该单身!!!所以你要给你女朋友一把钥匙让她也能进入到你的家中。而这个钥匙就是友元。
我解释的够清楚了吧!!!!!
友元的目的就是让一个函数或者类访问另一个类中的私有成员
友元的关键字为friend
有三个地方可以做友元:
- 全局函数
- 类
- 成员函数
全局函数做友元
在需要访问的私有类的里面写下面这个东西就可以访问了
friend void girlfriend (Building * building);//在building类里面写上这句话girlfriend的全局函数就可以访问building类中的私有成员
类做友元
在需要访问的私有类的里面写下面这个东西就可以访问了
friend class girlfriend;//在building类里面写上这句话girlfriend就可以访问building类中的私有成员
成员函数做友元
在需要访问的私有类的里面写下面这个东西就可以访问了
friend void goodGay::visit();//在building类里面写上这句话girlfriend类中的visit成员函数就可以访问building类中的私有成员
基本数据结构
栈
概念(八股文):
- 栈(Stack)是一种线性存储结构,它具有如下特点:
- 栈中的数据元素遵守“先进后出"的原则。
- 限定只能在栈顶进行插入和删除操作。
- 栈的相关概念:
- 栈顶与栈底:允许元素插入与删除的一端称为栈顶,另一端称为栈底。
- 压栈:栈的插入操作,叫做进栈,也称压栈、入栈。
- 弹栈:栈的删除操作,也叫做出栈。
-
栈的常用操作为:
#include<stack> stack<int> s; s.pop(); //1. 弹栈,通常命名为pop,弹出栈顶元素, 但不返回其值 s.push(); //2. 压栈,通常命名为push s.size(); //3. 求栈的大小 s.empty(); //4. 判断栈是否为空如果栈为空则返回true, 否则返回false; s.top(); //5. 获取栈顶元素的值
-
栈的常见分类:
-
基于数组的栈——以数组为底层数据结构时,通常以数组头为栈底,数组头到数组尾为栈顶的生长方向
-
基于单链表的栈——以链表为底层的数据结构时,以链表头为栈顶,便于节点的插入与删除,压栈产生的新节点将一直出现在链表的头部
使用:
在使用方面我们在使用时要包含头文件#include <stack>
声明为stack<int> s
操作
- 基于数组的操作:
#include <stack>
#include <iostream>
using namespace std;
int main()
{
stack<int> mystack;
int sum = 0;
for (int i = 0; i <= 10; i++){
mystack.push(i);
}
cout << "size is " << mystack.size() << endl;
while (!mystack.empty()){
cout << " " << mystack.top();
mystack.pop();
}
cout << endl;
system("pause");
return 0;
}
- 基于链表的操作:
#include <iostream>
using namespace std;
template<class T>class Stack//这里用到了栈模板
{
private:
struct Node
{
T data;
Node *next;
};
Node *head;
Node *p;
int length;
public:
Stack()
{
head = NULL;
length = 0;
}
void push(T n)//入栈
{
Node *q = new Node;
q->data = n;
if (head == NULL)
{
q->next = head;
head = q;
p = q;
}
else
{
q->next = p;
p = q;
}
length++;
}
T pop()//出栈并且将出栈的元素返回
{
if (length <= 0)
{
abort();
}
Node *q;
T data;
q = p;
data = p->data;
p = p->next;
delete(q);
length--;
return data;
}
int size()//返回元素个数
{
return length;
}
T top()//返回栈顶元素
{
return p->data;
}
bool isEmpty()//判断栈是不是空的
{
if (length == 0)
{
return true;
}
else
{
return false;
}
}
void clear()//清空栈中的所有元素
{
while (length > 0)
{
pop();
}
}
};
int main()
{
Stack<char> s;
s.push('a');
s.push('b');
s.push('c');
while (!s.isEmpty())
{
cout << s.pop() << endl;
}
system("pause");
return 0;
}
c++中的常用可直接用的库函数和方法(持续更新中…)
swap(a,b)//可以直接交换两个数
sizeof(a) / sizeof(arr[0]);//直接获取数组大小(注意如果在函数中数组作为指针传递的话不能使用)