《 android NDK 》 C++语言入门总结

-前言

上次总结了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;
}

以上是几种类声明的方法。这里注意几点:

  1. 当类中没有定义构造函数,系统会提供一个默认的无参构造函数;当类中有构造函数,系统将不提供无参构造函数。(和java中不同);
  2. 注释 5 :无论传多少个参数,都是使用的最后一位的参数去调用的一个参数的构造函数;
  3. 注释 7 8 : 调用的是拷贝构造函数;
  4. 形参也会调用拷贝构造函数 (如果类中没有拷贝函数,则调用系统中的);
  5. 构造函数中不能再调用别的构造函数;

析构函数

~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类变量的初始化顺序是和他们声明时的顺序一致。
初始化列表的使用场景:

  1. 成员变量是一个类类型,而且类中只要有参数的构造函数;
  2. const 变量
  3. 初始化父类的构造函数( 父类出现在初始化列表时,优先父类初始化再按初始化顺序初始化别的类成员)

对象的动态创建和释放

在栈上创建/释放对象和堆上创建/释放对象的区别:

  1. 在栈上创建的对象,创建后大小无法改变 (堆上可以动态调整);
  2. 栈上创建的对象,系统自动创建和销毁 (堆上申请的空间必须手动申请和释放);

堆上申请空间/释放空间方法: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:	
};

这里注意:

  1. 派生类中可以访问除了private修饰的所有基类的成员。
  2. 派生类继承所有基类函数,除了:构造函数,析构函数,拷贝构造函数,重载运算符 和 友元函数。
  3. 基类的构造函数,派生类用初始化列表调用;
  4. 类的继承类型按以下规则:
    1. public 继承基类的public 和 protected类型在派生类中仍是public 和 protected;
    2. protected 继承基类 public 和 protected类型变成派生类中的 protected类型;
    3. 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 {
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章