C++primer学习笔记

本文详细介绍了C++编程的基础知识,包括const对象指针和引用、数组、引用、指针、string类、文件操作、顺序容器和关联容器等内容。深入探讨了类的构造函数、继承、多态性、运算符重载、虚函数等高级技术。

 按Ctrl+F键进行相关关键词的查找。比如“vector ”、“指针”、“迭代器”等等。


const

1、const的对象指针和引用只能调用const成员函数,而不能调用非const成员函数。

2、有const引用/指针的形参的函数与有非const引用/指针的形参的函数不同。可以重载。
而const形参(非引用/指针)与非const形参相同。


数组

1、用之前初始化;

2、数组内存大小 sizeof(a)=sizeof(&a[0])=m*sizeof(int);因为a是int [] 类型,代表的是有m个元素的数组;而&a[0]和a指向相同的地址,所以大小也是m个单位。

3、求数组长度=sizeof(a)/sizeof(int);

4、数组做函数形参,int func (&arr)[10])
arr是int [10]的引用,有10个地址空间
int a[2]={0,1};func(a); //error,
int k[10]={0};func(k);

引用


1、sizeof引用得到对应变量的大小
2、const int& a;//引用的值不可以改变,没有int& const a

指针

1、指针是一种迭代器,可以用指针初始化容器。
char arr[6]="hello";
string str(arr,arr+3);

2、二级指针分配内存。
int **p=new int*[n];
for(i=0;i!=n;++i)
    p[i]=new int [m]();
释放内存
    delete p[i];
    delete p;//这个很重要

3、指向函数的指针:int (*ff(int))(int *,int );
ff(int)是函数指针,该函数返回int (*)(int *,int *);即带int *和int两个形参,返回int型。



string


1、str.c_str()将string类型转化为c_style字符串。

2、string支持容器操作:string str;
赋值操作
str.swap(s2);
str.assign(b, e);
str.assign(n, t);

访问
str.at(i);
str[i];

添加元素
str.insert(p, t);
str.insert(p, n, t);
str.insert(p, b, e);
str.begin();
str.end();

删除元素
str.erase(p);
str.erase(b, e);
str.clear();

大小
str.length();
str.size();
str.capacity();
str.reserve();
str.max_size();
str.resize();

支持类型别名
string::iterator iter=str.begin();

3、string所特有的操作


构造string类
str(cp, n);//cp所指向数组的前n个元素
str(s2, pos2);//下标pos2开始的元素
str(s2, pos2, len2);

插入操作,返回str的引用
str.insert(pos, n, c);//pos前插入
str.insert(pos, s2);//pos前
str.insert(pos, s2, pos2, len);//s2的从pos2开始的len个字符
str.insert(pos, cp, len);//cp所指向数组的前len个
str.insert(pos, cp);

整个替换操作,返回str的引用
str.assign(s2);//
str.assign(s2, pos2, len);
str.assign(cp);
str.assign(cp, len);//cp前len个字符
str.erase(pos, len);//pos之后。

4、子串操作
str.substr(pos, len);
str.substr(pos);//pos到末尾
str.substr();//返回str的副本。

5、修改string操作,返回s的引用
str.append(args);//args接在str后面
str.replace(pos, len, args);//用args替换pos开始的len个字符,args不能为b2,e2
str.replace(b, e, args);//等效于erase和insert操作,args不能为s2, pos2, len2
其中args代表
s2
s2, pos2, len2
cp
cp, len2
n, c
b2, e2


6、查找操作,6个,找到返回位置string::size_type pos,否则返回string::npos
str.find(args);//args的第一次出现
str.rfind(args);//args的最后一次出现
str.find_first_of(args);//args的任意字符的第一次出现
str.find_last_of(args);
str.find_not_first_of(args);//查找第一个不属于args的字符
str.find_not_last_of(args);
其中args代表
c, pos //str的pos位置开始查找c,pos默认为0
s2, pos
cp, pos
cp, pos, n  //没有默认值,查找cp所指数组的前n个字符

