Lesson01.c++入门
1.1c++关键字
C++总计63个关键字,C语言32个关键字
随着学习的深入,各个关键字都会使用到。
1.2命名空间
当一个项目代码量过大,多人协作时,难免会出现很多冲突,使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
//guo为命名空间的名字
namespace guo{
//命名空间中可以定义变量/函数/类型
int rad=10;
int sad=20;
int ADD(int a,int b){
return a+b;
}
struct LNode{
struct LNode* next;
int val;
}
//命名空间也可以嵌套定义
namespace A{
int a;
int b;
}
}
命名空间使用的三种方式
//1.加命名空间名称及作用域限定符
int main(){
cout<<guo::rad<<endl;
return 0;
}
//2.使用using将命名空间中某个成员引入
using guo::rad;
int main(){
cout<<rad<<endl;
cout<<guo::sad<<endl;//未引入的成员依旧加命名空间名称及作用域限定符
}
//3.使用using namespace命名空间名称引入
using namespace guo;
int main(){
cout<<rad<<endl;
cout<<sad<<endl;
}
1.3c++输入与输出
1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件
以及按命名空间使用方法使用std。
2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<
iostream >头文件中。
3. <<是流插入运算符,>>是流提取运算符。
4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。
C++的输入输出可以自动识别变量类型。
#include <iostream>
using namespace std;
int main(){
int a;
double b;
cin>>a>>b;//可以自动识别类型
cout<<a<<" "<<b<<endl;//endl进行换行
return 0;
}
1.4缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
void Fun(int a=0){
cout<<a<<endl;
}
int main(){
Fun();//没有参数时,使用参数的默认值
Fun(10);//有参数时,使用指定的实参
return 0;
}
缺省参数的分类
//全缺省参数
void Fun(int a=1,int b=2,int c=3){
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
}
//半缺省参数
void Fun(int a,int b=2,int c=3){
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
}
int main(){
Fun(); //1,2,3
Fun(10); //10,2,3
Fun(10,20); //10,20,3
Fun(10,20,30); //10,20,30
return 0;
}
注意:
1. 半缺省参数必须从右往左依次来给出,不能间隔着给
void Fun(int a=1,int b,int c=3){} //错误,参数必须从右往左依次给出
void Fun(int a=1,int b=2,int c=3){
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
}
int main(){
Fun(,,3); //错误,逗号用来分隔参数
Fun(1);
return 0;
}
2. 缺省参数不能在函数声明和定义中同时出现
3. 缺省值必须是常量或者全局变量
4. C语言不支持
1.5函数重载
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题
#include <iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right){
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right){
cout << "double Add(double left, double right)" << endl;
return left + right;
}
// 2、参数个数不同
void f(){
cout << "f()" << endl;
}
void f(int a){
cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void f(int a, char b){
cout << "f(int a,char b)" << endl;
}
void f(char b, int a){
cout << "f(char b, int a)" << endl;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
f(10, 'a');
f('a', 10);
return 0;
}
1.6引用
引用相当于给变量取别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共同使用一块空间。
使用方法:类型& 引用变量名(对象名)=引用实体;
void TestRef(){
int a=10;
int &re=a;//re相当于a,两者地址相同
}
注意:
1.引用在定义时必须初始化
2.一个变量可以有多个引用
3.引用一旦引用了一个实体,在不能引用其他的实体
4.引用类型和引用实体是同一种类型
void TestRe(){
int a=10;
int b=10;
int &re; //错误,必须初始化
int &re=a;
int &ree=a;//一个变量可以有多个引用
int &re=b;//错误,引用只能引用一个实体
}
常引用
void test(){
const int a=10;
int &ra=a; //错误的,因为a为只读,b为可读可写的
const int &ra=a; //正确的方式
int &b=10; //错误的,10是常量,不可以更改
const int &b=10; //正确的方式
double d=12.22;
int &rd=d; //错误的,类型不同
const int &rd=d; //正确的,中间会产生一个临时变量,具有常性
}
const可以赋给非const,但是非const不能赋给const。指针同样适用
使用场景
1.做参数
//引用做参数
void swap1(int &a,int &b){//引用,不是取地址
int t=a;
a=b;
b=t;
}
//指针做参数
void swap2(int *a,int *b){
int t=*a;
int *a=*b;
int *b=t;
}
int main(){
int a=1,b=2;
swap1(a,b);
swap2(&a,&b);//这里是取地址
}
2.做返回值
//做返回值
int test1(int &a){
a+=1;
return a;//会产生一个临时数据,将临时数据返回
}
int &test2(int &a){
a+=1;
return a;//会少创建一个临时变量,提高效率
}
注意:如果函数返回时,出了函数作用域,如果返回对象还在,则可以使用引用返回,如果已经被系统收回,则必须使用传值返回。
int &Add(int a,int b){
int c=a+b;
return c;
}
int main(){
int &ret=Add(1,2);
Add(3,4);
cout<<ret<<ednl; //这里会返回7而不是3,有可能是随机值
return 0;
}
解决方法:在函数使用static int c=a+b,这样c在静态区,出了函数这块空间不会被系统所回收
引用与指针的区别
- 引用在定义时要初始化,指针没有要求
- 引用在引用一个实体后不能再引用其他实体,而指针在任何时候都可以指向任何同一类型的实体
- 引用在概念上是变量的别名,而指针是存储变量的地址
- 没有NULL引用,但是有NULL指针
- 引用自加是实体加一,指针自加是向后偏移一个类型大小
1.7内联函数
c++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,提高系统效率
使用一种以空间换时间的思想,缺陷:会使目标文件变大,优势:少了栈帧的开销,提高系统效率
使用方法:在函数前面加上inline,对编译器而言只是建议,编译器未必会采用,一般小函数会成为内联函数
宏的优缺点:
缺点:不方便调试,代码可读性差,没有类型安全检查
1.8C++11新增
1.auto关键字
void test(){
int a=10;
auto b=a; //会自动进行类型匹配
int *p=&a;
auto b1=p;
}
注意:
使用auto定义变量必须对其进行初始化,auto不是类型声明,而是一个类型声明时的占位符
auto不能作为函数参数,不能直接来声明数组
2.范围for循环
void test(){
int arr[]={1,2,3,4,5,6,7};
for(auto e:arr){ //相当于e来遍历整个数组
cout<<e<<endl;
}
}
条件:
for循环迭代的范围是确定的
3.指针空值nullptr
在C++中建议将空指针置为nullptr,因为C++中将NULL设为0
Lesson02.类和对象
2.1类的定义
class className{
//类体:由成员函数和成员变量组成
};
//注意后面需要加;
class为定义类的关键字,className为类的名字,{}中为类的主体
2.2访问限定符
public(公有),private(私有),protected(保护)
public修饰的成员在类外可以直接访问,private和protected在类外不可以直接访问所修饰的成员
class默认访问限定符为private,struct默认访问限定符为public
#include <iostream>
using namespace std;
class Person{
public:
void printPerson(){
cout<<"name:"<<_name<<endl;
cout<<"age"<<_age<<endl;
private:
int _age;
char _name[20];
};
//printPerson成员函数也可以在类外定义,当一个项目很大,多人协作时,推荐类外定义
void Person::printPerson() //指定成员函数是哪一个类
{
cout<<"name:"<<_name<<endl;
cout<<"age"<<_age<<endl;
}
2.3类的实例化
用类类型创建对象的过程称为实例化
int main(){
Person p1;
p1.printPerson();
//一个类可以实例化出多个对象
Person p2;
p2.printPerson();
return 0;
}
2.4类对象的大小
//该类实例出对象的大小为4
class A{
public:
void print_A(){
cout<<_A<<endl;
}
private:
int _A;
}
//该类实例出对象的大小为8
class B{
public:
void print_B(){
cout<<_B<<endl;
}
private:
int _B;
char _cB;
}
//该类实例出对象的大小为1
class C{
public:
void print_C(){
cout<<_C<<endl;
}
}
没有成员变量的类大小为1,为了占位,表示对象存在
只计算成员变量,不包括成员函数,因为一个类实例化出N个对象时,每个对象的成员变量都可以存储不同的值,而调用的函数只有一个
成员变量大小同struct大小的计算方法,内存对齐原则
2.5this指针
class Date
{
public:
//void Init(Date* this,int year, int month, int day)
void Init(int year, int month, int day){
_year = year;
_month = month;
_day = day;
}
//void Print(Date* this)
void Print(){
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1, d2;
d1.Init(2022,1,11); //d1.Init(&d1,2022,1,11);
d2.Init(2022, 1, 12);
d1.Print(); //d1.Print(&d1);
d2.Print(); //d2.Print(&d2);
return 0;
}
C++编译器会给每个“非静态的成员函数”增加一个隐藏的指针参数,让该指针指向当前对象
void Display() //void Display(Date* this)
{
cout<<_year<<endl; //cout<<this->_year<<endl;
}
this指针存在栈,参数由编译器自动添加,不可手动写入
2.6 类的默认成员函数
2.6.1 构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。
6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数
class Date
{
public:
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2(2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
Date d3();// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
}
2.6.2析构函数
对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
5.如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏。
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
// 程序运行结束后输出:~Time()
/*在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,_day三个是内置类型
成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;
而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。
但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date
类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部
调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
*/
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
2.6.3拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征:
1.拷贝构造函数是构造函数的一种重载。
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为 会引发无穷递归调用
3.若未显式定义,编译器会生成默认的拷贝构造函数。
4.拷贝构造函数典型调用场景:使用已存在对象创建新对象,函数参数类型为类类型对象,函数返 回值类型为类类型对象
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)//拷贝构造函数
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date temp(d);//调用拷贝构造
return temp;
}
int main()
{
Date d1(2022,1,13);
Test(d1);
return 0;
}
2.6.4赋值运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
.* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator==(const Date& d2)
{
return _year == d2._year&& _month == d2._month&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
赋值运算符重载格式:
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time& operator=(const Time& t)
{
if (this != &t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
return *this;
}
private:
int _hour;
int _minute;
int _second;
};
2.6.5取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};