文章目录
-前言
上次总结了C语言的知识点,这次总结一下C++的学习心得。C++扩充和完善了C语言,是一门面向对象的编程语言。
-hello world
我们先看看hello world的例子:
#include <iostream>
using namespace std;
int main() {
cout << "hello world!" << endl;
return 0;
}
1.#include <iostream> c++提供给开发者的一些头文件;
2.using namespace std; 命名空间
-命名空间
C++中命名空间,作为附加信息来区分不同库中相同名称的函数、类、变量等。例如,我们现在有两个头文件中都有print()函数,那我们就需要头文件来区分。
first.h:在头文件first.h中写一个print()函数,
#pragma once
#include<iostream>
void print() {
std::cout << "头文件1" << std::endl;
}
再创建一个头文件two.h,也实现print()函数(这里和上面first.h代码一样),我们在.cpp中同时引入这两个头文件,如下:
#include "first.h"
#include "two.h"
#include <iostream>
using namespace std;
int main() {
print();
return 0;
}
在main函数中调用print()函数。这时候就会有问题,print()到底是调用哪个头文件下的呢?为了区分这print(),我们需要使用命名空间。在头文件first.h中定义命名空间:
namespace ftd {
void print() {
std::cout << "头文件1" << std::endl;
}
}
在main中使用命名空间
int main() {
ftd::print();
return 0;
}
这里就可以直接调用first.h中的print()函数了。当然也可以像下边这样使用:
using namespace ftd;
print();
-引用变量
引用变量可以理解为是变量的一个别名。
int a = 1;
int& b = a;
cout << a << endl;
cout << b << endl;
在这里 a,b打印出来的值是一样的。这里需要注意的是引用变量声明的时候必须初始化。
引用变量作为参数
void swap(int a,int b) {
a ^= b;
b ^= a;
a ^= b;
}
void swap1(int* a, int* b) {
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
void swap2(int& a, int& b) {
a ^= b;
b ^= a;
a ^= b;
}
int main() {
int a = 3;
int b = 4;
swap(a, b);
printf("a , b 的值:%d,%d \n",a,b);
swap1(&a,&b);
printf("a , b 的值:%d,%d \n", a, b);
swap2(a, b);
printf("a , b 的值:%d,%d \n", a, b);
return 0;
}
上面的三个函数 swap,swap1,swap2中第二个和第三个函数分别用指针和引用变量作为形参。可以发现第一个函数因为是值传递,main中的a,b两个参数的值并没有发生变化;第二个使用的指针,因为指针指向的值发生改变,所以main函数的a,b值换了;第三个传的是变量本身,因此也可以改变传入变量本身的值。
引用变量作为返回值
int getA1() {
static int a = 15;
return a;
}
int& getA2() {
static int a = 10;
return a;
}
int* getA3() {
static int b = 3;
return &b;
}
int main() {
cout << getA1() << endl;
// getA1() = 1;
cout << getA2() << endl;
getA2() = 2;
cout << getA2() << endl;
cout << *getA3() << endl;
*getA3() = 2;
cout << *getA3() << endl;
return 0;
}
首先三个函数中返回的是静态变量,因为局部变量在函数执行完会被释放。其次,我们可以发现函数getA2()和getA3()可以作为左值被赋值。因为getA2()返回的是变量,getA3()返回的是指针。getA1()返回的是一个int类型的值,所以getA1()不能作为左值。
-函数
内联函数
inline void printA() {
cout << "printA" << endl;
}
int main() {
printA();
return 0;
}
上面 printA() 即内联函数,在函数的前面声明inline。内联函数C++编译器会将函数里的代码嵌入到调用该函数的函数中,减少了函数入栈出栈的开销。但是声明了inline,编译器不一定就会让该函数内联。我们需要注意以下几点:
// 1.内联函数声明时候必须实现,不能分开。分开的话,编译器不会内联;
// 2.内联函数必须在调用函数的前面;
// 3.内联函数中不能存在任何形式的循环,不然编译器不会内联;
// 4.内联函数中不能存在过多的条件语句,不然编译器不会内联;
// 5.内联函数不能过于庞大;
// 6.内联函数不能进行取地址操作;
必须遵循以上几点,我们声明的内联函数编译器才可能允许内联。还有一点有的没有声明内联的函数编译器也可能将其内联。
默认参数
//默认参数
void print(int i=4) {
cout << i << endl;
}
int main() {
print();
print(2);
return 0;
}
在C++中形参是可以设置默认值的,像上面一样,print()中 i 打印出来是 4 ;print(2)中 i 打印的是2。也就是如果函数传参了,用传的值,否则用默认值。
//默认参数
void print(int a,int b=4) {
cout <<"a:"<<a<<"b:"<<b<< endl;
}
void print1(int a, int b = 4,int c) { //这里是错误的
cout << "a:" << a << "b:" << b << endl;
}
int main() {
print(1);
print(1,2);
return 0;
}
print1()函数是报错的,当参数中有默认值出现吗,那么后面的参数也必须有默认值。
-类
类和类函数的声明一般在头文件中,
class First
{
private:
int a;
public:
void setA(int a);
int getA();
};
函数的实现在cpp中:
void First::setA(int a) {
this->a= a;
}
int First::getA() {
return a;
}
类的使用:
int main() {
First first;
first.setA(2);
cout << first.getA() << endl;
return 0;
}
和java中是不是有点相似。
构造函数
class Test
{
public:
Test() {
}
Test(int a) {
}
Test(int a,int b) {
}
Test(const Test& test) {
}
private:
};
以上便是C++中的构造函数,基本和java相似。最后一个构造函数是拷贝构造函数,也是构造函数的一种。
int main() {
//1.调用无参构造函数
Test test;
//2.
Test test0();
//3.调用有参构造函数
Test test1(1,2);
//4.
Test test2 = 1;
//5.这种调用,调用一个参数的构造函数
Test test3 = (1, 2);
//6.手动调用构造函数
Test test4 = Test(1);
//7.调用的拷贝构造函数
Test test5 = test1;
//8.
Test test6(test1);
return 0;
}
以上是几种类声明的方法。这里注意几点:
- 当类中没有定义构造函数,系统会提供一个默认的无参构造函数;当类中有构造函数,系统将不提供无参构造函数。(和java中不同);
- 注释 5 :无论传多少个参数,都是使用的最后一位的参数去调用的一个参数的构造函数;
- 注释 7 8 : 调用的是拷贝构造函数;
- 形参也会调用拷贝构造函数 (如果类中没有拷贝函数,则调用系统中的);
- 构造函数中不能再调用别的构造函数;
析构函数
~Test(){
}
这就是析构函数,构造函数前面有一个~标识符。在该对象释放的时候调用,用于释放类内的资源。
初始化列表
class A
{
public:
A(int a) {
this->a = a;
cout << a << endl;
}
~A() {
}
private:
int a;
};
class B {
public :
B(int c):a1(1),a2(c){
}
private:
int b;
A a1;
A a2;
};
int main() {
B b(3);
return 0;
}
像上面代码,B类中含有A类对象并且A类没有无参构造函数的时候,在构造B类的时候需要初始化A类。如上 className() : A类变量名(参数值),A类变量名(参数值);并且这些A类变量的初始化顺序是和他们声明时的顺序一致。
初始化列表的使用场景:
- 成员变量是一个类类型,而且类中只要有参数的构造函数;
- const 变量
- 初始化父类的构造函数( 父类出现在初始化列表时,优先父类初始化再按初始化顺序初始化别的类成员)
对象的动态创建和释放
在栈上创建/释放对象和堆上创建/释放对象的区别:
- 在栈上创建的对象,创建后大小无法改变 (堆上可以动态调整);
- 栈上创建的对象,系统自动创建和销毁 (堆上申请的空间必须手动申请和释放);
堆上申请空间/释放空间方法:c语言:malloc/calloc free; c++: new/delete new[]/delete[];
//new 分配内存
int* p2 = new int(0);
cout << *p2 << endl;
*p2 = 10;
cout << *p2 << endl;
delete p2;
p2 = nullptr;
//new int[]
int* p3 = new int[10];
p3[2] = 2;
cout << p3[2] << endl;
delete[] p3;
p3 = nullptr;
这里注意:
1. 我们可以直接 new int(10) 初始int值为10;
2. new 申请的内存 delete 释放;new[]申请的内存 delete[]释放;
class A
{
public:
A(int a) {
}
~A();
}
int main() {
A* a = new A(10);
return 0;
}
new 为复杂对象申请内存也可以直接设置默认值。
-友元函数/友元类
友元函数
友元函数:定义在类的外部且函数内部的类对象可以直接操作该对象的private/protected属性和函数。
友元函数的声明也很简单,在函数的前面加上 friend 即可。如下:
class A{
public:
A(int c) {
}
private:
friend void test(A a);
void print() {
cout << "友元调用" << endl;
}
int c;
};
void test(A testA) {
testA.print();
cout << testA.c << endl;
}
int main() {
return 0;
}
上面的代码中:函数test()中的testA参数虽然是A类型的,但是是不可以使用A类中私有的 c元素和print()函数的。
但是,我们在A类中通过这行代码 friend void test(A a) 将这个函数声明为友元函数就可以了。
这里需要注意的是:友元函数的声明在public/private/protected中都可以。
友元类
友元类:假设在A类中声明了友元类B,则在B类中可以使用A类中任何类型的元素和函数。
友元类的声明:friend class 类名;
class A
{
public:
A() {}
private:
friend class B;
void print() {
cout << number << endl;
}
int number;
};
class B
{
public:
B() {}
private:
void print() {
testA.number;
}
A testA;
};
我们可以发现,在A中声明的友元类B中的testA对象可以直接使用number元素。
-继承
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
在C++中,一个派生类可以派生很多基类(就是可以有很多父类)。派生列表格式如下:
class derived-class: access-specifier base-class
derived-class:派生类的名称;base-class 基类的名称;access-specifier 继承类型。
class parent
{
public:
parent() {}
int number1;
private:
int number;
};
class child : public parent
{
public:
child() {
}
private:
};
这里注意:
- 派生类中可以访问除了private修饰的所有基类的成员。
- 派生类继承所有基类函数,除了:构造函数,析构函数,拷贝构造函数,重载运算符 和 友元函数。
- 基类的构造函数,派生类用初始化列表调用;
- 类的继承类型按以下规则:
- public 继承基类的public 和 protected类型在派生类中仍是public 和 protected;
- protected 继承基类 public 和 protected类型变成派生类中的 protected类型;
- private 继承基类 public 和 protected类型变成派生类中的 private 类型;
类 | public | protected | private |
---|---|---|---|
同一个类 | yes | yes | yes |
派生类 | yes | yes | no |
外部的类 (public继承) | yes | no | no |
外部的类 (protected继承) | no | no | no |
外部的类 ( private继承) | no | no | no |
-抽象类
class Shap
{
public:
virtual void area()=0;
};
class Circle:public Shap{
public:
Circle(int a) :a(a) {
}
void area() {
cout << 3.14 * a * a << endl;
}
private:
int a;
};
int main() {
Shap* shap = new Circle(2);
shap->area();
return 0;
}
C++ 接口是使用抽象类来实现的,如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 “= 0” 来指定的,上文中的Shap中的area()函数就是纯虚函数,并且Shap类不能被实例化。
-模板
template <typename T>
T const Max(T a,T b) {
return a < b ? b : a;
}
int main() {
int a = 2;
int b = 3;
cout << Max(3,5) << endl;
cout << Max(1.2,2.3) << endl;
return 0;
}
上面代码中使用的是c++中的模板,和java中的泛型有点类似。Max方法可以比较 int double的值的大小。一般模板函数的写法如下:
template <typename type>
ret-type func-name( list)
{
// 函数的主体
}
type: type 是函数所使用的数据类型的占位符名称,由我们自定义;
ret-type:函数返回类型;
func-name:函数名称;
类模板的声明如下:
template <class type> class class-name {
}