第一个c++程序
cpp
#include<iostream>
using namespace std;
int main()
{
cout << "hello c++" << endl;
return 0;
}
c++是兼容了大多数的c语言代码的,比如printf,但是c++也有自己的输入和输出。这里着重关注的是using namespace std;这个叫做命名空间,可以避免命名冲突或名字污染。上述代码不使用命名空间的代码如下
cpp
#include<iostream>
int main()
{
std::cout << "hello c++" << std::endl;
return 0;
}
命名空间
cpp
namespace x
{
int a;
int b;
}
int main()
{
x::a = 10;
x::b = 20;
std::cout << x::a+x::b << std::endl;
return 0;
}
这里有几个注意点
1.命名空间可以嵌套定义,命名空间名相同会进行合并。
2.命名空间中可以为变量,函数,类型等。
3.namespace只能定义在全局。
4.c++所有标准库都放在了std当中
c++的输入&输出
<iostream>是要包含的头文件,cin为标准输入,cout为标准输出,endl为换行加刷新缓冲区,<<为流插入,>>为流提取。这里需要注意我们在学习的时候使用using namespace std但在实际开发一般不会使用。
缺省函数
缺省函数是指在函数的定义中,给参数一个缺省值,当传参没有传递这个参数时,默认使用这个参数,全缺省就是每一个参数都有缺省值,半缺省就是部分参数缺省。要注意,缺省的参数必须连续,且从右往左。因为在实际调用中,会从左到右逐一赋值,不能跳跃。且当函数定义和声明分离时,不能同时给缺省值,规定在声明中给出缺省值。
cpp
#include<iostream>
using namespace std;
//全缺省
void func1(int a=10,int b=20,int c=30)
{
cout << a << " " << b << " " << c << endl;
}
//半缺省
void func2(int a, int b = 20, int c = 30)
{
cout << a << " " << b << " " << c << endl;
}
//声明定义分离
void func3(int a = 10, int b = 20, int c = 30);
void func3(int a, int b, int c ) //此时不能同时给缺省参数
{
cout << a << " " << b << " " << c << endl;
}
int main()
{
func1(); //不传参使用默认参数
func1(50); //传参后使用传入参数
func2(40); //这时第一个参数必须给值
return 0;
}
函数重载
c++支持在同一作用域下有同名函数,但要求参数不同,可以是个数不同,也可以是类型不同,这就叫做函数重载。
cpp
#include<iostream>
using namespace std;
void func(int a,int b,int c)
{
cout << a << " " << b << " " << c << endl;
}
void func(int a, int b) //个数不同
{
cout << a << " " << b << endl;
}
void func(float a, int b, int c) //类型不同
{
cout << a << " " << b << " " << c << endl;
}
int main()
{
func(10, 20, 30);
func( 20, 30);
func(10.5f, 20, 30);
return 0;
}
引用
类型& 引用别名 = 引用对象;
引用不是定义了一个新的对象,它和原本对象占用同一块空间,不单独开辟空间
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
cout << a << endl; //输出10
b++;
cout << a << endl; //输出11
return 0;
}
引用在定义时必须进行初始化,一个变量可以有多个引用,引用一旦引用一个实体后不可修改。
const引用
可以引用⼀个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访 问权限在引用过程中可以缩小,但是不能放大。
nullptr
cpp
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
类和对象
cpp
#include<iostream>
using namespace std;
class test
{
int m_a;
int m_b;
public:
void myPrint()
{
cout << "myPrint" << endl;
}
};
int main()
{
test t;
t.myPrint();
return 0;
}
class为定义类的关键字,test为类名
一般默认为private,这个修饰的变量或函数,只能在类内进行访问,public定义类外也能访问,protected定义,类外不能访问但可以继承。
代码中test t是对这个类进行实例化。这里注意这个实例出来的对象大小是不算函数的,只算变量的大小。
this指针
前面说函数不算大小,那么是怎么确定是哪个对象调用的呢,就是通过this指针,这个指针是自带的
cpp
void Init(Date* const this, int year,int month, int day)
类的默认成员函数

