C++重点知识点总结

tips

编译和调试的区别
编译(compile):依赖于编译器,将源码转化成目标文件 如.obj
调试:让程序在系统中运行之前查错和改错,找出并修正潜在的错误

linux c++的四个处理过程

  1. 预处理
    将所有#include头文件以及宏定义替换成其真正的内容
  2. 编译
    将经过预处理之后的程序转换成特定汇编代码的过程
  3. 汇编
    将上一步的汇编代码转换成机器代码,这一步产生的文件叫做目标文件
  4. 链接
    将多个目标文件以及所需的库文件(.so)链接成最终的可执行文件

数据类型

4种转换较为规范的运算符
reinterpret_cast<type-name>(expression)

  1. reinterpret_cast 可以将指针类型转换为足以存储指针表示的整型
  2. 不能将函数指针转换为数据指针

枚举类型 enum
定义:
enum 类型名{枚举值表}
枚举值表也叫枚举元素列表,列出定义的枚举类型的所有可用值,各个值之间用“,”分开。
enum Suit{Diamonds,Hearts,Clubs};

数组

c++的运算符优先要求使用括号

数组类比于指针
数组名是第一个元素的地址
int* a的类型是int* int a的类型是int
前者a是指向整型数据的指针,它的本质是一个地址,后者就是一个数据了

Dog* dog = new Dog; 分配一片内存 并把内存的地址赋给 dog

无符号类型

不能存储负数值的无符号变体,其优点是可以增大变量能够存储的最大值
(short范围-32768-32767,unsign short范围0-65535)

指针表示动态数组

使用new[]运算符创建数组时,将采用动态联遍,即将在运行时为数组分配空间,其长度也将在运行时设置
使用完成后,应用delete[]释放占用的内存

int size;
cin >>size;
int *arr = new int [size];
……
delete[] arr;

数组区间的函数

指定元素区间 可以通过传递两个指针完成:一个指针标识数组的开头,另一个指针标识数组的尾部

int sum_arr(const int *begin,const int *end); //函数声明
int main(){
    int arr[10];
    int sum = sum_arr(arr,arr+3); //定义区间  
}

const保护数组

为防止函数无意中修改数组的内容,可在声明形参时使用关键字const
void show_array(const double ar[],int n)

结构体

struct typename {
    char name[10];
    int ages;
}
  1. new 创建动态结构

inflat *ps = new inlat;
使用完成后要用delete释放内存 delete ps;

ps -> price 也是指向结构的price成员
ps 是指向结构的指针,则*ps就是指向的值–结构本身
(*ps)是一种结构 (*ps).name 是该结构的name成员

函数

函数原型

原型可以帮助编译器完成许多工作,降低程序出错的机率,具体有一下几点:

  1. 编译器正确处理函数返回值
  2. 编译器检查使用的参数数目是否正确
  3. 编译器检查使用参数类型是否正确

#define的定义

(文本替换)
define 是宏定义,程序在预处理阶段将用define定义的内容进行替换。程序在运行时,常量表中并没有用define定义的常量,系统不会为它分配内存

  1. #define定义一个标识符来表示一个常量。
    特点:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了

    • 宏展开是在预处理阶段完成的,这个阶段把替换文本只看做一个字符串,并不会有任何的计算发生。
    • 宏其实就是简单的文本替换。

定义标识符的一般形式:

#define 标识符 常量 //注意,最后没有分量
  1. 在大规模的开发过程中,特别是跨平台和系统的软件里,define最重要的的功能就是条件编译
#ifdef WINDOWS
……
#endif
#ifdef LINUX
……
#endif

可以在编译的时候通过#define设置编译环境。

函数指针

使用函数指针的三个步骤:

  1. 声明函数指针
  2. 让函数指针指向函数地址
  3. 通过函数指针调用函数

声明函数指针

double pam(int)  //函数原型

double (*f)(int)   //函数指针

pam 替换为了(*f).由于pam是函数名 即(*f)也是函数名, f 是函数指针

函数指针调用函数

//函数指针调用函数
f = pam;
double y = pam(2);
double x = (*f)(5);

#include<iostream>
using namespace std;
double jack(int);
double rose(int);
void accrate(int lines, double(*pf)(int)); //声明指针函数
int main() {
	int code;
	cin >> code;
	cout<<"jack和rose输入"<<code<<"行代码算花费的时间"<<endl;
	accrate(code, jack);
	accrate(code, rose);
}
double jack(int n) {
	double sum = n * 0.5;
	return sum;
}
double rose(int n) {
	double sum = n * 0.7;
	return sum;
}

void accrate(int lines, double(*pf)(int)) {
	//double um = (*pf)(lines);
	cout << lines << " lines may have " << (*pf)(lines) << "mins" << endl;

}

引用&

  1. 形参需要修改实参时
  2. 当实参较大时,如(数据 结构体)传递引用 系统不会生成临时变量 较小的内存消耗

形参实参的三种传值方式

  1. 按值传递
  2. 按地址传递 形参改变实参
  3. 按引用传递
#include<iostream>
void swap1(int a, int b) {
	int temp;
	temp = a;
	a = b;
	b = temp;
}

void swap2(int* p, int* q) {
	int temp;
	temp = *p;
	*p = *q;
	*q = temp;
}

void swap3(int& a, int& b) {
	int temp;
	temp = a;
	a = b;
	b = temp;
}

int main() {
	int i = 10;
	int j = 20;
	//按值传递   
	swap1(i, j); //形参不会对实参进行修改
	std::cout << "i的值是" << i << std::endl;
	std::cout << "j的值是" << j << std::endl;

	//按地址传递   
	swap2(&i, &j); //形参会对实参进行修改
	std::cout << "i的值是" << i << std::endl;
	std::cout << "j的值是" << j << std::endl;

	//按引用传递   
	swap3(i, j); //形参不会对实参进行修改
	std::cout << "i的值是" << i << std::endl;
	std::cout << "j的值是" << j << std::endl;
	return 0;

}