7、比较操作,6个
str.compare(s2);
str.compare(pos1, n1, s2);
str.compare(pos1, n1, s2, pos2, n2);
str.compare(cp);
str.compare(pos1, n1, cp);
str.compare(pos1, n1, cp, n2);



迭代器


1、迭代器失效问题:
vector<int>::iterator first=v.begin(),last=v.end();
while(first != last)
    first++=v.insert(first,2);
直观上解释迭代器失效原因是last和v.end()已经不是同一个单位
如resize()、insert()、push_front()操作等等。
改为while(first != v.end())

2、系统无法检查迭代器是否失效。避免使用end()操作返回的迭代器赋值



文件操作

 


流操作



顺序容器:

 

有vector、list、deque
0、注意迭代器失效问题
1、顺序容器构造函数5个:
C<T> c;
C c(c2);
C c(b, e);其中e指向最后一个的下一个
C c(n,t);
C c(n);
2、访问元素(注意迭代器失效的问题!)
c.front();//返回第一个元素的引用
c.back();
c[n];//n在[0,size()]之间,只适用于vector和deque
c.at(n);//只适用于vector和deque

c.begin();//返回迭代器,对迭代器解引用得到元素
c.end();
c.rbegin();
c.rend();
注意容器非空。

3、顺序容器添加元素5种:
c.push_back(t);
c.push_front(t);//只适用于list和deque
c.insert(p, t); //迭代器p前面插入元素t
c.insert(p, n, t);
c.insert(p, b, e);

4、顺序容器的大小操作
c.capacity();//总空间
c.reserve(n);//预留n个内存空间
c.size();
c.max_size();
c.resize(n);
c.resize(n, t);
bool c.empty();
其中resize操作可能使所有的迭代器失效。

5、删除元素
c.erase(p);//返回一个迭代器,它指向陪删除元素的下一个元素
c.erase(b, e);//[b, e)
c.clear();
c.pop_back();
c.pop_front();//只适用于list和deque容器

6、赋值与swap
c1=c2;
c1.swap(c2);//交换
c1.assign(b, e);//b,e必须不是指向c1的迭代器
c1.assign(n, t);
注意:在不同(或相同)类型的容器内,元素类型不同但是相互兼容,则其赋值运算必须用assign。
如可以通过assign操作实现将vector中char *类型的元素赋值给string类型的list容器。

7、类型别名
size_type
iterator
difference_type
value_type
reference

