C++基础知识
C++的基本数据类型
与Java不同的是,C++的基本数据类型所占用的字节数是根据平台来决定的,要知道确定的大小需要通过sizeof(type)来获取。
在64位操作系统下C++基本数据类型所占用的大小为:
| 基本数据类型 | 字节 |
|---|---|
| int | 4 |
| unsigned int | 4 |
| short | 2 |
| unsigned short | 2 |
| long | 4 |
| unsigned long | 4 |
| char | 1 |
| w_char | 2 |
| unsigned char | 1 |
| float | 4 |
| double | 8 |
| long double | 8 |
| bool | 1 |
数组
-
静态数组和动态数组
静态数组在内存中位于栈区,是在定义时就已经在栈上分配了固定大小,在运行时这个大小不能改变,在函数执行完以后,系统自动销毁:
int a[10];动态数组是malloc或者new出来的,位于内存的堆区,它的大小是在运行时给定,并且可以改变.
int *a; a = new int[10];注意:动态数组,其创建麻烦,使用完必须由程序员自己通过free或者delete释放,否则严重会引起内存泄露
-
数组指针
int *a; a = new int[10];a:是指向第一个数组元素的指针
*a:获取指针指向的数组的值
指针
基础知识
指针:指针是一块内存,它存放的内容是指向内存的地址
-
指针的创建
int i = 10; int *p = &i; -
解引用
指解析并返回内存地址中保存的值
int i = 10; int *p = &i; //解引用 //p指向一个内存地址,使用*解出这个地址的值 即为 10 int pv = *p; //修改地址的值,则i值也变成100 //为解引用的结果赋值也就是为指针所指的内存赋值 *p = 100; -
野指针
未赋值的指针叫野指针
int val = 10; int *p;//野指针 -
悬空指针
悬空指针:指针最初指向的内存已经被释放了的一种指针
-
指针占用内存的大小
在32位中指针占用4字节,在64位中为8字节
//32位: sizeof(p) == 4; //64位: sizeof(p) == 8; -
指针运算
int val = 10; int *p = &val; //指针可以进行 + - ++ -- 操作 p++;//表示指针指向下一个内存位置,这里移动 sizeof(int)字节 -
为什么需要指针指向char、int等数据类型
因为这些数据类型表示的是从指针存储的地址中一次能够读写的数据字节数。
例如
char *d; short *e; long *f;假设d、e、f的值都是100。在这种情况下,使用d时就能够从编号100的地址中读写1个字节的数据,使用e时就是2个字节(100-101地址)的数据,使用f时就是4个字节(100-104地址)的数据。
-
为什么创建指针时需要初始化
//错误示例 int *p;//指针p的值可能为任何值,有可能性和其他地址冲突 *p = 100;//如果产生冲突,赋值会修改冲突地址的值 //正确示例 int *p = &0;//初始化 *p = 100;创建指针未初始化时,该指针的值可能为任何值,就有可能会和其他的地址的值产生冲突;如果产生冲突,赋值会修改冲突地址的值。
数组指针和指针数组
//数组指针,指向数组的指针
int array[2][3] = {{1,2,3},{4,5,6}};
int (*p)[3] = array;
//获取 5
int val = *(*(p+1)+1);
//指针数组,一个数组里面的值的指针
int val = 1;
int *array1[3] = {&val,&val,&val};
数组名是数组首元素的地址;c语言中,二维数组的占用的内存(例如
int p[2][2])和一维数组int p[4]相同,二维数组的内存也是连续的。
const char *, char const *, char * const,char const * const的区别
char[] temp = "hello";
//从右到左来看
const char *p1 = temp;//指向不可变数组,即不能通过 p1 来修改数组的值
char const *p2 = temp;//和 p2 相同
char * const p3 = temp;//不可变指针,指针不能指向其他的地址,可以通过 p3 来修改数组的值
const char * const p4 = temp;//不可变指针指向不可变数组,指针不能指向其他的地址,不能通过 p3 来修改值
char const * const p5 = temp;//和 p4 相同
多级指针
int i = 1;
//存放i的地址
int *p = &i;
//存放 p 指针的地址
int **p1 = &p;
//获取p1指针的值
printf(*p1);
//获取p指针的值
printf(**p1);
经常使用的指针一般为一级、二级指针;很少使用三级及以上的指针
引用
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
引用的使用
int val = 10;
//声明引用
int& p = val;
//把引用作为参数
void func(int& v){
}
func(p);
//把引用作为返回值
int& func_1(){
//不能返回局部变量,但是可以返回static修饰的变量
static int p = 1;
return p;
}
引用 和 指针的区别
- 不存在空引用,引用必须连接到一块合法的内存。但可以存在空指针。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
类
类的定义和使用示例如下:
class Student {
int i; //默认 private
public:
Student(int i,int j,int k):i(i),j(j),k(k){}; //构造方法
~Student(){}; //析构方法
private:
int j;
protected:
int k;
};
Student student(1,2,3); //调用构造方法,在栈中分配内存
//出方法释放student 调用析构方法
//在堆中分配内存
Student *student = new Student(1,2,3);
//释放内存
delete student;
student = 0;
访问修饰符
在C++中只有public、protected、public三种访问修饰符,默认不加修饰符的情况下是private。与Java不同的是,所有的修饰符的变量都可以被友元函数访问。
| 访问修饰符 | 作用 |
|---|---|
| private | 可以被该类中的函数、友元函数访问。 不能被任何其他访问,该类的对象也不能访问。 |
| protected | 可以被该类中的函数、子类的函数、友元函数访问。 但不能被该类的对象访问。 |
| public | 可以被该类中的函数、子类的函数、友元函数访问,也可以被该类的对象访问。 |
变量
在C++,有局部变量、成员变量、静态变量、静态局部变量、全局变量、静态全局变量.
-
局部变量
在函数或一个代码块内部声明的变量,称为局部变量。
void func(){ int a = 0;//局部变量 ... } -
静态局部变量
静态局部变量在方法执行完后不会被消除,可以延长局部变量的生命周期,并且只初始化一次
void func(){ static int data = 0; ... } -
成员变量
在类中声明的变量称为成员变量。如下变量i、j、k是成员变量。
class Student { int i;//默认修饰符是private public: Student(int i,int j,int k):i(i),j(j),k(k){}; //构造方法 ~Student(){}; //析构方法 private: int j; protected: int k; }; -
静态变量
//Instance.h class Instance { public: ... private: static Instance *instance; }; #endif //Instance.cpp #include "Instance.h" Instance* Instance::instance = 0;//必须初始化 ...在C++类中声明静态变量,就必须在cpp文件中对它进行初始化,否则会报错
-
全局变量
在所有函数外部声明的变量,称为全局变量。全局变量不是在类中声明的,在类中声明的是成员变量。
int index = 0; ...全局变量是C语言特性,可以在整个文件中使用。使用
extern关键字,还能在其他文件使用。 -
静态全局变量
全局变量声明
static关键字,就是静态全局变量。static int index = 0; ...与全局变量的区别就是不能在其他文件中使用。
方法(函数)
构造函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。 构造函数的名称与类的名称是完全相同的,构造函数可用于为某些成员变量设置初始值。
class Student {
public:
Student();//无参构造函数
Student(int i,int j,int k);//带参数的构造函数
Student(int i,int j,int k):i(i),j(j),k(k);//使用初始化列表来初始化,等同于下面的
Student(int i,int j,int k){
this->i = i;
this->j = j;
this->k = k;
}
...
};
析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
class Student {
public:
~Student(){}; //析构方法
};
声明方法
-
非成员函数
//void : 返回类型 //func :方法名 // data : 方法参数 void func(int data){ //函数体 } -
成员函数
//Student.h class Student{ public: ... int getGrade(int id); } //Student.cpp #include "Student.h" int Student::getGrade(int id){ ... }
成员函数和非成员函数的区别:成员函数是在类中定义的函数,而非成员函数就是普通函数,即不在类中定义的函数,其中非成员函数比较典型的是友元函数
-
静态方法
//Instance.h class Instance { public: static Instance* getInstance();//声明静态方法 private: static Instance *instance; }; #endif //Instance.cpp #include "Instance.h" Instance* Instance::instance = 0; Instance* Instance::getInstance() {//实现静态方法 //C++11以后,编译器保证内部静态变量的线程安全性 if (!instance) { instance = new Instance; } return instance; }
方法参数
-
函数参数的类型
-
传值调用:把参数的值复制给函数的形式参数。修改形参不会影响实参
-
引用调用:形参为指向实参地址的指针或者指向原变量的引用,可以通过指针或者引用修改实参。
-
-
不定参数
和Java类似,c++的方法的参数也有不定参数。代码如下:
//先引入头文件<stdarg.h>
#include <stdarg.h>
//创建函数,注意第一个参数是必须的,不限定类型;
//第二个参数表示不定参数
int add(int num,int ...)
{
va_list valist;
int sum = 0;
// 初始化 valist指向第一个可变参数 (...)
va_start(valist, num);
for (size_t i = 0; i < num; i++)
{
//访问所有赋给 valist 的参数,
int j = va_arg(valist, int);
printf("%d\n", j);
sum += j;
}
//清理为 valist 内存
va_end(valist);
return sum;
}
方法指针(函数指针)
函数指针是指向函数的指针变量。定义如下:
void println(char *buffer) {
printf("%s\n", buffer);
}
//接受一个函数作为参数
//void(*p)(char*) void表示返回值 p表示这个函数 char*表示函数参数
void say(void(*p)(char*), char *buffer) {
p(buffer);
}
//使用
void(*p)(char*) = println;
//使用函数指针
p("hello");
//传递参数
say(println, "hello");
函数指针的应用,比如回调函数。
//typedef 创建别名 由编译器执行解释
//typedef unsigned char u_char;
typedef void(*Fun)(char *);
Fun fun = println;
fun("hello");
say(fun, "hello");
//类似java的回调函数
typedef void(*Callback)(int);
void test(Callback callback) {
callback("成功");
callback("失败");
}
void callback(char *msg) {
printf("%s\n", msg);
}
test(callback);
指针函数:指针函数就是指返回指针的函数
常量函数
函数后写上const,表示不会也不允许修改类中的成员。
class Student {
int i;
public:
Student() {}
~Student() {}
// 常量函数
void setName(char* _name) const {
//错误 不能修改name 去掉const之后可以
name = _name;
}
private:
int j;
char *name;
protected:
int k;
};
拷贝函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。如果在类中没有定义拷贝构造函数,编译器会自行定义一个。
class Line
{
public:
Line( const Line &obj); // 默认拷贝构造函数
...
};
-
浅拷贝
当类中没有指针的时候,可以使用浅拷贝。浅拷贝不需要声明拷贝函数,直接使用默认的就行。
-
深拷贝
当类中有指针类型时,需要定义拷贝函数,示例:
class Line { public: Line( const Line &obj){ // 拷贝构造函数 ptr = new int; *ptr = *obj.ptr; // 拷贝值 } ... private: int *ptr; }; -
拷贝构造函数调用时机
//初始化时
Line line;
Line copyLine(&line);
//作为参数时
void draw(Line line);
//作为返回值时
Line get();
重载函数
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分为函数重载和运算符重载。
-
函数重载
void print(int i) { cout << "整数为: " << i << endl; } void print(double f) { cout << "浮点数为: " << f << endl; } -
操作符重载
C++允许重定义或重载大部分 C++ 内置的运算符,其中重载的函数名是由关键字 operator 和其后要重载的运算符符号构成的。 示例如下:
示例1,重载
+运算符:class Test2 { public: int i; }; //定义非成员函数进行 + 重载 Test2 operator+(const Test2& t21, const Test2& t22) { Test2 t; t.i = t21.i + t22.i; return t; } Test2 t21; Test2 t22; t21.i = 100; t22.i = 200; Test2 t23 = t21 + t22; cout << t23.i << endl;示例2,重载
new和delete运算符:void *operator new (size_t size) { cout << "新的new:" << size << endl; return malloc(size); } void operator delete(void *p) { //释放由p指向的存储空间 cout << "新的delete" << endl; free(p); } ... ...
允许重载的运算符如下:
继承
class A:[private/protected/public] B表示A继承B。A是基类,B称为子类或者派生类。
| 方式 | 说明 |
|---|---|
| public | 基类的public、protected成员也是派生类相应的成员,基类的private成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。 |
| protected | 基类的公有和保护成员将成为派生类的保护成员 |
| private | 基类的公有和保护成员将成为派生类的私有成员,默认为private继承 |
单继承
class Parent {
public:
Parent(string name){
this->name = name;
}
void test() {
cout << "parent" << endl;
}
};
class Child : Parent {
public:
Child(string name):Parent(name){
this->name = name;
}
void test() {
// 调用父类 方法
Parent::test();
//使用 __super::test();调用父类方法
__super::test();
cout << "child" << endl;
}
};
多继承
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…实现多继承;示例如下:
class Father {
public:
Father(string name) {
this->name = name;
this->age = 0;
}
Father(string name, int age) {
this->age = age;
this->name = name;
}
virtual void work() {
cout << name<<"在工作赚钱" << endl;
}
virtual void sleep() {
cout << "爸爸在睡觉" << endl;
}
private:
string name;
int age;
};
class Mother {
public:
Mother(string name) {
this->name = name;
this->age = 0;
}
Mother(string name, int age) {
this->age = age;
this->name = name;
}
virtual void cook() {
cout << name<<"在做饭" << endl;
}
virtual void sleep() {
cout << "妈妈在睡觉" << endl;
}
private:
string name;
int age;
};
class Child : public Father, public Mother{
public:
Child(string name) :Father(name), Mother(name) {
this->name = name;
this->age = 0;
}
Child(string name,int age) :Father(name,age), Mother(name,age) {
this->name = name;
this->age = age;
}
void printInfo() {
cout << name << " " << age << endl;
}
virtual void sleep() {
__super::Father::sleep();
cout << name << "在睡觉" << endl;
}
private:
string name;
int age;
};
int main() {
Child child("小明");
child.cook();
child.work();
child.printInfo();
child.sleep();
}
多态
静态多态
静态多态(静态联编)是指在编译期间就可以确定函数的调用地址,通过函数重载和模版(泛型编程) 实现
class Parent {
public:
void test() {
cout << "parent" << endl;
}
};
class Child :public Parent {
public:
void test() {
cout << "child" << endl;
}
};
Parent *c = new Child();
// 编译期间 确定c 为 parent 调用parent的test方法
c->test();
//修改Parent为virtual 虚函数 动态链接,告诉编译器不要静态链接到该函数
virtual void test() {
cout << "parent" << endl;
}
//动态多态 调用Child的test方法
c->test();
动态多态
动态多态(动态联编)是指函数调用的地址不能在编译器期间确定,必须需要在运行时才确定 ,通过继承+虚函数 实现。构造函数任何时候都不可以声明为虚函数,析构函数一般都是虚函数,释放先执行子类再执行父类
- 纯虚函数
类似于Java的抽象方法。
class Parent {
public:
//纯虚函数 继承自这个类需要实现 抽象类型
virtual void test() = 0;
};
class Child :public Parent {
public:
void test(){}
};
泛型(模板)
函数模板
函数模板能够用来创建一个通用的函数。以支持多种不同的形參。避免重载函数的函数体反复设计。
template <typename T> //也可以使用 template <class T>
T max(T a,T b)
{
// 函数的主体
return a > b ? a : b;
}
//代替了
int max(int a,int b)
int max(float a,float b)
类模板(泛型类)
为类定义一种模式。使得类中的某些数据成员、默写成员函数的參数、某些成员函数的返回值,能够取随意类型.常见的 容器比如 向量 vector 或 vector 就是模板类。
//定义两个不同的模板
template<class E,class T>
class Queue {
public:
T add(E e,T t){
return e+t;
}
};
Queue<int,float> q;
q.add(1,1.1f) = 2.1f
非类型模版参数
非类型模板参数可以是整形、枚举或者外部链接指针
template<typename T, int MAXSIZE>
class Stack{
Private:
T elems[MAXSIZE];
...
};
int main()
{
//使用
Stack<int, 20> stack;
};
模板注意事项
- 使用typedef时产生的问题
- 返回class中通过
typedef定义的变量时,需要添加typename
//Queue.h
#pragma once
template <typename T>
class Queue {
public:
T data;
typedef unsigned int queue_size;
queue_size size();//返回使用typedef定义的queue_size
};
//Queue.cpp
template<class T>//声明方法和变量时必须添加
T data;
template <class T>
typename Queue<T>::queue_size Queue<T>::size() {//需要加上typename,否则无法编译
return 0;
}
- 导入问题
当模板声明和模板定义分开时,需要同时导入.h和.cpp文件。
//Queue.h
#pragma once
template <typename T>
class Queue {
public:
T data;
typedef unsigned int queue_size;
queue_size size();
};
//Queue.cpp
template<class T>
T data;
template <class T>
typename Queue<T>::queue_size Queue<T>::size() {
return 0;
}
//Main.cpp
#include"Queue.h"//需要同时导入Queue.h Queue.cpp
#include"Queue.cpp"
#include<iostream>
#include <vector>
#include<stdarg.h>
int main() {
...
}
I/O操作
C语言的I/O操作
导入头文件stdio.h,使用函数原型:FILE * fopen(const char * path, const char * mode); 就可以获取文件对象,对文件进行操作。
mode模式如下:
| 模式 | 描述 |
|---|---|
| r | 打开一个已有的文本文件,允许读取文件。 |
| w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。 |
| a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
| r+ | 打开一个文本文件,允许读写文件。 |
| w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
| a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
示例1:往文件写入内容
FILE *f = fopen("xxxx\\t.txt","w");
//写入单个字符
fputc('a', f);
//写入字符串
char *txt = "123456";
fputs(txt, f);
//格式化并写入文件
fprintf(f,"%s",txt);
fclose(f);
示例2:读取文件内容
FILE *f = fopen("xxxx\\t.txt","w");
//读取一个字符
char c = fgetc(f);
//读取 遇到第一个空格字符停止
char buff[255];
fscanf(f, "%s", buff);
printf("1: %s\n", buff);
//最大读取 255-1 个字符
fgets(buff, 255, f);
printf("2: %s\n", buff);
fclose(f);
示例3:读取和写入二进制数据
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp;
char c[] = "This is runoob";
char buffer[20];
/* 打开文件用于读写 */
fp = fopen("file.txt", "w+");
/* 写入数据到文件 */
fwrite(c, strlen(c) + 1, 1, fp);
/* 查找文件的开头 */
fseek(fp, 0, SEEK_SET);
/* 读取并显示数据 */
fread(buffer, strlen(c)+1, 1, fp);
printf("%s\n", buffer);
fclose(fp);
return(0);
}
C++的I/O操作
导入<iostream> 和 <fstream>
| 数据类型 | 描述 |
|---|---|
| ofstream | 输出文件流,创建文件并向文件写入信息。 |
| ifstream | 输入文件流,从文件读取信息。 |
| fstream | 文件流,且同时具有 ofstream 和 ifstream 两种功能。 |
char data[100];
// 以写模式打开文件
ofstream outfile;
outfile.open("XXX\\f.txt");
cout << "输入你的名字: ";
//cin 接收终端的输入
cin >> data;
// 向文件写入用户输入的数据
outfile << data << endl;
// 关闭打开的文件
outfile.close();
// 以读模式打开文件
ifstream infile;
infile.open("XXX\\f.txt");
cout << "读取文件" << endl;
infile >> data;
cout << data << endl;
// 关闭
infile.close();
异常
void test1()
{
throw "测试!";
}
void test2()
{
throw exception("测试");
}
try {
test1();
}
catch (const char *m) {
cout << m << endl;
}
try {
test2();
}
catch (exception &e) {
cout << e.what() << endl;
}
//自定义
class MyException : public exception
{
public:
virtual char const* what() const
{
return "myexception";
}
};
常用库
string字符串
int main()
{
char data[] = {'a','b','\0'};
//创建string实例
string s = "name";
string* s1 = new string("str");
string* s2 = new string(data);
cout << s << endl;
cout << *s1 << endl;
cout << *s2 << endl;
//字符串拼接
string s3 = *s1 + *s2;
cout << s3 << endl;
//字符串复制,地址不同
string s4 = s;
cout << &s << "\n"<< &s4 << endl;
string s5 = "hello";
string* s6 = new string("hello");
//字符串占用字节的大小
cout << "字符串的大小为:"<< s5.size()<< endl;
cout << "字符串的大小为:" << s6->size() << endl;
cout << "字符串的长度为:" << s5.length() << endl;
cout << "字符串的长度为:" << s6->length() << endl;
//插入字符串
s5.insert(s5.length(), " world");
cout << s5 << endl;
//删除字符串,从 start 开始,删除 n 个
int start = 1;
int n = 2;
s6->erase(start, n);
cout << *s6 << endl;
//替换字符串,从 start 开始,替换 n 个
s5.replace(5, s5.length(), "");
cout << s5 << endl;
//查找字符串
s.find("na"); //查找s中第一次出现s1的位置,并返回(包括0)
s.rfind("na"); //查找s中最后次出现s1的位置,并返回(包括0)
s.find_first_of("e"); //查找在s1中任意一个字符在s中第一次出现的位置,并返回(包括0)
s.find_last_of("name"); //查找在s1中任意一个字符在s中最后一次出现的位置,并返回(包括0)
s.find_first_not_of("name"); //查找s中第一个不属于s1中的字符的位置,并返回(包括0)
s.find_last_not_of("name"); //查找s中最后一个不属于s1中的字符的位置,并返回(包括0)
//字符串截取
s.substr(0,1);
//字符串是否为空
s.empty();
//获取字符串数组
const char* array = s.c_str();
system("pause");
return 0;
}
cctype字符函数库
可以较好的判断是字母、数字、其他
| 函数名称 | 返回值 |
|---|---|
| isalnum() | 如果参数是字母数字,即字母或数字,该函数返回true |
| isalpha() | 如果参数是字母,该函数返回真 |
| isblank() | 如果参数是空格或水平制表符,该函数返回true |
| iscntrl() | 如果参数是控制字符,该函数返回true |
| isdigit() | 如果参数是数字(0~9),该函数返回true |
| isgraph() | 如果参数是除空格之外的打印字符,该函数返回true |
| islower() | 如果参数是小写字母,该函数返回true |
| isprint() | 如果参数是打印字符(包括空格),该函数返回true |
| ispunct() | 如果参数是标点符号,该函数返回true |
| isspace() | 如果参数是标准空白字符,如空格、进纸、换行符、回车、水平制表符或者垂直制表符,该函数返回true |
| isupper() | 如果参数是大写字母,该函数返回true |
| isxdigit() | 如果参数是十六进制的数字,即0~9、a |
| tolower() | 如果参数是大写字符,则返回其小写,否则返回该参数 |
| toupper() | 如果参数是小写字母,则返回其大写,否则返回该参数 |
容器
常用的数据结构包括:数组array, 链表list, 树tree, 栈stack, 队列queue, 散列表hash table, 集合set、映射表map 等等。容器便是容纳这些数据结构的。这些数据结构分为序列式与关联式两种,容器也分为序列式容器和关联式容器。
序列式容器/顺序容器
元素排列次序与元素无关,由元素添加到容器的顺序决定
| 容器 | 说明 |
|---|---|
| vector | 支持快速随机访问 |
| list | 支持快速插入、删除 |
| deque | 双端队列 允许两端都可以进行入队和出队操作的队列 |
| stack | 后进先出LIFO(Last In First Out)堆栈 |
| queue | 先进先出FIFO(First Input First Output)队列 |
| priority_queue | 有优先级管理的queue |
向量(vector) 、列表 (list)、双端队列(deque)
向量(vector):连续存储的元素
列表 (list):由节点组成的双向链表,每个结点包含着一个元素
双端队列(deque):连续存储的指向不同元素的指针所组成的数组
以上三种容器操作基本一样
基本操作:
#include<iostream>
#include <vector>
using namespace std;
int main() {
//创建vector对象
vector<int> vec_1;
vector<int> vec_2(1); //1个元素
vector<int> vec_3(6,1); //6个值为 1 的元素
vector<int> vec_4(vec_3); //使用容器初始化
//增加
vec.push_back(2);//往末尾插入元素
vec.insert(vec.begin()+0, 0);//往第一个位置插入数据
vec.insert(vec.begin() + 2, 6);//往第三个位置插入数据
//删除
vec.erase(vec.begin());//删除第一个元素
vec.erase(vec.begin(), vec.begin() + 2);//删除指定范围的元素
vec.clear();//清空容器
//改变指定元素
vec[0] = 2;
//查找指定位置的元素
cout << vec[2] << endl;
//首尾元素
vec_3.front()
vec_3.back()
//遍历1
for (int i = 0; i < vec.size(); i++) {
cout << vec[i];
}
//遍历2
vector<int>::iterator it = vec.begin();
for (; it < vec.end(); it++) {
cout << *it;
}
//迭代器
//获得指向首元素的迭代器 模板类,不是指针,当做指针来使用
vector<int>::iterator it = vec.begin();
//遍历元素
for (; it < vec.end(); it++)
{
cout << *it << endl;
}
//begin和end 分别获得 指向容器第一个元素和最后一个元素下一个位置的迭代器
//rbegin和rend 分别获得 指向容器最后一个元素和第一个元素前一个位置的迭代器
//注意循环中操作元素对迭代器的影响
vector<int>::iterator it = vec.begin();
for (; it < vec.end(); )
{
//删除值为2的元素
if (*it == 2) {
vec.erase(it);
}
else {
it++;
}
}
}
栈(stack)
后进先出的值的排列
stack<int> s;
//入栈
s.push(1);
s.push(2);
//弹栈
s.pop();
//获取栈顶
cout << s.top() << endl;
队列(queue)
先进先出的值的排列
queue<int> q;
q.push(1);
q.push(2);
//移除最后一个
q.pop();
//获得第一个
q.front();
//最后一个元素
cout << q.back() << endl;
优先队列(priority_queue )
元素的次序是由所存储的数据的某个值排列的一种队列
//最大的在队首
priority_queue<int>;
//在vector之上实现的
priority_queue<int, vector<int>, less<int> >;
//vector 承载底层数据结构堆的容器
//less 表示数字大的优先级高,而 greater 表示数字小的优先级高
//less 让优先队列总是把最大的元素放在队首
//greater 让优先队列总是把最小的元素放在队首
//less和greater都是一个模板结构体 也可以自定义
class Student {
public:
int grade;
Student(int grade):grade(grade) {
}
};
struct cmp {
bool operator ()(Student* s1, Student* s2) {
// > 从小到大
// < 从大到小
return s1->grade > s2->grade;
}
bool operator ()(Student s1, Student s2) {
return s1.grade > s2.grade;
}
};
priority_queue<Student*, vector<Student*>, cmp > q1;
q1.push(new Student(2));
q1.push(new Student(1));
q1.push(new Student(3));
cout << q1.top()->grade << endl;
关联式容器
关联容器中的元素是按关键字来保存和访问的 支持高效的关键字查找与访问
集合(set)
由节点组成的红黑树,每个节点都包含着一个元素,元素不可重复
set<string> a;
set<string> a1={"fengxin","666"};
a.insert("fengxin"); // 插入一个元素
a.erase("123"); //删除
键值对(map)
由{键,值}对组成的集合
map<int, string> m;
map<int, string> m1 = { { 1,"Lance" },{ 2,"David" } };
//插入元素
m1.insert({ 3,"Jett" });
//pair=键值对
pair<int, string> p(4, "dongnao");
m1.insert(p);
//insetrt 返回 map<int, string>::iterator : bool 键值对
//如果 插入已经存在的 key,则会插入失败
//multimap:允许重复key
//使用m1[3] = "xx" 能够覆盖
//通过【key】操作元素
m1[5] = "yihan";
cout << m1[5].c_str() << endl;
//通过key查找元素
map<int, string>::iterator it = m1.find(3);
cout << (*it).second.c_str()<< endl;
// 删除
m1.erase(5);
//遍历
for (it = m1.begin(); it != m1.end(); it++)
{
pair<int, string> item = *it;
cout << item.first << ":" << item.second.c_str() << endl;
}
//其他map================================
unordered_map c++11取代hash_map(哈希表实现,无序) 需要无序容器,高频快速查找删除,数据量较大用unordered_map; 需要有序容器,查找删除频率稳定,在意内存时用map。
其他
类型转换
在C语言中可以通过(需要转换的类型)原类型来实现强制转换;在C++增加了const_cast、static_cast、dynamic_cast、reinterpret_cast四种新式转换。
const_cast
用来修改类型的const或volatile属性
const char *a;
char *b = const_cast<char*>(a);
char *a;
const char *b = const_cast<const char*>(a);
static_cast
应用场景:
- 基础类型之间互转。如:
float转成int、int转成unsigned int等 - 指针与void之间互转。如:
float*转成void*、Bean*转成void*、函数指针转成void*等 子类指针/引用与父类指针/引用进行转换。
class Parent {
public:
void test() {
cout << "p" << endl;
}
};
class Child :public Parent{
public:
void test() {
cout << "c" << endl;
}
};
Parent *p = new Parent();
Child *c = static_cast<Child*>(p);
//输出c
//Parent test加上 virtual时才输出 p
c->test();
dynamic_cast
主要 将基类指针、引用 安全地转为派生类.在运行期对可疑的转型操作进行安全检查,仅对多态有效
//基类至少有一个虚函数
//对指针转换失败的得到NULL,对引用失败 抛出bad_cast异常
Parent *p = new Parent();
Child *c = dynamic_cast<Child*>(p);
if (!c) {
cout << "转换失败" << endl;
}
Parent *p = new Child;
Child *c = dynamic_cast<Child*>(p);
if (c) {
cout << "转换成功" << endl;
}
reinterpret_cast
对指针、引用进行原始转换
float i = 10;
//&i float指针,指向一个地址,转换为int类型,j就是这个地址
int j = reinterpret_cast<int>(&i);
cout << hex << &i << endl;
cout << hex << j << endl;
cout<<hex<<i<<endl; //输出十六进制数
cout<<oct<<i<<endl; //输出八进制数
cout<<dec<<i<<endl; //输出十进制数
关键字
-
inline
inline关键字修饰的函数是内联函数。内联函数是在编译期间将调用该内联函数的地方都转换为函数的实现,避免了频繁调用函数对栈内存重复开辟所带来的开销。
缺点:
1. 如果函数体内代码比较长,使用内联将导致内存消耗代价较高 2. 内联会导致代码膨胀 3. 声明inline关键字只是对编译器的一个建议,编译器不一定会采用 -
restrict
只能在C99标准的C程序中使用,C++不支持。
-
register
用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的
&运算符(因为它没有内存位置)。 -
extern
用来引用其他文件声明的全局变量和函数;使用
extern "C" {...}表示使用C语言风格来编译代码 -
auto
auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符
auto f=3.14; //double auto s("hello"); //const char* auto z = new auto(9); // int* auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型 -
typedef
typedef 关键字用来为类型取一个新的名字
type int INT -
explicit和implicit
explicit关键字只能修饰构造函数,表示该构造函数是显示的;implicit则表示隐式的,默认就是隐式的。
预处理器
预处理器不是编译器,但是它是编译过程中一个单独的步骤。 简单来说预处理器是一个文本替换工具,所有的预处理器命令都是以井号(#)开头
#define
#define用来定义宏。注意:预处理器是一个文本替换工具,而宏就是文本替换
//宏一般使用大写区分
//宏变量
//在代码中使用 A 就会被替换为1
#define A 1
宏函数
//宏函数
#defind test(i) i > 10 ? 1: 0
//其他技巧
// # 连接符 连接两个符号组成新符号
#define DN_INT(arg) int dn_ ## arg
DN_INT(i) = 10;
dn_i = 100;
// \ 换行符
#define PRINT_I(arg) if(arg) { \
printf("%d\n",arg); \
}
PRINT_I(dn_i);
//可变宏
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"NDK", __VA_ARGS__);
//陷阱
#define MULTI(x,y) x*y
//获得 4
printf("%d\n", MULTI(2, 2));
//获得 1+1*2 = 3
printf("%d\n", MULTI(1+1, 2));
优点:文本替换,每个使用到的地方都会替换为宏定义。不会造成函数调用的开销(开辟栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆栈。) 缺点:生成的目标文件大,不会执行代码检查
-
宏函数与内联函数的区别
内联函数有类型检查同时也可以debug,但是内联函数不能包含复杂的控制语句,while、switch,并且内联函数本身不能直接调用自身。如果内联函数的函数体过大,编译器会自动的把这个内联函数变成普通函数。
-
typedef 和 #define 的区别:
#define 用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
-
typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
-
typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
-
预定义宏
| 预定义宏 | 描述 |
|---|---|
| VA_ARGS | 代表不定参数 |
#include
作用:导入头文件,相对于Java的import
-
导入方式
- 使用
#include<xxx.h>方式导入的是系统的库文件 - 使用
#include"xxx.h"方式导入的是项目目录下的文件
- 使用
-
与Java的import关键字的区别
import不会导入我们需要导入类中import的类;而include会引入。例如,在Java中导入了ArrayList,如果要使用List,则还需要导入List,即使ArrayList中导入过List。在c/c++中则会在引入ArrayList的同时引入List,但是这种方式容易产生重复引入的情况
头文件的作用:头文件可以让我们控制方法的访问,同意被访问的方法定义到头文件中,而不同意的方法则不放到头文件里
条件编译
| 条件编译 | 说明 |
|---|---|
| #if | if |
| #elif | else if |
| #else | else |
| #endif | 结束 if |
| #ifdef | 如果定义了宏 |
| #ifndef | 如果未定义宏 |
NULL 与 nullptr
//__cplusplus 代表c++
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
在C++中,NULL被定义为 0 ;而在 C 中 NULL则定义为 ((void *)0) 空指针。为了避免二义性,C++ 11 引入了 nullptr 代表空指针
C语言动态内存申请
- malloc
到堆中申请内存,其内存的初始值是随机的垃圾值
//当数据无法确定 或者 比较庞大 需要使用动态内存申请 在堆中
int *di1 = (int*)malloc(size);
//memset 初始化内存值为 0
memset(di1, 0, size);
- calloc
到堆中申请内存,其内存的初始值是NULL
// 申请内存并将内存初始化为 null
//第一个参数:元素的数目
//第二个参数:元素的大小
int *di2 = (int*)calloc(10, sizeof(int));
- realloc
对malloc申请的内存进行大小的调整.
char *a = (char*)malloc(10);
realloc(a,20);
特别的:alloca在栈申请内存,因此无需释放.例如
int *p = (int *)alloca(sizeof(int) * 10);
malloc和free 和new和delete
malloc申请的内存,需要使用free来释放;类似的new申请的内存也需要delete来释放。
内存布局
-
BSS段 :通常是指用来存放程序中 未初始化的全局变量、静态变量(全局变量未初始化时默认为0)的一块内存区域
-
数据段(DATA段) :通常是指用来存放程序中 初始化后的全局变量和静态变量
-
代码段 :通常是指用来存放程序中 代码和常量
-
堆 :通常是指用来存放程序中 进程运行时被动态分配的内存段 ( 动态分配:malloc / new,者动态释放:free / delete)
-
栈 :通常是指用来存放程序中 用户临时创建的局部变量、函数形参、数组(局部变量未初始化则默认为垃圾值)也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。它是由操作系统分配的,内存的申请与回收都由OS管理。
命名空间
namespace是c++的命名空间相当于Java的package
- 创建命名空间
namespace A{
void a(){}
}
- 命名空间的使用
错误 : a();
// :: 域操作符
正确: A::a();
//当然也能够嵌套
namespace A {
namespace B{
void a() {};
}
}
A::B::a();
//还能够使用using 关键字
using namespace A;
using namespace A::B;
当全局变量在局部函数中与其中某个变量重名,那么就可以用::来区分
int i;
int main(){
int i = 10;
printf("i : %d\n",i);
//操作全局变量
::i = 11;
printf("i : %d\n",::i);
}
友元
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
友元函数
class Student {
int i;
public:
Student() {}
~Student() {}
void setName(char* _name) {
name = _name;
}
friend void printName(Student *student);
private:
int j;
char *name;
protected:
int k;
};
void printName(Student *student) {
//能够使用student中私有的name属性
cout << student->name << endl;
}
Student *student = new Student;
student->setName("Lance");
printName(student);
友元类
class Student {
int i;
public:
Student() {}
~Student() {}
void setName(char* _name) {
name = _name;
}
friend void printName(Student *student);
//友元类
friend class Teacher;
private:
int j;
char *name;
protected:
int k;
};
class Teacher {
public:
void call(Student *student) {
//能够使用student中私有的name属性
cout << "call:" << student->name << endl;
}
};