构造函数
构造函数函数名与类名相同,无返回值,默认构造函数参数为空,自己实现构造函数后,不会提供默认构造函数,实例化时会调用构造函数。无参构造函数,全缺省构造函数,不写构造函数默认生成的构造函数都叫默认构造函数,三者只能存在一个。
cpp
class test
{
int m_a;
int m_b;
public:
void myPrint()
{
cout << "myPrint" << endl;
}
//无参构造函数
test()
{
m_a = 1;
m_b = 2;
}
//有参构造函数
test(int a,int b)
{
m_a = a;
m_b = b;
}
};
析构函数
析构函数与构造函数相反,实在对象销毁的时候进行调用。析构函数名实在类名前加上~,一个类只能有一个析构函数。一般来讲,类中申请了空间要靠析构函数来释放。
cpp
//析构函数
~test()
{
cout << "析构函数" << endl;
}
拷贝构造函数
首先拷贝构造是一个特殊的构造函数。构造函数第一个参数为自身类型的引用,其余参数都有默认值,则这个构造函数叫做拷贝构造函数。这里第一个参数必须为引用,如果使用传值方式会有如下问题
这里调用拷贝构造进行创建d2,但是使用值传递,需要创建一个d1的临时变量,创建临时变量使用的是拷贝构造,而我们写的拷贝构造又是值传递,因此会陷入无穷
若未自定义拷贝构造,编译器会自己生成一个浅拷贝的拷贝构造函数,一般定义拷贝构造函数会加一个const修饰,防止改变原对象的值。且注意默认拷贝构造函数是浅拷贝,对与存在申请空间的类要使用拷贝构造函数申请空间,实现深拷贝。
拷贝构造的调用时机在如下三个部分
cpp
#include<iostream>
using namespace std;
class test
{
public:
int m_a;
int m_b;
test(int a, int b)
{
m_a = a;
m_b = b;
}
void myPrint()
{
cout << m_a << m_b << endl;
}
test(const test& t)
{
m_a = t.m_a;
m_b = t.m_b;
}
};
int main()
{
test t1(1,2);
t1.myPrint();
//使用已创建对象初始化新对象
test t2(t1);
t2.myPrint();
//值传递的方式给函数参数传值
//这里就是我前面说的为什么必须使用引用传递,不使用值传递,值传递会调用拷贝构造函数
//值方式返回局部对象
//return t1;
return 0;
}
赋值运算符重载
当运算符用于类类型对象时,c++允许我们通过运算符重载对运算符赋予新的含义。但是重载运算符必须有一个类类型参数,不能重载改变内置类型对象的含义。且c++规定,类类型使用运算符时,必须调用运算符重载,否则会报错。若运算符重载函数未成员函数,则第一个变量默认为this指针,进行了隐式传递。重载<<>>必须使用全局函数,因为作为成员函数,第一个参数默认为this指针,此时参数出现了顺序问题。在重载++的时候函数名都是一样的,为了进行区分,后置++加了一个int形参。示例如下
cpp
#include<iostream>
using namespace std;
class test
{
public:
int m_a;
int m_b;
test(int a, int b)
{
m_a = a;
m_b = b;
}
void myPrint()
{
cout << m_a << m_b << endl;
}
//重载前置++
test& operator++()
{
test temp(m_a, m_b);
this->m_b++;
return temp;
}
// 重载后置++
test& operator++(int)
{
m_b++;
return *this;
}
//重载运算符时成员函数
void operator+(test& b)
{
m_a += b.m_a;
}
};
//重载运算符为全局函数
bool operator==(test& a, test& b)
{
if (a.m_a == b.m_a && a.m_b == b.m_b)
{
return true;
}
return false;
}
//重载<<>>必须使用全局函数
ostream& operator<<(ostream& out,test& t)
{
out << t.m_a << t.m_b << endl;
return out;
}
int main()
{
test t1(1,2);
test t2(1,2);
if (t1 == t2)
{
cout << "相等" << endl;
}
t1 + t2;
cout << t1.m_a << endl;
cout << t1;
cout << t1++;
cout << t1;
cout << ++t1;
cout << t1;
return 0;
}
赋值运算符重载用于完成已经存在的两个对象进行直接的拷贝赋值,与拷贝构造不同,拷贝构造是创建出新的对象。规定其必须重载为成员函数,参数建议写为const当前对象引用,返回值建议写为当前类型的引用,当没有显示的定义,默认会生成类似浅拷贝的样子。
cpp
#include<iostream>
using namespace std;
class test
{
public:
int m_a;
int m_b;
test(int a, int b)
{
m_a = a;
m_b = b;
}
void myPrint()
{
cout << m_a << m_b << endl;
}
test& operator=(test& t)
{
m_a = t.m_a;
m_b = t.m_b;
return t;
}
};
int main()
{
test t1(1,2);
test t2(2,4);
t1 = t2;
t1.myPrint();
return 0;
}
取地址运算符重载
const修饰的成员函数称之为const成员函数,修饰放在成员函数列表后面,其修饰后,隐含this指针由Date* const this 变为 const Date* const this
cpp
#include<iostream>
using namespace std;
class test
{
public:
int m_a;
int m_b;
test(int a, int b)
{
m_a = a;
m_b = b;
}
void myPrint() const
{
cout << m_a << m_b << endl;
}
};
int main()
{
test t1(1,2);
//权限缩小
t1.myPrint();
return 0;
}
初始化列表
构造函数还有另一种初始化方式,就是初始化列表。每个成员变量在初始化列表中只能出现一次。引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。因为C++规定,对象的"初始化"发生在进入构造函数函数体(即花括号 {})之前,而不是在函数体内部。这些变量不能先定义后赋值,必须定义和赋值一起完成。
无论是否显示写初始化列表,每个构造函数都有初始化列表;
无论是否在初始化列表显示初始化成员变量,每个成员变量都要走初始化列表初始化;

