C++课程笔记
A better C
C++ is a better C. 本文首先对C++的一些基础语法进行说明,然后介绍面向对象的用法,最后有些STL和异常处理相关的补充。
重载
同名函数不同参数
//overloading,支持重载,同名函数,不同参数
void print(int i){}
void print(char *str){}
但是,同名同参不同返回值是不允许的
默认参数
void fun(int i, int j, int k=3){}
//注意不能写成 void fun(int i, int j=3, int k)
一旦直接给参数,则调用函数时可以传2个参数(k取值为3)或3个参数。第一个默认参数之后必须默认(最容易默认的要放在最后)
-
函数声明时默认参数只能出现一次,如果头文件中定义了默认参数,那么源文件不允许定义
/*.h文件*/ void funct(int mode = 0); /*.cpp文件*/ void funct(int mode = 0); //错误,重新定义的默认参数 void funct(int mode); //正确
.lib : .exe使用static lib 时直接包起来,直接使用exe即可;使用dynamic link library(dll)时需要将exe和lib一起放在一块才能使用
占位参数
占位参数就是,函数声明或定义时,某个形参只有类型声明,而没有参数名。
- 声明时是占位参数,但是定义时补上了参数名
//声明
int funct(int a, int )
//定义
int funct(int a, int b) {
return a + b;
}
- 用途
void fun(int){}
//调用程序者需要给出实际值
方便更新函数,程序;当函数名字和参数相同时,但就是两个不同的函数,此时可以通过占位参数辨别两个不同的函数。
macro
四种形式:
-
常量constant
#define PI 3.14 //消除没有物理意义的常数
-
小函数, tiny function, 目的是要减小函数调用的开销。 (不带有循环的函数是小函数)
#define ARRAY_SIZE(a) sizeof(a)/sizeof(a[0]) #define ADD(a,b) (a+b) //小心括号危机,要写成(a+b)
-
预编译控制宏,又称为开关
以
#if
,#ifdef
,#ifndef
开头,#elif
和#else
作为衔接,以#endif
作为结尾#define LOCAL_VER //这个define定义在头文件里,可以通过注释掉该代码实现开关 int main(){ #ifdef LOCAL_VER ... #else ... #endif } //注意 #include <a> //头文件可以放任何东西, include意为复制,也可以重复包含多个头文件 #include <a> //为解决重复包含问题(变量重复定义会报错),在每个头文件这样写 #ifndef MY_H #define MY_H ... #endif //举例 #ifdef ID // equals #if define ID /* ---------- */ #ifndef ID // equals #if !define ID
头文件不参与编译,在编译前已经被粘贴过来;但是在头文件(.h文件)里,如果一个函数定义是用c语言写的,在.c文件中实现,那么c++编译器不能直接编译没有extern修饰的该函数,头文件(.h)最好这样写:
#ifndef MY_H
#define MY_H
#ifdef __cplusplus //任何c++编译器都会定义此标识
extern "C" //表示该函数定义是c语言写成的
{
#endif
void md5();//该函数是在.c文件中被实现,因此,如果.c文件编译,使用的是c编译器;如果.cpp调用了这个函数,那么会加上extern "C", 用处理c语言的方法处理这个函数;也就是说,extern “C” 表示外部函数定义在C源文件中
#ifdef __cplusplus
}
#endif
#endif
- 一个C++程序示例:
#include <iostream>
//调用c语言的函数时,不写.h,在文件前加c
#include <cstdio>
using namespace std;//声明一个命名空间std,之后所有出现std命名空间的方法如cout时,不用写成std::cout,直接写cout就可以使用
int main(){
cout << 1 << endl;//endl为换行符
cout << "hello" << endl;
}
- 补充:c的struct定义时其大小以最大的数据类型为对齐,如:
#pragma pack(1)//若加此句,则以合适的大小存储,强制对齐
struct TestSize{
int i;
int j;
double d;
char c;
}
//如果没有#pragma这句话
sizeof(TestSize)//大小为24字节,i和j分别为4字节,共用8字节
//如果有
sizeof(TestSize)//大小为17字节
struct Test{
int a:1;
int b:1;
};//位结构,
extern, external link,修饰变量,(变量默认的可见范围时文件域),表示引用外部的变量。函数是默认外连接的,可见范围是全局域,写与不写extern都行。
函数可多次声明,但只能定义一次。
面向对象
C++支持面向对象。面向对象有三大特征:封装、继承、多态
封装
对象有三种属性:private, public, protected。其中protected是对子类public,对其他类private。
另外,这些属性是对于类来说的,也就是说,不同的对象,如果是同一个类,那么是可以互相直接访问的。
class
析构函数
一个类中最多只有一个析构函数,如果不定义的话,默认调用缺省析构函数。
- 栈区对象会在函数结束时自己主动执行析构函数,而堆区的对象(通过new出来的)不会。就算运行到主函数结束(
return
之前),堆区对象的析构函数也不会被执行,只有delete
才可以触发析构函数。
int main() {
Student s; //自动执行析构
return 0;
}
int main() {
Student *p = new Student();
delete p; //只有执行了这一句,才会调用student的析构函数。(类似于free)
return 0;
}
- 当我们在类中有指针变量时,我们需要在自定义的析构函数中主动为指针变量释放空间(通过delete实现)。
class Student{
int id;
char* name;
public:
Student(){
name = new int;
cout << "generate" << endl;
}
~Student(){
delete name;
cout << "disappear" << endl;
}
};
引用
借值之名,行指针之实。是一个类似于安全的指针,不允许野引用,一旦绑定了一个值后,不会再修改。
传引用的好处:1.避免拷贝构造后再传;2.可以间接带回返回值;3.便于实现多态;4.
常见用法:
- 方法参数用引用,更安全,如
swap(int& a, int& b)
; - 方法返回值用引用,有两种用途:返回值作左值;返回this指针的内容。(注意,方法返回时,如果返回的不是引用,会调用拷贝构造)
- 举例如下:
int& setValue(int i){
int& ref = ref[i];//有一全局数组
return ref;
}//该函数可以这么用:setValue(1) = 12;
void fun(int& i){
i++;
}
int main()
{
int a = 10;
fun(a); //这里会改变a的值
int& r1 = a;//引用必须连接到一块合法的内存,r1的地址&r1一直会等于&a,不能更改
&r1 = &b;//error
r1++;//a = 11 ,使用引用时就像使用值
int b =11;
r1 = b; //a=b,将b的值赋给a
cout << a << endl;
return 0 ;
}
拷贝构造
C++的类默认支持用一个已存在的类初始化一个新建的类。
Student s1(10);
Student s2(s1);//默认为浅拷贝构造
- bitwise copy浅拷贝,就是将数据以字节的形式拷贝,虽然开辟了一块内存空间,但如果是指针,传的是地址,两下拷贝完成后两个 指针指向 同一个 地址,非常危险。
- logical copy深拷贝,避免出现两个指针指向同一个地址的情况,我们需要深拷贝
class Test{
int i;
int *j;
public:
Test(int ai, int aj);
Test(const Test& t);//实现深拷贝,重写了拷贝构造函数
};
Test::Test(const Test& t){//const means 只允许读t,不允许改变t
this->i = t.i;
this->j = new int(*(t.j));
}
Test::Test(int ai, int aj){
i = ai;
this->j = new int(aj);
}
Test t1(1,2);
Test t2(t1);//拷贝构造,在java中叫克隆,默认会有浅拷贝构造方法
如果类构造方法中有new(说明有指针成员),一定要深拷贝。函数传值就是在传拷贝,就是在调用拷贝构造方法,所以永远不要传值(对于class类型),可以传地址(传引用)。
如不能写成
void fun(Test t)
,应该写成void fun(Test& t)
。最好是将拷贝构造方法声明在private中,但不定义,这样就不会出现浅拷贝了,当然,如果希望拷贝的话,就public。不要pass by value, 要pass by address
static
-
static var, 静态局部变量, 一次构造,永不析构,直到程序运行结束,放在全局变量区(不是堆)。如函数中定义
static int i;
,但作用域还是在当前函数 -
static fun()
,隐藏此函数,由全局域限制为文件域,不允许外界使用。函数默认是全局域,全局变量默认是文件域。这也就是说为什么文件使用其他文件定义的变量时需要extern
。 -
类
class
中static修饰变量,则该变量为全类所有对象之间共享的。注意,静态属性不允许构造,只能一次性的初始化,如class s{static int i}; int s::i = 1;
-
类
class
中static
修饰函数,表明函数的意义与对象无关,可以直接通过类名去调用(对象也行)。但是,在这个函数里,只能使用静态变量,而且函数里没有this指针。
class Student{
public:
static int i;
static void fun();
};
void fun(){//注意这里不要加static
i++;
}
int main(){
Student s;
s.fun();//通过对象调用
Student::fun();//通过类调用
}
设计模式:单件模式,singleton,目的:创建一个对象,而且世界上只存在1个
class Single{ static Single* self; Single();//私有构造函数 public: static Single* getInstance(); }; Single* Single::self = null; Single* Single::getInstance(){ if(self==null){ self = new Single(); } return self; } int main(){ Single *s = Single::getInstance(); }
const
-
const parameter of function
,只允许读,不允许写(表示只是Input,不允许写; 没有const修饰的参数一般表示output),如void fun(const int *p)
,如果执行(*p)++
会报错 -
const return value
修饰函数返回值。如果一个函数返回值是自定义类型,未经const修饰,允许一串调用;返回值是内置类型,building type,自动为常量class Const{ public: const Test* fun2();//返回值是常量 }; const Test* Const::fun2(){ Test *p = new Test; return p; } int main(){ Const c; c.fun2()->fun();//不允许这样,常量做了左值 }
-
const datamember
,表示数据成员只读。- 对于
const
数据成员来说,初始化主要有以下两种方法——- 使用类内初始值
- 使用构造函数的初始化列表
如果同时使用以上两种方式,则已初始化列表中的值为最终初始化结果
- 不能在构造函数和其他成员函数内部对
const
数据成员进行赋值!
class Const{ const int i;//属于本类的常量 enum{ tcp,udp };//这也是为类定义常量,tcp=1,udp=2 };
- 对于
-
const function member
,使常量对象也可以调用函数。- 一般来说,被
const
修饰的对象是不可以调用函数的,这是为了防止函数会修改const
对象中的值。但是如果真的需要其调用函数怎么办(前提是被调用的函数不能修改对象的值)? - 我们可以将被调用的函数用
const
来限定,这样就可以让const
对象调用该函数了。写法如下
- 一般来说,被
class Const{
public:
void fun() const;
};
void Const::fun() const{
//内部不允许做写入操作,但可以读
}
int main(){
const Const c1;
c1.fun();//如果fun不加const,会报错。
}
new\malloc
new\delete in C++ is operator, new 为内存分配运算符, new = malloc + constructor, delete = ~析构+free
注意不要交叉使用。
因此malloc, free不会调用构造函数和析构函数。
运算符重载
运算符重载,在类内部重新定义+
, -
, *
, []
, new
, delete
等运算符,(重载new
时,触及到内存分配方式),目的是让使用者能够圆滑地使用类。
重载运算符写法如下:
[返回值类型] operator [运算符]( 形参表) {
//其实,operator [运算符]就相当于函数名,因此和普通函数其实是一样的
}
-
重载+
class Account{ int balance; public: Account(int ab){ balance = ab; } Account& operator + (int money); //必须加&,否则发生拷贝构造 Account operator + (const Account& b);//不能加&,否则返回一个未知数 friend Account operator + (const Account& a, const Account& b);//将全局函数变成友元函数,内部可以直接对本类成员进行访问 }; //重载+ Account& Account::operator + (int money){ this->balance += money; return *this; } //重载 +,用于把两个对象相加 Account Account::operator+(const Account& b)//注意,返回值不应该写成Account&,否则a的作用域只局限在本函数中,返回一个不确定的东西 { Account a(0); a.balance = this->balance + b.balance; return a; } //重载+为一个全局函数,使用时编译器发现a+b时将其转换为operator+(a, b); //该函数其实相当于上面的函数,同时出现时调用顺序我也不知道 Account operator+(const Account& a, const Account& b){ Account ret(0); ret.balance = a.balance + b.balance; return ret; } int main(){ Account a(10); a+100;//相当于a=a+100 Account b = a + a;//发生拷贝构造,定义一个变量为它赋初值发生拷贝构造 //但是,如果这样写,不会调用拷贝构造函数,编译器会默认给出一个重载=的函数,但注意,这样子是浅拷贝的,也就是说,指针成员是不安全的。 //Account b; //b = a; }
-
重载++
class Account{
int balance;
public:
Account(int ab){
balance = ab;
}
Account& operator ++();//前++
Account operator ++(int);//用占位符作参数,区分前++和后++
};
//重载后++,必须是(int)
Account Account::operator ++ (int){
Account early = *this;//浅拷贝this
this->balance++;
return early;
}
//重载前++
Account& Account::operator ++ (){
this->balance++;
return *this;
}
int main(){
Account a(10);
a++;
++a;
}
- 重载
[]
其他
-
方法重写(override): 子类对父类完全相同的方法进行重新编写
方法重载(overloading):一个类中参数不同但是其他完全相同的方法
-
Cpp构造有多种方法,可以new出对象再赋给指针,也可以直接定义分配空间,如
type *t = new t([param])
或type t([param])
, t为自己取得变量名
但是有一种特殊情况,无参构造type t();
为了与函数声明区分,应该写为type t;
或type *t = new type();
或type *t = new type;
-
free指针格式:
if(p != NULL) { free(p); p = NULL; }
-
返回引用不会进行拷贝而是直接将引用返回,返回值则是进行拷贝再将拷贝返回,注意同样不要返回局部变量的引用,否则会出问题(局部变量生命周期有限)
-
总的例子
class Test{
private:
int i;//attribute data member
int *j;//32位系统指针大小为4B
public:
void initialize(int ii);//function member
//constructor构造函数,与类同名,任意参数,没有返回值,为类对象给初始值
Test();
Test(int ii, int aj);
//destructor,释放分配的空间(new)
void clean();//这是自己定义的
~Test();//析构函数。不能重载;不会返回任何值,也不能带有任何参数。
};
void Test::clean(){
delete j;
}
Test::Test(){
cout << "create a new Test" << endl;
}
Test::Test(int ii, int aj){
i = ii;
this -> j = new int(aj);
}
Test::~Test(){
delete j;
cout << "delete" << endl;
}
//::指“的”
void Test::initialize(int ii){
i=ii;
//equals this->i=ii;隐藏了this指针,指向调用对象
}
Test s(10);//实例化,line是对象
Test s2;//无参构造时,不能加括号
Test *t = new Test(1);
s.initialize(10);
对象所占空间就是attribute所占的空间,和c的struct相似。但是对于c++,struct 默认public,class默认private。
- 内存注意小点:
内存分有代码区(只读,包括程序中所有的常量),全局变量区(程序运行之初,开拓一大片空间,在main函数之前),runtime memory(栈区,堆区)。
stack栈区:连续空间,局部变量和函数参数在这里
heap堆区:new的对象在这里,空间离散,dynamic memory,动态内存分配,如:不能确定对象个数;人为控制对象的生命周期,举例:
struct s{
...
}
struct Car{
s *ss;
}
void fun(){
Car *p = new Car;
p -> ss = new s;
/*不能写成这样
s s1;
Car *p = new Car;
p -> ss = s1;
生命周期不匹配
*/
}
注意,如果函数返回值是一个局部对象的话,会发生拷贝构造;不能返回局部对象的引用,因为会析构,返回 什么不确定了就。
继承
继承与组合,解决reuse问题
继承
继承:共性(子类继承父类所有数据成员和方法)与特性(子类可以重写方法等)的关系,可以有多个父类,不同于java。
写法:
class [子类名] : public [父类名] {};
私有继承:继承时父类没有加public或者加了private,则子类无法使用父类的public方法 。私有继承没有用。
class Computer{
int price;
char *brand;
public:
int get_price();
void maintenance();
};
int Computer::get_price(){
return this->price;
}
class MacBook : public Computer{//inheritance
public:
void maintenance();//特性
};
void MacBook::maintenance(){//必须(其实不调也行)调用父类的该方法,满足共性的要求
//子类调用父类的方法,这么写
Computer::maintenace();
}
int main(){
Macbook mac;
Computer cp;
}
构造初始化列表
构造参数初始列表,具体方式是”在构造函数后以一个冒号:开始,接着是以逗号分隔的数据成员列表(注意,写的是数据成员名,不是类名),每个数据成员后面跟一个放在括号中的初始化式“。
参数的顺序无所谓。构造时的顺序不是以该列表的顺序,而是以class内部定义成员时的顺序。
初始化父类对象,和子类的member,详情见下:
当父类只有一个带参构造函数时,构造子类时需要显式调用。
//父类
class Base {
private:
int i;
public:
Base(int i);
};
Base::Base(int i) {
this->i = i;
cout << "constructor of Base" << endl;
}
//子类
class Dervied : public Base {
private:
int j;
public:
Dervied(int i, int j);
};
Dervied::Dervied(int i, int j) : Base(i){
this->j = j;
cout << "constructor of Derived" << endl;
}
//这样写也行
Derived::Derived(int i, int j) : Base(i), j(j){
....
}
//当然,如果调用的是父类的无参构造,可以默认不写
其他
-
is a & is like a :当子类的方法总数定义与父类一致,则子类is a 父类;若子类有不同于父类的方法,那么子类 is like a 父类
-
如果父类有overloading的重载函数,子类如果没有动,就可以用;但一旦子类重定义了其中的一个重载函数,应该都重定义。
-
一个子类对象,就是由一个父类对象和子类对象特性成员组成的。
sizeof
为父类和子类之和 -
初始化子类时,先调用父类的构造函数;当子类消亡时,先调用子类的析构函数,后调用父类的析构函数
-
多重继承:继承多个类,会出现各种问题,不建议使用。子类可以直接使用父类中所有
public
函数和成员变量。但如果两个父类有同名的函数时,编译器会报错
class Derived : pubilc Base1, public Base2{};
组合
组合其实描述的就是在一个类里内嵌了其他类的对象作为成员的情况,它们之间的关系是一种包含与被包含的关系。在下面的例子中,Car类包含了 Engine。
class Engine{
int part;
public:
void run();
}
class Car{
Engine e;//组合
public:
void run();//Car's run() 和 Engine's run没有任何关系
};
void Car::run(){
e.run();//reuse
....
}
要先创建 一个Car,先调用Engine的构造函数(默认调用无参构造)。在java中这也是很自然的,如果Engine只有一个 有参构造,则Car的构造函数必须要构造e,可以用构造初始化列表:
Car::Car(): e(1){};//here is e, not Engine
注意,如果成员是一个指针,那就不需要构造
多态
多态就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
向上转型
向上转型,有继承关系时,需要父类时可以传递子类。
向上转型有两种方式:
- 用父类的引用类型来引用子类对象
- 用父类的指针类型来指向子类对象
int main() {
//第一种转换
Child a;
Father& b = a;
//第二种转换
Child *a = new Child();
Father *b = a;
}
- 向上转型后只能调用父类的数据成员和方法。
class Father {
public:
int father_id;
public:
Father(int a) : father_id(a){}
};
class Child : public Father{
public:
int child_id;
public:
Child(int a, int b) : Father(a), child_id(b) {}
};
int main() {
Child c(1, 2);
Father &f = c;
cout << f.father_id << endl; //输出1
cout << f.child_id << endl; //error!child_id不是子类从父类中继承来的数据成员!
}
- 通常一个函数参数需要用到向上转型,比如:
int get_father_id(Father &f) {
return f.father_id;
}//传父类引用或子类都可以
int main() {
Child c(1, 2);
cout << get_father_id(c) << endl;
/*上述代码相当于
Child c(1, 2);
Father& f = c;
cout << get_father_id(f) << endl;
}
关于override:
当子类对父类中的某虚函数进行重写是,我们可以在子类的对应函数后面写上override关键字,表示该函数是重写父类的。
class Base { public: virtual void speak(int); }; class Derived { public: virtual void speak(int) override; };
final:
父类中有某个函数,他只想让子类继承而不想让子类重写,这样我们可以在该函数后面加上final关键字。一旦子类重写了父类中被final修饰的函数,那么编译器就会报错。
class Base { public: void speak(int) final;//无法被子类修改,也就不用加virtual }; class Derived { public: virtual void speak(int) override; //error!!! };
绑定
前绑定:
class Pet{
int age;
char *name;
public:
void speak(){//early binding
cout << "Pet::speak" << endl;
}
};
class Dog : public Pet{
public:
void speak(){
cout << "Dog::speak" << endl;
}
};
void Needle(Pet& pet){//如果传值,那么拷贝构造,会使用pet的构造函数。所以必须是引用。
//Never pass by value!!!
pet.speak();//静态多态,这里肯定执行的是父类的方法
}
int main(){
Dog dog;
Needle(dog);//upcasting 向上转型
return 0;
}
这种例子的多态,只是静态多态(静态链接),函数调用在程序执行前就确定好了。有时候这也被称为早绑定。
绑定:当子类和父类都定义了相同的函数时,将函数的一次调用,与函数体相对应的过程:
-
早绑定:运行之前已经决定好,早已注定。
-
后期绑定:later binding: dynamic binding, 运行时动态绑定,不提前决定函数执行的行为,用
virtual
函数实现,这种动态动态的核心是靠虚函数virtual
实现。
动态多态才是真正的多态。
class Pet{
int age;
char *name;
public:
virtual void speak(){//真正的多态
cout << "Pet::speak" << endl;
}
};
class Dog : public Pet{
public:
virtual void speak(){
cout << "Dog::speak" << endl;
}
};
void Needle(Pet& pet){
pet.speak();//这里执行的是真正对象的方法
}
Java的多态也是动态多态:编译看左边,执行看右边。
public static void main(String[] args) { Son s = new Son(); print(s); } public static void print(Father father){ father.print();//调用的是Son的print方法 }
虚函数
虚函数是c++多态的核心,是在父类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到父类函数。
-
virtual
关键字自动继承,也就是说,如果父类函数定义为了virtual
,那么子类重写的该函数也是虚函数。 -
虚函数的原理: 一旦有一个虚函数,该类会开辟一个内存空间,存储所有虚函数的入口地址,叫做虚函数表
V-table
。该类实例化的对象所占空间的前4个字节为一个虚指针V-ptr
,指向本类的虚函数表。由于父类和子类同名的虚函数在虚函数表中的偏移量相等,那么执行函数时只要找到偏移量就可以确定是哪个函数了。 -
构造函数没有多态性,故不加
virtual
。析构函数往往有多态性,所以定义时需要加virtual
。例如class Father{ public: virtual ~Father(){ cout << "father is deleted" << endl; } }; class Son : public Father{ public: virtual ~Son(){ cout << "son is deleted" << endl; } }; void Mydelete(Father* f){ delete f; } int main(){ Son *s = new Son(); Mydelete(s); } /* 输出: son is deleted father is deleted 如果析构函数前不加virtual,则输出 father is deleted */
-
静态成员函数不具有多态性,因为虚指针是由this指针找到的,而静态成员函数编译时不加this指针。
如果子类重写了父类的某个函数,想要调用父类的函数,这么写:
Father::func();
纯虚函数
在父类中有一种虚函数,在父类中没有必要将他定义,只有子类才能根据具体情况实现它,这种函数称为纯虚函数(pure virtual
)。纯虚函数的定义方式为——
virtual void func() = 0;
一个类一旦含有一个pure virtual
, 则成为抽象类。抽象类不允许实例化。
抽象类的作用:
- 规定一个类家族所有的共性行为。 这种抽象类既可以有纯虚函数,也可以有定义好的函数(包括虚函数和一般函数),相当于JAVA中的抽象类(
abstract class
)。 - 链接本不相关的多个类家族。 这种抽象类只能由纯虚函数组成,相当于JAVA中的接口(interface)。
STL
Standard Template Library
前置知识
有模板类、模板函数
解决类的行为相同,但是数据类型不同的复用问题。
template <class T> //T就是一个形参, template就是模板的意思
class Stack{
T pool[100];
int top;
public:
void push(T i){
pool[top++] = i;
}
T pop(){
return pool[--top];
}
Stack() : top(0){}
}
int main(){
Stack<int> s;
for(int i=0;i<10;i++){
s.push(i*i);
}
}
模板类库
C++为我们定义好一些模板了,不需要我们再去像上面一样定义Stack了。
动态增长的万能容器(不像c需要指定数组大小)
- vector
#include "vector"//实际上就是数组
using namespace std;
int main(){
vector<int> v;//
v.push_back(1);
cout << v[0] << endl;// v[0]是运算符重载,重载[]运算符
for(int i=0;i<10000;i++){
v.push_back(i);
}
}
- list. list是一个链表,来一个放一个;vector是数组,是一个足够大的空间,如果空间不够了搬家。
#include "list"
using namespace std;
int main(){
list<int> v;//
v.push_back(1);
for(int i=0;i<10000;i++){
v.push_back(i);
}
}
验证上述说法
class Test{
int a[100];
static int cnt;
public:
Test();
Test(const Test& t);
}
int Test::cnt =0;
Test::Test(){};
Test::Test(const Test& t){
this->cnt++;
cout << this->cnt;
}
int main(){
// for list
list<Test> v;
Test t;
for(int i=0;i<10000;i++){
v.push_back(t);//传值,发生拷贝构造
}
//for vector
vector<Test> v;
Test t;
for(int i=0;i<10000;i++){
v.push_back(t);//传值,发生拷贝构造
}
}
迭代器模式
迭代器是每个STL容器都定义的一种数据类型,为他们提供统一的访问方式
#include "list"
using namespace std;
int main(){
list<int> v;
v.push_back(1);
for(int i=0;i<10000;i++)
v.push_back(i);
list<int>::iterator it = v.begin();//定义一个类里的类iterator,初始化为v.begin()
while(it != v.end()){
cout << *it << endl;//*it为运算符重载
it++;
}
}
异常
超出程序员可控范围
void func(int m) throw (int) //可能要扔的类型,也可以这么写: throw (int, Dog)
{
if(m==4) throw 1;
}
int main(){
try{
func(4);
} catch(int i){
cout << "error" << endl;
} //catch(...),可以接受任何异常
}
断言
如果括号内不满足条件,报错,并给出出错位置。
//#define NDEBUG 如果在断言头文件以上定义此,所有断言失效
#include "cassert"
void fun(int *m){
assert(m!=NULL);//给程序员调试程序用的
if(m==NULL){ //这是给用户用的
return;
}
}
int main(){
fun(NULL);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?