模板

函数模板

每声明一个模板函数 都必须有一下两行声明;

template<typename T>
void swap(T a,T b){
    T temp;
    temp = a;
    a = b;
    b = temp;
}

建立一个模板,关键字template和typename是必需的

  1. 显示调用
    swap<int>(a,b);
  2. 隐式调用
    swap(a,b);
重载模板

需要多个对不同类型使用同一种算法的函数时,可以使用模板。可以像重载常规函数定义那样重载模板定义。

#include<iostream>
const int size = 8;
template<typename T>
void swap(T& a, T& b) {
	T temp;
	temp = a;
	a = b;
	b = temp;
}
template<typename T>
void swap(T* a, T* b, int n) {
	T temp;
	for (T i = 0; i < n; i++) {
		temp = a[i];
		a[i] = b[i];
		b[i] = temp;
	}
}
void show(int a[]) {
	for (int i=0;i<size;i++)
	{
		std::cout << a[i];
	}
	std::cout << std::endl;

}
int main() {
	int n = 10;
	int m = 20;
	swap(n, m);
	std::cout << "n的值是" << n << std::endl;
	std::cout << "m的值是" << m << std::endl;
	int a[size] = { 1,2,3,4,5,6,7,8 };
	int b[size] = { 9,8,7,6,5,4,3,2 };
	swap( a,  b,size);
	show(a);

}

类模板

建立一个通用类,类中的成员 数据类型可以不做具体指定

template<typename T>
#include <iostream>
using namespace std;
template<typename T1, typename T2>
class Person {
public:
	Person(T1 name, T2 age);
	T1 m_name;
	T2 m_age;
};

template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->m_age = age;
	this->m_name = name;
}

void test()
{
	Person<string, int> p1 = Person<string, int>("zhangsan", 18); //无自动推导 要在积极手动加入
	Person<string, int> p2("lisi", 20);
	cout << p1.m_age << p1.m_name << endl;
	cout << p2.m_age << p2.m_name << endl;
}
int main() {
	test();
}

类模板与函数模板的区别

  1. 类模板没有自动类型推导的使用方式
void test()
{
 	Person<string, int> p1 = Person<string,int>("zhangsan", 18);
	Person<string, int> p2("lisi", 10);
}
  1. 类模板在模板参数列表可以有多个默认参数
template<typename T1,typename T2=int> //默认类型
class Person {
public:
	Person(T1 name, T2 age);
	T1 m_name;
	T2 m_age;
	void showmess();
};

//调用
Person<string> p1("zhangsan",19);

模板函数中成员函数的创建时机

类模板中的成员函数 并不是一开始就创建的,而是在模板调用时再生成

类模板对象做函数参数

  1. 指定传入类型
//指定传入类型
void showperson1(Person<string,int>&p) {
	cout << "姓名1:" << p.m_name << "年龄1 :" << p.m_age << endl;

}
void test1() {
	Person<string, int > p1("zhangsan", 18);
	showperson1(p1);
}

模板类分文件编写 可以将.h和.cpp中的内容写到一起,将后缀名改成.hpp

类模板与友元

实现步骤

  1. 先在类定义的前面声明每个函数模板
template<typename T1, typename T2>
class Person;
template <typename T1, typename T2>
void show(Person<T1, T2> p)
{
	std::cout << p.m_age << std::endl;
}
  1. 在函数中再次将模板声明为友元。同时在声明中函数名后加<> 这是模板的具体化
template<typename T1,typename T2>
class Person
{
//	加一个空模板的参数列表
	friend void show<>(Person<T1,T2> p);
public:
	Person(T1 name,T2 age);
private:
	T1 m_name;
	T2 m_age;
};

void test() {
	Person<std::string, int> p1("zhangsan", 16);
	show(p1);
	
}

对象和类

指定类设计的第一步是提供类声明。类声明类似结构声明,包括数据成员和函数成员。

  1. 提供类声明(数据成员和函数成员)
  2. 私有部分声明只能通过成员函数进行访问
class person{
    private:
    //数据项目通常放在私有部分
    char name[10];
    int ages;
    public:
    //组成类接口的成员函数放在公有部分
    void eating();
    void sleep();

}

公有部分的内容构成了设计的抽象部分————公有接口

实现类成员函数

  • 成员函数的函数头使用(::)来指出函数所属的类

  • 类方法可以访问类的private组件

:: 作用域解析符

void person ::sleep( );

类成员函数可通过类来调用,需要使用成员运算符.

#include<iostream>
#include<string>
//std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
class Stock     //定义Stock类
{
public:
	void acquire(const std::string& co, long n, double pr);
	void buy(long num, double price);
	void sell(long num, double price);
	void update(double price);
	void show();
private:
	std::string company;
	long shares;
	double share_val;
	double total_val;
	void set_tot() { total_val = share_val * shares; };
};

void Stock::acquire(const std::string& co, long n, double pr)     //
 {
	company = co;
	if (n < 0) {
		std::cout << "no shares";
		shares = 0;
	}
	else
		shares = n;
	share_val = pr;
	set_tot();
}
void Stock::show() {
	using namespace std;
	cout << "company s shares is " << shares << endl;
	cout << "company s shares is " << share_val << endl;

	cout << "company s shares is " << total_val << endl;

}

int main() {
	Stock tencent, ali;   //创建两个Stock对象
	tencent.acquire("baidu", 20, 12.50);
	tencent.show();
	return 0;
}

static关键字的用法

  1. 函数的变量存放在栈区,函数调用完毕分配的空间就会释放。如若想要持续使用函数的变量,可以引入关键字static,函数中的变量可以持续到程序执行完毕。(类比于全局变量,全局变量的缺点就是破坏了此变量的访问范围);
  2. 在C++中,需要一个数据对象为整个类而不是某个对象服务,同时又不破坏类的封装性,可以使用static修饰。