cpp
class MyClass {
private:
const int m_const;
int& m_ref;
Other m_other;
public:
// 初始化列表:在对象"出生"的一瞬间完成所有设定
MyClass(int a, int b)
: m_const(a), // 出生即赋值常量
m_ref(a), // 出生即绑定引用
m_other(b) // 出生即调用带参构造
{
// 函数体内什么都不用写了,所有成员都已经初始化完毕
}
};
类型转换
cpp
#include<iostream>
using namespace std;
class A
{
public:
// 构造函数explicit就不再支持隐式类型转换
// explicit A(int a1)
A(int a1)
:_a1(a1)
{}
//explicit A(int a1, int a2)
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
int Get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(const A& a)
:_b(a.Get())
{}
private:
int _b = 0;
};
int main()
{
// 1构造一个A的临时对象,再用这个临时对象拷贝构造aa3
// 编译器遇到连续构造+拷贝构造->优化为直接构造
A aa1 = 1;
aa1.Print();
// C++11之后才支持多参数转化
A aa3 = { 2,2 };
// aa3隐式类型转换为b对象
// 原理跟上面类似
B b = aa3;
return 0;
}
这里直接将1赋值给了类对象,就会进行类型转换。
static成员
static修饰称为静态成员变量,要在类外进行初始化,修饰函数,称为静态成员函数,没有this指针。
cpp
#include<iostream>
using namespace std;
class test
{
public:
static int m_c;
void static myStatic()
{
cout << "static" << endl;
}
};
int test::m_c = 10;
int main()
{
test::myStatic();
cout << test::m_c << endl;
return 0;
}
友元
友元提供了一种突破访问限定符的访问方式。友元分为友元函数和友元类,将某一类或函数注册为自己的友元,该类或函数就可以访问自己的private限定的变量或函数。
cpp
#include<iostream>
using namespace std;
class test
{
//友元函数
friend void func(const test& t);
//友元类
friend class test1;
private:
int _a = 10;
int _b = 20;
};
class test1
{
int _c;
int _d;
public:
test1(const test& t)
{
_c = t._a;
_d = t._b;
}
void myPrint()
{
cout << _c << _d << endl;
}
};
void func(const test& t)
{
cout << t._a << t._b << endl;
}
int main()
{
test t;
func(t);
test1 t1(t);
t1.myPrint();
return 0;
}
内部类
一个类内部在声明一个类,这个类就叫内部类。内部类默认为外部类的友元类。注意定义外部类不包含内部类
cpp
#include<iostream>
using namespace std;
class test
{
public:
class test1
{
int a;
int b;
public:
test1(const test& t)
{
a = t._a;
b = t._b;
}
};
private:
int _a = 10;
int _b = 20;
};
int main()
{
test t;
test::test1 t1(t);
return 0;
}
匿名对象
这个其实就是不用变量进行接收,会调用一次构造函数和一次析构函数就会结束声明周期