8、容器内元素的类型约束(即vector<***>中的***:
元素类型必须支持赋值运算;
元素类型的对象必须可以复制

9、C++语言只允许两个容器做其元素类型定义的关系运算。

10、将一个容器复制为另一个容器时类型必须匹配:元素类型和容器类型相同。

顺序容器适配器:queue、stack、priority_queue
1、默认的stack和queue是基于deque实现,而priority_queue则是基于vector实现。
通过将一个顺序容器指定为适配器的第二个实参类型,可以覆盖其关联的基础容器类型
例如:stack<string, vector<string> > str;
      stack<string, vector<string> > str1(svec);
其中stack可以建立在list、vector、deque上
    queue需要提供push_front运算,只能建立在list上
    priority_queue要随机访问元素,可以建立在vector和deque上

2、适配器通用类型和操作
A a;
A a(c);//c是容器
关系操作==、>=等等
size_type;//
value_type;//元素类型
container_type;//容器类型
3、栈适配器stack的操作
s.empty();
s.size();
s.pop();
s.push(t);
s.top();
4、队列和优先级队列的操作
q.empty();
q.size();
q.pop();
q.front();//返回队首元素,只适用于队列
q.back();
q.top();//返回最高优先级的元素,只适用于优先级队列
q.push();//在队列尾压入;在优先级队列中的适当位置压入


vector

1、二维vector的实现:vector<vector<int> > vec1(n,vector<int> (m))
或者vector<vector<int> > vec1(n);vec1[i].resize(m)

list


1、list容器特有的操作
lst.merge(lst2);//合并,用 < 操作
lst.merge(lst2, comp);
lst.remove(val);//删除指定值的元素
lst.remove(pred);//谓词函数
lst.reverse();
lst.sort();
lst.splice(iter, lst2);
lst.splice(iter, list2, iter2);
lst.splice(iter, b, e);
lst.unique();//用 == 操作
lst.unique(pred);



关联容器:

map、set、multimap、multiset

 

泛型算法


0、#include <algorithm>和泛化的算术算法#include <numeric>

1、泛型算法和迭代器有关,和容器无关,并且从不删除或添加元素。

2、只读算法
find(b, e, value);//元素支持==操作
accumulate(b, e, value);//初值value,累加b到e
string sum= accumulate(v.begin(), v.end(), string(" "));//若string(" ")改为“ ” 则出错,因为此时累加和为const char *类型
find_first_of(iter, e1, b2, e2);//迭代器e1与e2类型可以不同,b2与e2相同

3、写容器元素的算法。不检查目标大小是否足够,注意不能在空容器中操作
fill(b, e, value);
fill_n(b, n, value);

4、插入迭代器(522页)
back_inserter(v);//生成容器v的插入迭代器//#include <iterator>
front_inserter(v);//只有容器提供push_front操作才能用,始终在容器第一个元素前插入,使元素反转
inserter(v, p);//固定位置p前面插入

5、写入到目标迭代器的算法
copy(b, e, p);
replace(b, e, value1, value2);//把[b,e)内的值为value1的元素替换为value2
replace_copy(b, e, p, value1, value2);//replace与copy的结合

6、排序算法
sort(b, e);
stable_sort(b, e, weici);//第三个形参是谓词函数(519页),需要两个实参,指明了排序方法。


count_if(b, e, weici);//返回使谓词函数条件成立的元素个数,此谓词函数需要一个实参
unique(b, e);//算法返回无重复元素最后位置的下一个位置迭代器
unique_copy(b, e, p);

7、iostream迭代器

8、反向迭代器

9、算法的形参模式
alg(b, e, params);
alg(b, e, dest, params);//带单个目标迭代器的算法
alg(b, e, b2, params);//带第二个输入序列的算法
alg(b, e, b2, e2, params);//同上
10、算法的命名规范
区别带有一个值和一个谓词函数的算法版本:
重新对元素排序的算法要使用 < 操作符,其第二个版本(重载)要使用一个形参,表示元素排序的不同运算
例如: sort(b, e);
sort(b, e, comp);

检查指定值的算法默认使用 == 操作符,其第二个版本(非重载),带有谓词函数的形参,函数名使用 _if 后缀
例如: find(b, e, val);
find_if(b, e, pred);
不使用重载,因为可能导致二义性

区别是否实现复制的算法版本,带后缀 _copy:
例如: replace(b, e);
replace_copy(b, e, dest);


1、构造函数。
2、在类内部定义的成员函数默认为inline。
3、关键字const必须同时出现在声明和定义后面。不然他们就不是同一个函数。
4、两个重载成员的形参数量和类型不能完全相同。
5、可以在多个文件中定义完全相同的一个类。
6、前向声明指先声明后定义,在声明和定义之间的类属于不完全类型,即只知道有这个类,不知道类有什么成员。不完全类型不能定义该类型的对象,只能定义指针和引用或声明使用该类型做形参或返回类型的函数。
这涉及到内存分配问题。因为不完全类型的指针的大小固定,而类内存大小还不知道。
7、由6可知类不能拥有指向自身数据类型的成员,但可以有其指针或引用。
8、当成员函数返回调用该函数的对象的引用时使用this指针,返回*this,可做左值使用。

如:myScreen.move(40).set(0);
9、可变数据成员在const成员函数里也可变。用mutable关键字放在声明之前。
10、const构造函数是不必要的。
11、没有默认构造函数的类类型的成员,const或引用类型的成员必须用初始化列表初始化。不能在构造函数里赋值。
12、Sales myobj;
       Sales myobj();
两者有何大区别,前者使用默认构造函数;后者创建一个用默认构造函数产生的对象初始化myobj。
13、在const成员函数里,this指针不能重定向,而且对象的成员也不能修改,即value++出错,因为value是常量
14、被引用的对象必须有地址,所以函数不能返回局部引用
15、int Counter::count = 100;静态数据成员初始化不加关键字static
16、静态成员函数static void sum(Simple &p):
 直接调用法Simple::sum(obj1);//也是调用私有静态成员函数的唯一方法
 对象调用法obj1.sum(obj1);
静态成员函数只能通过对象指针或引用访问普通数据成员
17、常量对象在定义的时候必须初始化
18、常量对象调用常量成员函数
19、赋值操作返回*this的引用
20、保护部分只能被本类成员函数和派生类访问,主要用于继承
21、友元用于两个不同类的对象共享一个函数,减少调用成员函数访问私有数据成员的开销友元可以是函数或类。
友元函数用friend 声明,定义在类体外不加friend和类:: ,因为他不属于某个类
友元函数没有this指针,所以必须有类对象的指针或引用做参数。
友元类friend class Y;//声明Y是X的友元类,Y可以直接访问X
22、头文件内容:
     类型定义
     函数声明
     内联函数定义
     常量定义
     全局变量和全局数组的声明:extern int k;extern char a[6];
     预处理语句
不适合内容:
     普通函数定义
     全局变量和数组定义:int k;int a[6];
     常量数组定义:const int a[]={1,2,3}
23、对象成员和容器类
用初始化列表进行初始化,对象成员没有构造函数的可不写到初始化列表中。
先调用对象成员的构造函数,再调用容器类的。
容器类必须有初始化列表。
容器类必须用公有成员函数访问对象成员的私有数据成员:b1.Bar::get()//b1为对象成员
24、new/delete
使用"new <类型> [元素个数]"表达式创建动态对象时,在数组元素所属的类体内必须定义一个无参构造函数
25、void B::getb(char *s, double &n)
{
 strcpy(s,name);n=b;
}
上述函数的好处是用s和n代替了数据成员name和b,从而在类体外可以直接访问,而不用调用类的公有成员函数去访问私有数据成员。

 

继承


1、派生类class Derived: public Base{
2、派生类构造函数中应包含基类的初始化列表
3、派生类可以覆盖基类的同名成员函数,成员函数访问基类同名成员函数时用Base::fun()指明,对象访问obj.Base::fun().
4、私有继承下,派生类必须定义公有成员函数访问基类的公有成员函数,再用基类的公有成员函数访问基类的私有数据成员。
5、派生类可以直接访问基类公有和保护的数据成员
6、在私有继承下,访问声明机制:Base::fun;//把从基类继承的、因私有继承方式而变成私有成员函数fun()恢复成公有成员函数
7、派生类构造函数初始化顺序:基类——内部成员对象——派生类
8、赋值兼容性规则(公有派生类)
 基类=派生类                    //派生类为子类型,类型适应
 (派生类名*)基类指针名    //强制类型转换
9、公有继承下,将一个函数形参设为基类,可以访问所有继承类
10、不能继承的部分:
 构造函数和析构函数
 友元关系
11、多继承class Derived: public Base1, public Base2{
12、虚基类仅用于多继承
class A
class B: virtual public A
class C: virtual public A
class D: public B, public C
13、多继承中的二义性
 B、C有同名的数据成员时
  1)成员名限定法:d.B::draw();
      d.C::draw();
  2)在派生类中重新定义同名的成员函数draw();
 D类的对象d调用A类的公有数据成员value时不能区分是从B还是C间接继承而来。
  使用虚基类
14、虚基类构造函数
 B(int v, int b): A(v) {}
 C(int v, int c): A(v) {}
 D(int v1, int v2, int b, int c, int d): B(v1,b),C(v2,c),A(v1)
15、虚基类满足第7条

16、派生类声明而不定义时,不要加派生列表。

17、没说明为何种继承方式时,类默认为私有继承

多态性:

运算符重载、函数名重载、虚函数


1、运算符重载为类的成员函数,有this指针,所以对与单目运算符不带参数,双目带一个参数
 Complex Complex::operator +(const Comple &b);
2、运算符重载为友元函数,单目带一个参数,双目两个
 friend Comple operator +(const Comple &a, const Comple &b);
3、单目运算符有前置和后置运算,为了区分必须重载
 成员函数
  op X —— X.operator op()
  X op —— X.operator op(int)
 友元函数
  op X —— X.operator op(X)
  X op —— X.operator op(X, int)
4、通常单目重载为成员函数,双目重载为友元(除了赋值运算符)
eg: 复数对象c 重载+
  6.8+c
  若”+“为成员函数则上式表达为:6.8.operator+(c)
  这显然错误。若将‘+’重载为友元函数则上式解释为
  operator +(Comple(6.8),c)
5、return Comple(rea-c.real, imag-c.imag);
由于return的是一个匿名对象(局部对象),并用一般构造函数初始化该匿名对象,所以返回类型必须是对象本身,
而不能是其指针或引用若想用传地址方式,可以static Comple result;return &result;或返回其引用,
避免了调用复制构造函数,提高效率,通常用传值方式
6、通常用传值方式,形参选用对象本身比较好,可以进行类型转换
eg: c3=2.9-8i;
若形参为对象引用则出错
7、运算符重载规则:
 不能定义新的运算符
 不能改变优先级和运算量的个数
 若运算符是非成员函数,参数至少有一个是类的对象
 不能将两个运算符合并建立新的运算符:
  可以operator+=()
  但不可以将+和=合并成+=
 下列不能重载
  ?:
  &
  ::
  *
 应保持运算符原来的含义
8、重载赋值运算符operator =()必须是非静态成员函数
返回值定义为目标对象的类型
为了多重赋值可以return *this;
为了加快速度,返回类型和形参定义成类型引用”类型 &“,可以节省复制开销
对于有new 和delete的数据成员,在重载=时可以先释放原来占用的内存,再申请新内存
eg:p2=p1;
9、有new时要自己编写赋值运算符、复制构造函数、析构函数
10、慎重选择"是采用函数重载还是选用函数参数的默认值"
 void fun(void);
 void fun(int a=6);
 fun();//产生二义性
通常最好选择函数参数的默认值编写程序
11、前置增量运算符返回类型为对象引用,后置返回类型为对象。
因为前置返回值改变后的对象,后置返回t,t是改变前的对象副本。
12、函数调用运算符()的重载,是多目运算符
a(1,2)——a.operator()(1,2)
13、下标运算符[]作为普通成员函数,只能带一个参数,只能重载一维数组,如果想重载多维可用()。
返回类型为对象引用。可以作为左值表达式。
str.operator[](cnt)——str[cnt]
14、静态联编:在编译时,编译系统根据传给该函数的实参对象就能决定调用固定的函数体代码块。
其支持C++运算符重载和函数名重载这两种形式的多态性。优点是效率高,缺点是灵活性差
 动态联编:运行时选函数
15、函数重载实现“多接口界面,多实现版本”
    为了实现“单接口界面,多实现版本”的程序框架——虚函数,即动态联编技术
虚函数是 基类 的公有部分或保护部分的某成员函数,在函数名前加virtual
虚函数在公有派生类中被重新定义时,函数的原型不变(返回类型,参数个数、类型、排列顺序),否则还是静态联编。
用虚函数实现多态性的关键是必须用指向基类的指针或引用访问虚函数。
普通函数、友元函数、构造函数、静态成员函数都不能说明为虚函数,但析构函数可以为‘虚析构函数’
即使在派生类中没有相应的虚函数实现,在调用基类的虚函数时还是用动态联编
16、纯虚函数:基类中没有定义部分,在基类的虚函数后加“=0”,派生类中不变。
在纯虚函数的所有派生类中都必须定义其实现版本,否则编译错误。
17、有纯虚函数的基类都是抽象类。
作用是:
 提供统一的接口界面
 定义指针或引用,可以指向所有公有派生类对象。
18、虚析构函数
 函数名不同,派生类中virtual可以省略不写。
只要类中有虚函数,就要有虚析构函数,防止new/delete的问题。

19、作用域与重载
void print(const string&);
void print(double);
void fooBar(int via)
{
void print(int);
print("value");//error
}
fooBar中的print("value")是局部函数,形参是int型,而“value”是string型,所以出错。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值