在内存四区中,静态变量存储在全区局,程序结束运行后才会释放。

static的作用
  • 延长了变量的作用时间,一直到程序结束后才释放;
  • static修饰的全局变量,只能在该文件中访问,即使是extern外部声明也不可以;
  • statci修饰的函数,只能在该文件中调用;
  • 静态变量相对于全局变量跟为安全。

静态变量不能被其他文件所用,其他文件可以定义相同的变量名称,不会冲突

全局变量默认是有外部链接性的,作用域是整个工程,其他文件想要访问,可以使用extern

在C++中,静态成员是属于整个类的而不是某个对象。静态成员可以通过双冒号来使用<类名>::<静态成员名>

1. 类的对象可以使用静态函数和非静态函数。
2. 静态函数中不能引用非静态成员变量,非静态函数可以引用静态变量
3. **类的静态成员变量在使用前必须先初始化** `int student::n = 0;`
#include<iostream>
class student {
public:
	static void show1(int n);
	void show2();
private:
	static int n;  //声明静态局部变量
	double s;
};
int student::n = 0; //定义并初始化静态成员变量
 void  student::show1(int m) {
	 student::n = m;
	std::cout << "static的作用" << std::endl;
	std::cout << "static变量n的值: " << std::endl;
}
 void student::show2() {
	 std::cout << "showtime" << std::endl;
 }

int main() {
	student li;
	student::show1(5);
	/*student::n = 10;*/
    li.show1(3); //对象调用静态成员函数
	li.show2();
}

类构造函数

专门由于构造新对象、将值赋给它们的数据成员

名称与类名相同

Stock类的构造函数Stock()

声明类构造函数

Stock(const string &co,long n = 0,double pr = 0.0) //声明类构造函数

定义构造函数

//定义构造函数
Stock ::Stock(const string & co, long n,double pr){

}

与普通函数不同的是,程序声明对象时,将自动调用构造函数

使用构造函数

  1. 显性调用
    Stock stock1 = Stock("zkhx",3,2.1);
  2. 隐性调用
    Stock stock1("zkhx",3,2.1);
    创建对象的同时进行了初始化
    stock1.show();
    构造函数用来创建对象,而不能通过对象来调用

析构函数

~DataTimerTest() 函数前面加~表示析构函数

就像对象被创建时程序将调用构造函数一样,当对象结束生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数,释放内存

(每个类只能有一个析构函数)

	//析构函数
	data_queue::~data_queue() {
		if (m_minBufferData) {
			delete m_minBufferData;
			m_minBufferData = NULL;
		}
		if (m_maxBufferData) {
			delete m_maxBufferData;
			m_maxBufferData = NULL;
		}
	}

如函数使用了指针变量,析构函数中加入delete

delete释放指针原本所指的内存,指针被delete后,如果不置为null,指针就会成为野指针,会在内存乱指一通。,再次调用可能会导致系统崩溃。

对一个非空指针delete后,若没有赋null,再次delete是不可行的

第一次delete指针后,指针的地址并不是空的,同一块内存释放两次会导致崩溃。

总结: delete一个空指针是合法的,为了防止多次delete指针导致程序崩溃,需要养成良好的置空(null)习惯

this指针

  1. 解决名称冲突

    this指针指向 被调用的成员函数 所属的对象

    class Person{
     public:
        Person(int age){
     	   this->age = age;
        }
        Person& addage(Person &p){
     	   this->age +=p.age;
     	   return *this;  //this 指向p2的指针,而*this指向的就是p2这个对象的本体
        }
        int age;
    }
    void test(){
        Person p1(18);
        cout <<p1.age<<std::endl;
    }
    
  2. 返回对象本身用*this 链式编程思想

Person& Person::addage(Person& p)
{
	this->age += p.age;
	return *this;  
}

p2.addage(p1).addage(p2)   //链式编程思想

如果不用引用的方式返回,相当于返回与p2不同的另一个Person(只是age都是20),那么后续的加年龄操作与p2就没有关系了;
返回是值的话,相当于创建了一个新的对象,同时调用了拷贝构造函数

对象数组

通常要创建同一个类的多个对象。声明对象数组的方法与声明标准类型数组相同

stock mystuff[4]; //创建一个对象数组

每个元素(mystuff[0],mystuff[1])都是stock的对象

mystuff[0].update();

可以用构造函数初始化数组元素。在这种情况下,必须为每个元素调用构造函数:

const int STK =4;
Stock stock[STK] = {
	Stock("NanoSmart",12.3,30);
	Stock("zkhx",12,4);
	Stock();
};

运算符重载

对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型

加号运算符重载

实现自定义的加法运算


#include<iostream>
using namespace std;

class Person
{
public:
	//1.成员函数重载+号
  	Person operator+(Person& p) {
  		Person temp;
  		temp.m_a = this->m_a + p.m_a; //this只能用于非静态成员函数
  		temp.m_b = this->m_b + p.m_b;
  		return temp;
  	}
	int m_a;
	int m_b;
};
//2. 全局函数重载
   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;
   }
  Person operator+(Person& p1, int n) {  //运算符重载外加函数重载
	  Person temp;
	  temp.m_a = p1.m_a + n;
	  temp.m_b = p1.m_b + n;
	  return temp;
  }
void test01() {
	Person p1;
	p1.m_a = 10;
	p1.m_b = 10;
	Person p2;
	p2.m_a = 10;
	p2.m_b = 10;
	//成员函数重载本质
	Person p3 = p1.operator+(p2);
	

	//全局函数重载本质
	Person p3 = operator+(p1, p2);
	Person p3 = p1 + p2;
	cout << "p3.m_a的值 " << p3.m_a << endl;
	cout << "p3.m_b的值 " << p3.m_b << endl;
	//运算符重载 也可以发生函数重载
	Person p4 = p1 + 15;  //   Person + int
	cout << "p4.m_a的值 " << p4.m_a << endl;
	cout << "p4.m_b的值 " << p4.m_b << endl;

}
int main() {
	test01();
	return 0;
}

全局函数可以改变运算顺序 成员函数不行

总结1:对于内置的数据类型的表达式的运算符是不可修改的

总结2:不要滥用运算符重载
下面的运算符只能通过成员函数进行重载
= 赋值运算符
( ) 函数调用运算符
[ ] 下标运算符
-> 通过指针访问类成员的运算符

左移运算符重载

实现自定义的输出

  1. <<的第一种重载方式
void operator<<(ostream& cout, Person& p);
	cout << p;

这种重载方式只能加载左右两个重载对象

  1. <<的第二种重载方式
ostream& operator(ostream& cout,Person& p){
	cout << x << y;
	return cout;
}

<<运算符要求左边有一个ostrem对象。所以表达式cout<<p满足这种要求。然而,表达式cout<<x<<y,cout<<x位于<<y的左侧,所以输出语句也要求该表达式是一个ostream类型的对象。
因此,ostream类将operator<<()函数实现为返回一个指向ostream对象的引用

#include<iostream>
using namespace std;
class Person
{
	friend void operator<<(ostream& cout, Person& p);   //友元函数声明

public:
	Person(int a, int b) {  //有参构造函数
		m_a = a;
		m_b = b;
	}
	//成员函数重载左移运算符
private:
	int m_a;
	int m_b;
};

void operator<<(ostream& cout, Person& p) {
	cout << "p.m_a" << p.m_b ;
	
}
ostream& operator<<(ostream& cout, Person& p) {   //链式编程
	cout << "p.m_b" << p.m_b << "hello world" << endl;   //如若想在cout后链接其他输出 需要返回ostream类对象,并在后面加上引用
	return cout;
}
void test() {
	 /*Person p(10,20);*/   //隐式使用构造函数
	 Person p = Person(10, 20); // 显式使用构造函数
	
	cout << p;
	operator(cout,p);
}

int main() {
	test();
}

友元函数

全局函数做友元

成员函数不能改变运算顺序 全局函数可以 但非成员函数不能直接访问类的私有数据。这时候就要引入一个特殊的函数 友元函数

  1. 创建友元函数

创建友元函数的第一步是将其原型放在类声明中,并在前面加上一个friend

friend Person operator*(double m ,Person &p2)

虽然operator放在类声明中,但它不是成员函数 不能使用成员符号调用
operator
不是成员函数 但是它有访问私有变量

  1. 定义函数

友元函数不是成员函数 不能使用类::限定符。定义中不用加friend

class Person {
public:
	
	friend void operator*(double n, Person& p1);
private:
	int m_a;
	int m_b;
};

void operator*(double n, Person& p1) {
	p1.m_a = 10;
	p1.m_b = 23;
	Person total;
	total.m_a = n * p1.m_a;
	total.m_b = n * p1.m_b;
	std::cout << total.m_a << std::endl;
}

类做友元

在想要访问的类中加入友元friend类

#include<iostream>
using namespace std;
class Building;
class Goodgay
{
public:
	Goodgay();
	void visit();
private:
	Building* building;
};
class Building {
	friend class Goodgay;
public:
	Building();
	string m_livingroom ;
private:
	string m_bedroom;
};
Goodgay::Goodgay() {
	building = new Building;
}
Building::Building() {
	this->m_bedroom = "卧室";
	this->m_livingroom = "客厅";
}
void Goodgay::visit() {
	cout << "好朋友正在访问" << building->m_livingroom << endl;
	cout << "好朋友正在访问" << building->m_bedroom << endl;

}
int main() {
	Goodgay li;
	li.visit();
	return 0;
}
成员函数做友元

friend void Goodgay::visit();

class Building {
public:
	friend void Goodgay::visit();   //将成员函数写在要访问的类中
	Building();
	string m_livingroom;
private:
	string m_bedroom;
};

类继承

下级别的成员有上一级的共性,还有自己的特性

减少重复代码

语法: class 子类: 继承方式 父类

继承方式

  1. 公共继承
  2. 保护继承
  3. 私有继承

三种继承法是,父类的私有部分,任何一种方式都不能继承。
保护继承将父类的公有的,保护的都转为子类保护的,私有继承将父类公有的,保护的都转为子类保护的

父类:

class Pro{
public:
	int a;
protected:
	int b;
private:
	int c;
}
公共继承:                         私有继承:                               保护继承:
class A :public Pro{              class B:private Pro{              class C:protected Pro{
public:                           private:                          protected:
	int a;                            int a;                              int a;
protected:                            int b;                              int b;
	int b;                        不可访问:                          不可访问:
不可访问:                              int c;                              int c;
	int c;                        };                                 };
};

在父类中所有非静态成员属性都会被子类继承下去,父类私有成员属性,是被编译器给隐藏了,因此访问不到,但是确实被继承了。

继承中,父类子类构造和析构的顺序

在继承的过程中,子类继承父类,他们的顺序可以类比于栈的过程

先构造父类 在构造子类 接在析构子类 再析构父类

同名成员处理

	Son s;
	std::cout << "使用子类同名变量" << s.m_a << std::endl;
	std::cout <<"子类对象使用父类同名变量"<<s.Base::m_a << std::endl;

子类在继承父类时有同名变量

如果通过子类对象访问 父类同名成员,需要加作用域

总结:

  1. 子类对象可以直接访问父类同名成员
  2. 子类对象加作用域可以访问父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问父类同名函数

同名静态函数与普通函数访问的方式一致,只是要加上static的属性,有两种访问方式1.按类名访问2.按对象访问

多重继承为防止二义性 可以用::作用域

菱形继承

虚基类

#include<iostream>
using namespace std;
class Animal //虚基类
{
public:
	int m_age;
};
class Sheep : virtual public Animal {}; //virtual继承
class Tuo:virtual public Animal{};
class Sheeptuo:public Sheep,public Tuo{};
void test01() {
	Sheeptuo st;
	st.Sheep::m_age = 100;
	st.Tuo::m_age = 200;
	cout << st.Sheep::m_age << endl;
	cout << st.Tuo::m_age << endl;
 	cout << st.m_age << endl;


}
int main() {
	test01();
	return 0;
}

菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费,可以使用virtual加以避免

多态

  1. 静态多态:函数重载 运算符重载
  2. 动态多态:派生类和虚函数实现运行时多态

区别:
静态 编译阶段确定函数地址
动态 运行阶段确定函数地址

动态多态满足的条件

  1. 子类继承父类
  2. 子类重写父类(virtual)虚函数

重写:函数返回值 函数名 参数列表 完全一致

动态多态的使用

父类的指针或引用指向子类的对象

class Animal
{
public:
	virtual void speak() {
		cout << "动物说话" << endl;
	}
};
class Cat :public Animal {
public:
	void speak() {
		cout << "小猫叫" << endl;
	}
};

//动态多态的使用

//父类的指针或引用 执行类对象

1. 方法一 父类的引用指向子类对象

void dospeak(Animal& animal)   //Animal & animal = cat;
{
	animal.speak();
}
void test() {
	Cat cat;
	dospeak(cat);

2. 方法二 父类的指针指向子类对象

void dospeak(Animal* animal)   //Animal * animal = new Cat;
{
	animal->speak();
}
void test() {
	dospeak(new Cat);

什么类型的指针都占用四个字节

加了个virtual 等于加了个指针vfptr(virtual function pointer)

非静态成员函数不属于类的对象上

纯虚函数

多态中,通常父类中的虚函数的实现毫无意义,主要都是调用子类重写,可以把这类函数改成纯虚函数

virtual 返回值类型 函数名(参数列表) = 0;

当类中有了纯虚函数,这个类也称为抽象类

抽象类的特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include <iostream>
using namespace std;
class Base {
public:
	virtual void funct() = 0;   //纯虚函数
};
class Son1 :public Base {
public:
	void funct() {
		cout << "hello " << endl;
	}
};
class Son2 :public Base {
public:
	void fi() {                    //没有重写纯虚函数 该类不能实例化
		cout << "world" << endl;
	}
};
void test() {
// 	Son2 ji;  // !!报错
	Son1* ji = new Son1;
	ji->funct();
}

String类

string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器

find delete insert

string构造函数

string赋值操作

#include<iostream>
#include<string>
using namespace  std;
void test01() {
	std::string str;
	str = "hello world";
	std::cout << str << std::endl;
	std::string str1("hallo");
	std::string str2;
	str2 = 'a';
	std::cout << str2 << std::endl;
	string str5;
	str5.assign("helloworld");
	cout << str5 << endl;
	string str6;
	str6.assign("helloworld", 5);
	cout << str6 << endl;
	string str7;
	str7.assign(str6);
	cout << str7 << endl;
	string str8;
	str8.assign(20, 'w');
	cout << str8 << endl;
}

string的赋值方式很多,operator=这种方式是比较实用的

字符串拼接

string +=str1

string查找和替换

find和rfind replace

void test01() {
	string str1 = "abcdefg";
	int n =str1.find('cf');  //从左往右找  查到了 返回下表索引,没找到 返回-1
	cout << n << endl;
	int j = str1.rfind("de"); //从右往左找
	cout << j << endl;
}
void test02() {
	string strl = "abcdefghi";
	strl.replace(1, 3, "233");   //a索引1到3 替换成233
	cout << strl << endl;
}

字符串的比较

string字符存取

char &operator[] (int n); 通过[]方式取字符

char & at(int n) 通过at方法取字符

void test() {
	string str = "hello";
	//通过[]访问单个字符
	for (int i = 0; i < str.size(); i++) {
		cout << str[i];
	}
	cout << endl;
	// 通过at访问单个字符
	for (int i=0;i<str.size();i++)
	{
		cout << str.at(i);

	}
	cout << endl;

	str[0] = 'x';  //使用[]修改字符串内容
	cout << str;
}

string字符串的插入和删除

插入和删除起始下标都是从0开始的

void test() {
	string str = "hello";
	//插入
	str.insert(3, "liu");
	cout << str << endl;
	//删除
	str.erase(3);
	cout << str << endl;
}

string子串
substr(1,3)

void test1() {
	string email = "zhangsan@sina.com";
	int n = email.find('@');
	cout << email.substr(0, n) << endl;
}

STL模板

vector容器

与数据相似,也成为单端数组(类比于栈)

vector与普通的数组的区别

数组时静态空间,而vector可以动态扩展

动态扩展

并不是在原有的基础上进行拓展,而是找一个更大的空间,将原数据拷贝到新空间,释放原空间

vector迭代器 支持随机访问的迭代器

vector四种构造方法

  1. 默认构造
  2. 区间构造
  3. 拷贝构造
  4. n个元素构造
#include<iostream>
#include<vector>
using namespace std;
void printvector(vector<int> &v) {
	for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}
void test() {
	//默认构造
	vector<int>v1; 

	for (int i = 0; i < 50; i++) {
		v1.push_back(i);

	}
	printvector(v1);

	//通过区间的方式进行构造
	vector<int>v2(v1.begin(), v1.end());
	printvector(v2);

	//N个elem方式构造
	vector<int>v3(10, 123);//(个数,元素)
	printvector(v3);
	//拷贝构造
	vector<int>v4(v3);
	printvector(v4);

vector的增删查

struct review
{
	string title;
	int rating;
};
bool Fillreview(review& re);
void printbook(vector<review>& b1);
int main() {
	vector<review>book;
	review temp;
	while (Fillreview( temp)) {
		book.push_back(temp);
	}
	printbook(book);
	vector<review>oldbook(book);
	printbook(oldbook);
	book.erase(book.begin() + 1, book.begin() + 3); //vector的删除
	printbook(book);
	book.insert(book.begin(), oldbook.begin() + 1, oldbook.begin() + 2);//vector的插入
	printbook(book);
	
	
	book.swap(oldbook);
	printbook(book);
	return 0;
	system("pause");
}

bool Fillreview(review& re)
{
	cout << "please enter a book name" << endl;
	getline(cin,re.title);
	if (re.title == "quit")
	{
		return false;
	}
	else
		cin >> re.rating;
	if (!cin)
		return false;
	while (cin.get()!='\n')
	{
		continue;
	}
	return true;
}

void printbook(vector<review>& b1)
{
	for (vector<review>::iterator it =b1.begin();it!=b1.end();it++)
	{
		cout << (*it).rating <<" "<<(*it).title<< endl;
	}
	cout << "--------------------" << endl;
}

支持随机访问的容器 都可以用标准算法sort进行排序

list双向循环链表

由于链表的存储方式并不是连续的内存空间,因此链表list中的迭代器只支持前移和后移,属于双向迭代器

list 不支持随机存取

List有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效,这在vector是不成立的

STL中list和vector是两个最常使用的容器,各有缺点

list.reverse(); 双向链表的反转

list.sort() 双向链表的排序

font 首元素

back 尾部元素

list自定义类型排序

自定义类型 类类型or结构类型 要先指定类型规则

语法

//指定排序规则   如果年龄相等 按照体重排序
bool compareage(Person& p1, Person& p2) {
	if (p1.m_age == p2.m_age) {
		return p1.m_weight < p2.m_weight;
	}
	else
	{
		return p1.m_age < p2.m_age;
	}
	
}
#include<iostream>
#include<list>
#include<string>
using namespace std;
class Person {
public:
	Person(string name, int age,float weight) {
		this->m_name = name;
		this->m_age = age;
		this->m_weight = weight;
	}
	string m_name;
	int m_age;
	float m_weight;
};
void printlist(list<Person>& p) {
	for (list<Person>::const_iterator it=p.begin();it!=p.end();it++)
	{
		cout << (*it).m_name << it->m_age <<" " << it->m_weight << " ";
	}
	cout << endl;
}

//指定排序规则   如果年龄相等 按照体重排序
bool compareage(Person& p1, Person& p2) {
	if (p1.m_age == p2.m_age) {
		return p1.m_weight < p2.m_weight;
	}
	else
	{
		return p1.m_age < p2.m_age;
	}
	
}
void test() {
	Person p1("zhangsan", 56,50.2);
	Person p2("lisi", 16,53.2);
	Person p3("wangwu", 16,59.9);
	Person p4("zhaoliu", 17,45.9);
	Person p5("gouba", 19,49.3);
	list<Person>ll;
	ll.push_back(p1);
	ll.push_back(p2);
	ll.push_back(p3);
	ll.push_back(p4);
	ll.push_back(p5);
	printlist(ll);
	ll.sort(compareage);
	printlist(ll);
}
int main() {
	test();
	return 0;
}

关联容器set,mutiset

所以的元素都会在插入时自动排序

set与multiset属于关联容器,底层结构是二叉树实现

区别

  1. set不允许容器有重复的元素
  2. multiset允许容器有重复的元素

set的插入使用的是insert,没有push_back这个方法

empty();

size();

swap();

插入删除

insert()

erase(迭代器) erase(容器元素)

clear(); 清空

查找和统计

find(); //查找key是否存在,返回该元素的迭代器不存在 返回set.end()

cout(); 要么是0 要么是1

set容器排序规则

set的默认排序规则是从小到大,改变排序规则从大到小

利用仿函数,改变排序规则

map/multimap容器

map所有元素都是一对

第一个key值(索引的作用)第二个valu值(实值)

所有元素都会根据元素的键值自动排序

关联容器,二叉树实现

可以通过key值快速的找到value值

map与multimap 的区别 是能不能插入重复的key值

void printmap(map<int, int>& ma) {
	for (map<int,int>::const_iterator it =ma.begin();it!=ma.end();it++)
	{
		cout << "key= " << (*it).first << " value=" << (*it).second << endl;
	}
	cout << endl;
}
void test() {
	//map容器
	map<int, int>m;    //k v  创建map容器
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 28));
	m.insert(pair<int, int>(4, 20));
	m.insert(pair<int, int>(6, 21));
	m.insert(pair<int, int>(9, 320));
	m.insert(make_pair(10, 22));       //make_pair 插入
	printmap(m);
	map<int, int>m2(m);   //拷贝构造函数
	printmap(m2);
	map<int, int>m3;
	m3 = m2;          //赋值
	printmap(m3);
	cout << m[6] << endl;     //可以使用[]查找;
	cout<<m.size()<<endl;
	//删除
	m.erase(m.begin()); //按照迭代器删除
	m.erase(6);   //按照key值删除
	printmap(m);
	}

查找 统计

find() 查找key值是否存在,不存在返回set.end;

map<int, int>::iterator it = m.find(9);
	cout << (*it).first <<it->second<<endl;

cout() 0or1

使用仿函数改变map的默认排序 由大到小

仿函数 传入的时候传入一个数据类型,在类中重载了()函数
重载()

class Mycompare {
public:
	bool operator()(int v1, int v2) const  自定义排序规则
	{ 
		return v1 > v2;
	}
};
void printmap(map<int, int,Mycompare>& ma) {
	for (map<int, int>::const_iterator it = ma.begin(); it != ma.end(); it++)
	{
		cout << "key= " << (*it).first << " value=" << (*it).second << endl;
	}
	cout << endl;
}
void test() {
	//map容器
	map<int, int,Mycompare>m;    //k v  创建map容器
	m.insert(pair<int, int>(1, 10));
	m.insert(pair<int, int>(2, 28));
	m.insert(pair<int, int>(4, 20));
	m.insert(pair<int, int>(6, 21));
	m.insert(pair<int, int>(9, 320));
	m.insert(make_pair(10, 22));
	printmap(m);
}

函数对象

重载函数调用操作符的类,其对象成为函数对象

对()重载的函数 也叫仿函数

#include<iostream>
using namespace std;
//1. 函数对象在使用时,可以像普通函数那样调用,可以有参数,可以有返回值
class Mycompare {
public:
	int operator()(int v1, int v2) {
		return v1 + v1;
	}
};

void test() {
	Mycompare myadd;
	cout << myadd(10, 10) << endl;
}
//2. 函数对象可以有自己的状态
class Myprint {
public:
	Myprint() {
		count = 0;
		}
	void operator()(string test) {
		cout << test << endl;
		this->count++;
	}
	int count;  //内部自己的状态
};

void test1() {
	Myprint myprint;
	myprint("helloworld");
	myprint("helloworld");
	myprint("helloworld");
	cout << "myprint调用次数" << myprint.count << endl;
}

void doprint(Myprint& mp, string test) {
	mp(test);
}
//3. 函数对象可以作为参数传递
void test2() {
	Myprint my1;
	doprint(my1, "hello C++");
}
内建仿函数

算术仿函数

#include<iostream>
#include<functional>    //使用内建函数对象时,需要引入头文件 #include<functional>
using namespace std;
void test() {
	//negate 一元仿函数 取反函数
	negate<int>n;
	cout << n(50) << endl;
}
void test1() {
	//plus 二元仿函数 加法
	plus<int>p;
	cout << p(10, 20) << endl;
}

关系仿函数

自定义降序仿函数定价与greater<int>() -> 需要使用#include<functional>

自定义

class Mycompare {
public:
	bool operator()(int v1, int v2) const  自定义排序规则
	{ 
		return v1 > v2;
	}
};

STL-常用算法

算法主要是由头文件 最大的一个,涉及比较、交换、查找、遍历仿函数

常用遍历算法

for_each(iterator_beg;iterator_end;函数 仿函数);

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;


//仿函数
class Myprint {
public:
	 void operator()(int val) {
		cout << val;
	}
};

void printv(int val) {
	cout << val<<endl;
}
void test() {
	vector<int>v1;
	for (int i=0;i<10;i++)
	{
		v1.push_back(i);
	}
	//for_each算法
	for_each(v1.begin(),v1.end(), printv);
	for_each(v1.begin(), v1.end(), Myprint());

}

transform(v.begin(),v.end(),target.begin(),fun_函数)

搬运的目标容器必须提前开辟空间,否则无法正常搬运

class Transform {
public:
	int operator()(int v) {
		return v;
	}

};
class Print {
public:
	void operator()(int val) {
		cout << val << endl;
	}
};
void test() {
	vector<int>v1;
	for (int i=1;i<10;i++)
	{
		v1.push_back(i);
	}
	vector<int>target;
	target.resize(v1.size());  //目标容器需要提前开辟空间
	transform(v1.begin(), v1.end(), target.begin(), Transform());
	for_each(target.begin(), target.end(), Print());
}

常用的查找算法

  1. find

find(iterator.beg(),iter.end(),value)
find 可以在容器中找指定的元素,返回值是迭代器
自定义类型 需要写仿函数 对"=="就行重载

class Person {
public:
	Person(string name, int age) {
		this->m_name = name;
		this->m_age = age;
	}
	string m_name;
	int m_age;
	bool operator ==(Person p2 ) {
		if (this->m_age==p2.m_age&&this->m_name==p2.m_name)
		{
			return true;
		}
	}
};
  1. find_if

按条件查找

vector<Person>::iterator it =find_if(p.begin(), p.end(), fun_仿函数);

  1. binary_search

二分查找 对于有序的数列
bool binary_search(it.beg(),it.end(),查找值)

	bool ret = binary_search(v1.begin(), v1.end(), 8);
	cout << ret << endl;

  1. count

统计自定义类型时 需要配合重载operator==

自定义数据类型 就在自定义的类型里面就行仿函数的定义

内建型 就添加写一个类对运算符进行重载

  1. count_if
class mix14  //谓词
{
public:
	bool operator()(int v1) {
		return v1 > 12;
	}
};
	int num =count_if(v1.begin(), v1.end(), mix14());

常用排序算法

  1. sort(it.beg(),it.end(),谓词)
class ree {
public:
	bool operator()(int v1, int v2) {
		return v1 > v2;
	}
};
	sort(v1.begin(), v1.end(), ree());

  1. 洗牌算法

random_shuffle(it.beg(),it.end())

	srand((unsigned int)time(NULL));   //实时时间
	vector<int>v1;
	for (int i=1;i<10;i++)
	{
		v1.push_back(i);
	}
	random_shuffle(v1.begin(), v1.end());
	for_each(v1.begin(), v1.end(), print);

  1. merge算法
void test() {
	vector<int>v1;
	vector<int>v2;
	for (int i=1;i<10;i++)
	{
		v1.push_back(i);
		v2.push_back(i + 2);
	}
	vector<int>target;
	target.reserve()
	target.resize(v1.size() + v2.size());     //目标容器定义大小
	
	merge(v1.begin(), v1.end(), v2.begin(), v2.end(), target.begin());
	for_each(target.begin(), target.end(), myprint);
}

  1. reverse算法

reverse(target.begin(), target.end()); 容器元素反转

常用的拷贝和替换算法

copy 容器内指定范围的元素拷贝到另一容器总·

	vector<int>tar2;
	tar2.resize(6);
	copy(target.begin(), target.begin() + 6, tar2.begin());

replace

replace(tar3.begin(), tar3.end(), 5, 500); //将区间的5 替换成500

replace_if

//谓词
class mix5 {   
public:
	bool operator()(int val) {
		return val > 5;
	}
};

 	replace_if(tar3.begin(), tar3.end(), mix5(), 3000);

两个容器元素合并,并储存在另一个容器

内存模型和名称空间

单独编译

将组件函数放在独立的文件中,然后将他们链接成可执行的程序。如若要修改文件,只需重新头文件,然后将它与其他文件编译版本链接,程序的管理更为便捷

组织策略 可以把程序拆分为三个部分

  1. 头文件:编写函数原型 结构声明

eg:

#ifndef STOCK_H_
#define STOCK_H_
struct polar
{
	double distance;
	double angle;
};
struct rect
{
	double x;
	double y;
};

polar rect_to_polar(rect xypos);
void show_polar(polar dapos);
#endif
  1. 源代码文件:函数定义

eg:

#include <iostream>
#include<cmath>
#include "stock.h"
polar rect_to_polar(rect xypos) {
	using namespace std;
	polar answer;
	answer.distance = sqrt(xypos.x * xypos.x + xypos.y * xypos.y);
	answer.angle = atan2(xypos.x, xypos.y);
	return answer;
}
void show_polar(polar dapos) {
	using namespace std;
	cout << "angle" << dapos.angle << endl;
	cout << "distance" << dapos.distance << endl;
}

  1. 源代码文件:main()和其他使用这些函数的函数放在第三个文件
#include <iostream>
#include "stock.h"
using namespace std;
int main() {
	rect rplace;
	polar pplace;
	cout << "enter the x and y values: ";
	while (cin >> rplace.x >> rplace.y) {
		pplace = rect_to_polar(rplace);
		show_polar(pplace);
		cout << "next two number(q to quit):";

	}
	cout << "bye!\n";
	return 0;
}

1.高效的组织策略,编写另一程序,需要使用这些函数时,只需包含头文件,并将函数文件添加到项目列表或make列表中即可。
2.这种组织方式也是面向对象(OOP)的体现

头文件包含的内容:

  • 函数原型
  • 结构声明
  • 类声明
  • 内联函数(~)
  • 模板声明(~)

头文件编写

#ifndef HEAD_H_  //根据include名选择名称,并加上下划线 以防止被其他地方定义
#define HEAD_H_
…… //file content
#endif

名称空间 -ing

名称可以是函数、变量、结构、类以及类的成员,随着项目的增大,名称相互冲突的可能性也将增加

namespace jack{
    double pail;
    void fetch();
    int pal;
    struct well{}
}

名称空间是开放的(open),

  1. .
namespace jack{
    char name[10];
} 
  1. jack名称空间为fetch()函数提供原型。可以在文件后面再次使用Jack名称空间定义这个函数
namespace jack{
    void fetch(){
        ……
    }
}

using声明和using编译

  • using声明由using和被限定的名称组成

using jack::pail; // 一个using声明

using声明将特定的名称添加到它所属的声明区域中。

int main(){
    using jack::pail;
    cin >> pail;
    cin >> ::pail; //读入进去一个pail全局变量
}

using声明使一个名称可用,而using编译指令使所有名称都可用。

  • using编译指令由using namespace和名称空间组成

using namespace jack; //一个using编译声明

using namespace

总结: 导入名称时,首选使用作用域解析运算符或using声明的方法。

const成员函数

const关键字放在函数的括号后面 stock类中的函数声明

void show() const //函数声明

函数定义的开头

void stock :: show() const //函数定义 不被改变

只要类不修改调用对象,就应将其声明为const

effective C++

永远在使用对象之前先将它初始化

构造函数做好使用成员初始列,而不要在构造本体函数内使用赋值操作

  • 34
    点赞
  • 201
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C信息奥赛是一项关于信息技术和计算机科学的竞赛,其知识点涉及广泛且深入。以下是一些C信息奥赛的知识点: 1. 编程语言:C信息奥赛要求熟悉C语言的语法、数据类型、运算符和控制结构。同时,还需要了解有关C语言库函数和文件操作的知识。 2. 数据结构和算法:C信息奥赛考察了解和应用各种数据结构,如数组、链表、栈、队列和树等。此外,还要求掌握排序算法(如冒泡排序、快速排序和归并排序)和查找算法(如二分查找)。 3. 网络编程:了解网络编程是非常重要的,因为它涉及到通过网络进行数据传输和通信。要掌握TCP/IP协议、套接字编程和网络通信的基本原理。 4. 数据库:对于数据库的基本概念和技术也需要有所了解。包括关系型数据库(如MySQL、Oracle)和非关系型数据库(如MongoDB、Redis)的原理和操作方法。 5. 操作系统:熟悉操作系统的概念和原理,包括进程管理、内存管理和文件系统等。特别是对于Linux操作系统的使用和命令行操作有一定的要求。 6. 算法设计与分析:需要具备设计和分析算法的能力,了解动态规划、贪心算法、回溯算法和分治法等基本算法的思想和应用。 7. 计算机图形学:具备一定的图形学知识,掌握图形学基本概念、基本算法和编程库的使用。 总结起来,C信息奥赛的知识点主要包括编程语言、数据结构和算法、网络编程、数据库、操作系统、算法设计与分析以及计算机图形学等方面的知识。同时,熟练运用这些知识,能够解决各种实际的计算机科学问题,并在竞赛中取得好成绩。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值