2016年下半年开始接触音视频相关的项目,在这之前对C++没有系统的学习过,就在中国大学MOOC上学习了西安交通大学的计算机程序设计(C++),课程学完之后把做的笔记总结出来发布到了自己的博客便于以后复习,最近发现访问不太稳定,同时也动了想要把遇到的问题以及解决方式记录下来的心思,起因是因为发现好多遇到过的问题当时花好多时间解决了,过了很长一段时间再遇到就忘记当时是怎么解决的了导致时间浪费,另外做笔记也能够重新思考一遍加深一次印象,才把之前博客上的内容迁移到简书上。
优先级:后置++ > 前置++ > 乘除
int a=3; //全局变量
int main()
{
int a =8;
cout<<a<<endl; //同名变量,局部变量优先级高于全局变量
}
//如果想使用全局变量而不是局部变量,应在变量前加上作用域运算符"::"即可.
变量的存储类型,C++程序运行时使用的内存区域
堆区 | 存放动态分配的数据,new出来的 |
---|---|
栈区 | 存放局部数据,局部变量 |
全局数据区 | 存放全局数据和静态数据,全局变量 |
程序代码区 | 存放程序各个函数的代码 |
所有共用成员函数代码区对象中的函数成员共用一个存储空间,在代码区存放.
存储类型 auto,register,static,extern
一个变量完整形式
<存储类型> <数据类型> <变量名>;
- auto:在定义块的开始分配空间,执行结束时释放空间,函数的自动变量(auto)是在函数执行中才分配.
- register:尽可能存放在CPU的寄存器中,提高程序的效率,仅局部变量和形式参数才可作为寄存器变量.
- static变量是在编译时才分配的内存.如果在全局变量前加上static修饰符,则成为静态全局变量,只能在本文件中使用
- extern:如果在一个源文件a.cpp中定义的全局变量想在b.cpp文件中使用,则应该在b.cpp中使用,则应该在b.cpp中加上extern进行声明,表示该全局变量不在b.cpp中定义的
a.cpp
int Dimension =100;
b.cpp
使用的话,应该声明如下
extern int Dimension;
位运算符
&按位”与”
a=01101001B
b=01011110B //&,全1为1全0为0
c=01001000B
|按位”或”
a=01101001B
b=01011110B //|,有1为1其他为0
c=01111111B
^按位异或
a=01101001B
b=01011110B //^,不同为1,相同为0
c=00110111B
~按位取反
//如:整数是4个字节占32位,补全之后对应位取反
字符的处理
字符串占的字节数=字符串的长度+1.如”程序设计”(4个汉子占8个字节),末尾加了一个结束符’\0’,所有+1个字符.
有些ASCII符号是不可显示的,如转义字符:
\n 换行符 ,\r 回车, \b 退格符,\t 制表符 ,\’ 单引号, \’’双引号, \0 字符串结束符, \f 分页符.
char *strcat(char *destin,char *source) //链接两个字符
char *strcpy(char *destin,char *source)//将字符串source拷贝到destin中
char *strlwr(char *string) //string转换成小写
char *strupr(char *string)//string转换成大写
String text ="heavy rains are pushing water";
k = text.find("heavy");
text.erase(k,sizeof("heavy")-1);
text.insert(k,"strong");
指针(*and&)
1.我们将存放地址的变量称为指针变量,这里的地址就是指针,指针也是数据类型.
2.*是指针类型变量的标识符
3.定义一个指针变量系统为该指针变量分配一定大小的内存(C++中,每个指针变量占有8个字节长度)
int a,*pta;//先定义变量
pta = &a;不可写成 *pta=&a;
/*
*pta并不表示指针变量pta.而表示pta指向的变量a.指针变量和指针变量所指向的变量完全是不同的概念.
&取地址运算符,&变量名 //获取变量的内存单元地址
*指针运算符(间接访问运算符)
指针变量pta存放着变量a的指针,则*pta表示pta所指向的变量,即变量a.
*/
int a =5; *p =&a;
cout<<&a<<endl;//a的地址
cout<<a<<endl;//变量a的值
cout<<*p<<endl;//p指向的变量
//函数在编译时被分配了一个入口地址,这个入口地址就称为函数的指针.
char name1[50];
//name1是字符数组name1的首地址,也就是&name1[0];
//指针+/-整数---->指针
假设:int a[10] ={10,20,30},*p=a,i;
p+i:表示p所指元素之后的第i个元素的指针,p指向的是int类型,int类型变量为4个字节长度,
所以p+1,相当于指针P+4,p+i相当于p+4*i
1000 |10
1004 |20
1008 |30
100C |...
//同类型的指针做减法运算
//指针2-指针1 --->整数,常用于计算两个指针间包含元素的个数.
(指针2-指针1)/元素字节长度 =元素个数
int a[];*p=a,i;
a+i表示对象的地址
*(a+i)表示指向的对象
char str[81],*pstr;
str ='abcd';错误,数组名是常量指针!不能被赋值
结构体指针
结构体变量的指针 &结构体变量名
指向结构体变量的指针 结构体类型 *指针变量名
struct telelist{
char name[8];
char sex;
char num[15];
char num2[15];
}List[3];
//表示list包含telelist这种格式的3个元素
结构体指针访问结构体中变量中的成员
1.(*指针变量).成员名
2.指针变量->成员名 //->称为结构体指向运算符
例如:
Date d = {2015,4,8},*p = &d; //定义日期结构体变量和指针变量
(*p).year p->year
delete运算符:释放动态申请到的存储空间
1.动态释放单个变量
delete 指针变量 ; //释放单个动态变量
2.动态释放数组
delete []指针变量 ;//释放动态数组
new运算符->动态申请所需的内存空间
1.动态申请单个变量
指针变量 = new 类型;
double *p;
p= new double(100.0);
2.动态申请数组
指针变量 = new 类型[元素个数];
char *str;
str =new char[80];
cin>>n; p = new int [n];
if(p=NULL){
cout<<"空间申请失败!";
}
抽象&封装
结构体和类的区别?
1.结构体只有数据成员没有函数成员.
2.类=数据成员+函数成员
3.结构体又称为特殊的类,现在的结构体中也可以包含函数成员
1.内联函数
//在类体内直接定义的函数成员,该函数成员又称为内联函数
Class clock{
void show_Time(){cout<<hour:<<:<<minute<<endl;} //内联函数
}
inline int square(int x)
{
return x*x;
}
int sum =0;
sum+=square(i);
//编译时遇到内联函数调用square(i),实参换形参,sum+=square(i);将被替换为sum+=i*i;
2.在类体外定义函数成员
<类型><类名> :: <函数名>(<参数列表>)
{
<函数体>
}
//::表示作用域符号
面向过程
以功能为中心,通过分解问题的功能,采用函数描述数据与函数分离,数据(类型或结构)发生变化,函数也要发生相应变化.
如 void sort(int a[],int n);//只能排整数的数组
面向对象
以数据为中心,采用对象来描述内部属性和操作方法,将数据和函数当做一个统一体,采用软件对象来描述客观对象.
构造函数
构造函数,用于创建一个对象,提供了初始化该对象的手段.成员函数完成初始化数据成员.
constructor 在对象创建时执行,提供了初始化对象的一种简便手段.
Destructor 在对象被撤销时(前)执行,用于完成对象被销毁前的一些清理工作.
往往用于释放constructor中动态申请的内存空间.
Destructor 语法格式
<类名>::~<类名>(){<函数体>}
功能:撤销对象前进行一些善后处理工作,由系统调用.类名前加~.
Class robot{
robot();//构造函数
{
strcpy(name,"xx");
strcpy(type,"xx");
}
void set(char n[],char t[],int m); //设置修改数据
~robot(){delete []ps;} //释放构造函数和set函数中动态申请的空间
}
继承(inheritance)
从以前定义的类(基类)产生新类的过程为派生,新产生的类为派生类
派生类的定义
Class 派生类名 :继承方式(即是访问权限) 基类名1
Class singStar:public Person
//对基类的扩充/对基类成员的改造,系统默认就是私有继承
访问权限
- public(基类共有成员,基类保护成员)
- protected(基类的public和protected成员相当于派生类的保护成员,派生类可以通过子类和自身的成员函数访问他们)
- private(基类的public和protected成员相当于派生类的私有成员,派生类只能通过函数成员访问)
基类的constructor和destructor不能被继承
派生类的构造函数一般形式为
派生类名::派生类名(参数列表):基类名1(参数列表1)…内嵌对象名(对象参数表)
Class Employee:public Person
{
Person lerader;//内嵌对象
char Dept[20];
}
Employee(char *name,int age,char *dept,char name,int age):Person(name,age),Leader(name1,age1)
Cylinder::cylinder(int x,int y,double r,double h):Circle(x,y,r);
多态
相同语法结构,代表不同的功能和操作,一种接口多种方法
- 编译时多态性:编译器对源程序编译时可以确定调用哪一个函数,通过重载实现(参数类型不同)
- 运行时多态性:在运行过程中确定调用哪一个函数,通过虚函数来实现
dog,cat:Pet
pet pet1;
*p = &pet1;
cin>>x;
if(x==1) p = &cat1;
if(x==2) p = &dog1;
p->speak();//只有运行时才知道,究竟运行哪个函数
Class Pet{
public;
virtual void speak(){
cout<<"222"<<endl;
}
}
//virtual使得speak为虚函数,实现基类指针访问派生类的成员函数.
虚函数
基类加入virtual的函数是虚函数,子类可以重写虚函数实现对父函数的覆盖.
当想用派生类的成员函数取代基类的同名函数时,C++要求你必须预先通知编译器.通知的方法就是在可能被取代的基类成员函数前面加上virtual关键字.
在计算机领域virtual的意思是用户看到的东西事实上并不存在,它只是用某种方法支撑的幻觉罢了.这里他的意思是不让用户看到事实上存在的东西(基类的成员函数).
virtual可以替换成另外一个关键字会更加确切choose_the_appropriate_method_at_runtime_for_whatever_object_this_is(在运行时根据对象的类型选择合适的成员函数),这里也可以替换成一个更简单的词placeholder.
不管怎么如果需要调用基类的成员函数可以使用下面的方法:
p->Fruit::peel();
为什么成员函数不缺省使用virtual?
不能缺省virtual的原因和C语言不能缺省使用register关键字有异曲同工之妙,它是一种笨拙的优化措施.既然不是每个成员函数都需要这种运行时的间接形式,那为什么要让每一个成员函数都添加一个额外的负担呢?应该显示的告诉编译器哪些成员函数需要多态.
定义:
函数定义的头部加上virtual,该函数就是虚函数.在基类中声明为virtual并在派生类中重新定义的同名函数,成为虚函数.
格式:
virtual 函数返回类型 函数名(参数列表){
函数体
}
用处:
实现运行时的多态性,通过基类指针访问派生类中的同名覆盖函数
使用限制:
应该通过指针或引用调用虚函数,而不能以对象名调用虚函数.
pet obj;
Dog dog1;
obj =dog1;
obj.speak();//执行基类的speak()函数
Pet *pet1 =&dog1;
pet1->speak(); //执行是Dog类的speak()函数.
//在派生类重定义基类的虚函数仍为虚函数,同时可省略virtual关键字.
//不能定义虚constructor函数,可以定义虚destructor函数.
抽象类
- 将那些并不用来声明对象(实例化)的类称为抽象类,只供继承.
- 具体实现只能在派生类中完成,抽象类又可以定义成:至少包含一个纯虚函数的类,虚函数是多态的一种形式,作用是实现函数的覆盖.
纯虚函数定义:
virtual 返回类型 函数名(参数列表) = 0
虚析构函数:通过基类指针可以释放派生类对象的空间.
Class Base{
void show();
virtual void Base::show{cout<<x<<endl;} //调用时说明virtual虚函数是不正确的
}
Class Base
{
virtual void show(); //声明
}
void Base::show(){cout<<x<<endl;}//类外定义
函数模板
根据函数实参中的类型来确认是否匹配函数模板中对应的形参,然后生成一个重载函数.
template <class T> //class指某种类型(char,int,struct)
//类模板定义
template <class <类型参数>>
迭代器 确定元素位置的数据类型,可用来遍历容器中的元素,可以读取,修改它指向的元素.
vector 对动态数组的封装,能够插入元素且能够自动调整容器大小.
创建10个元素的向量
vector <String> s2(10);
v1.Insert(v1.end()-1,3);//倒数第2的位置上插入元素向迭代器
输入/输出流
ifstream //对象只进行读操作
ofstream //对象只进行写操作
fstream //可读可写
istream & getline(char *pch,int ncount,char delim='\n')
pch:读取多个字符放在pch中,ncount:读取字符的上限,读取字符到delim结束,终止字符被舍弃.
cin.getline();//将指针移到终止字符之后
cin.get(); //将指针移到终止字符处
打开文件
void open(const char *fileName,openMode mode)
ifstream in("file.txt");
if(!in) //不可以打开文件
打开文件用于输入
ifstream file1; file1.open("grade.txt");
打开文件用于输出
ofstream file2; file2.open("c:\msg.txt");
以二进制的形式打开文件 c:\abc.bmp
fstream file3; file3.open("c:\abc.bmp",ios::binary|ios::in);
二进制文件读写函数
二进制文件输入数据
istream &read(char *buffer,int len);
二进制文件输出数据
ostream &write(const char *buffer,int len);
STL(Standard Template Library)
Map
你有必要包含,maps是std命名空间的一部分,maps需要2到3种类型对于模板
you will need to include and maps are part of the std namespace. Maps require two, and possibly three, types for the template:
std::map <key_type, data_type, [comparison_function]>
std::map <string, char> grade_list;
grade_list["John"] = 'B';
// John's grade improves
grade_list["John"] = 'A';
需要调用一个erase函数进行删除map中的一个成员
requires calling the function erase, which is a member of the map class
erase(key_type key_value);
grade_list.erase("John");
how many values the map contains by using the size function
int size();
we could call the size function on the grade list:
std::cout<<"The class is size "<<grade_list.size()<<std::endl;
If we’re only interested in whether the map is empty, we can just use the map member function empty:
bool empty();
If you want guarantee that the map is empty, you can use the clear function.
grade_list.clear();
一个key是否在map中有对应的value,你需要调用find函数如果有这个key对应的value时将会返回一个iterator,如果没有发现key对应的value迭代器将会指向这个 map_name.end()
whether a key has an associated value in a map,you need to use the find function, which will return an iterator pointing to the value of the element of the map associated with the particular key, or if the key isn’t found, a pointer to the iterator map_name.end()
std::map <string, char> grade_list;
grade_list["John"] = 'A';
if(grade_list.find("Tim") == grade_list.end())
{
std::cout<<"Tim is not in the map!"<<endl;
}
std::map<parameters>::iterator iterator_name;
包含两个成员,第一个对应的是key,第二个对应的是value,迭代器被当做是一个指针去访问成员变量,你需要用->去取消引用这个迭代器
essentially has two members, first and second. First corresponds to the key, second to the value. Note that because an iterator is treated like a pointer, to access the member variables, you need to use the arrow operator, ->, to “dereference” the iterator.
the following sample shows the use of an iterator (pointing to the beginning of a map) to access the key and value.
std::map <string, char> grade_list;
grade_list["John"] = 'A';
// Should be John
std::cout<<grade_list.begin()->first<<endl;
// Should be A
std::cout<<grade_list.begin()->second<<endl;
List
向量相对比较花费时间插入向量中,但是能够很快的进行随机访问,链表比较容易插入但是访问比较困难
The vector has relatively costly insertions into the middle of the vector, but fast random access, whereas the list allows cheap insertions, but slow access (because the list has to be traversed to reach any item),declares a list storing integers:
std::list<int> integer_list;
新的元素将会各自插在链表的开头和结尾用push_back and push_front 两个函数
Like the vector class, the list class includes the push_back and push_front functions, which add new elements to the front or back of the list respectively.
std::list<int> integer_list;
integer_list.push_front(1);
integer_list.push_front(2);
插入需要一个迭代器指向这个元素应该被插入的位置,新的元素将会被插入当前指向的
元素之前
insert requires an iterator pointing to the position into which the element should be inserted (the new element will be inserted right before the element currently being pointed to will). iterator insert(iterator position, const T& element_to_insert);
链表返回一个迭代器在链表的第一个元素,返回一个迭代器在元素在链表的最后,你能声明一个迭代器作为其他对象的容器
Fortunately, the list container supports both the begin – returning an iterator to the beginning of the list – and end – returning an iterator past the last element of the list – iterator functions, and you can declare iterators as with any other container, in the following manner:
list<type>::iterator iterator_name;
增加一个元素与在链表的结尾也可以这样实现:
using insert and the function end, the functionality of push_back, which adds an element to the end of the list, could also be implemented as
std::list<int> integer_list;
integer_list.insert(integer_list.end(), item);
链表包含两个函数size,empty,size的时间复杂度是O(N),如果你想要测试一个链表是否为空用empty函数代替size函数,如果你想保证这个链表为空,就要用clear函数
排序算法仅仅提供能随机访问迭代器,不提供排序在链表容器中,这时候就有必要用成员函数进行排序了
instance_name.sort();
Lists can be reversed using
instance_name.reverse();
其中一个特点,用逆序成员函数取代算法逆序是它不影响如果正在被其他迭代器指向的值.
Another potentially useful list function is the member function called unique; unique converts a string of equal elements into a single element by removing all but the first element in the sequence. For instance, if you had a list consisting of
另外一个可能的用法链表的函数是一个成员函数被调用unique,删除链表中相同的元素最后只保留一个,如果你有一个链表包含如下
1 1 8 9 7 8 2 3 3
the calling unique would result in the following output:可以发现仍然还有两个8,仅仅挨着的两个元素其中一个被删除了
1 8 9 7 8 2 3
你如果想要把相同的元素只保留一个,就需要先排序再调用unique函数.
If you want each element to show up once, and only once, you need to sort the list first! Try the following code to see how this works and see many of the previous functions in action!,
std::list<int> int_list;
int_list.push_back(1);
int_list.push_back(1);
int_list.push_back(8);
int_list.push_back(9);
int_list.push_back(7);
int_list.push_back(8);
int_list.push_back(2);
int_list.push_back(3);
int_list.push_back(3);
int_list.sort();
int_list.unique();
for(std::list<int>::iterator list_iter = int_list.begin();
list_iter != int_list.end(); list_iter++)
{
std::cout<<*list_iter<<endl;
}
The Good
- Lists provide fast insertions (in amortized constant time) at the expensive of lookups (链表能够快速的插入以固定的时间),在查找时比较耗时
- Lists support bidirectional iterators, but not random access iterators,链表支持双向的迭代器,但不支持随机读取的迭代器
- Iterators on lists tend to handle the removal and insertion of surrounding elements well,链表上的迭代器更倾向于手动插入,删除周围的元素
The Gotchas
- Lists are slow to search, and using the size function will take O(n) time,链表遍历起来非常慢,用size函数的时间复杂度为O(N)
- Searching for an element in a list will require O(n) time because it lacks support for random access,遍历一个元素的时间复杂度是O(N),它缺少随机访问的支持
容器&迭代器
- 容器(Container),是一种数据结构,如list,vector,和deques ,以模板类的方法提供
- 迭代器(Iterator),提供了访问容器中对象的方法。Iterator(迭代器)模式又称Cursor(游标)模式,用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
迭代器的作用:能够让迭代器与算法不干扰的相互发展,最后又能无间隙的粘合起来,重载了*,++,==,!=,=运算符。用以操作复杂的数据结构,容器提供迭代器,算法使用迭代器;
vector<T>::iterator it;
list<T>::iterator it;deque<T>::iterator it;
从容器中读取元素。
- 输入迭代器只能一次读入一个元素向前移动,输入迭代器只支持一遍算法,同一个输入迭代器不能两遍遍历一个序列output:向容器中写入元素。
- 输出迭代器只能一次一个元素向前移动。输出迭代器只支持一遍算法,统一输出迭代器不能两次遍历一个序列
- forward:组合输入迭代器和输出迭代器的功能,并保留在容器中的位置bidirectional:组合正向迭代器和逆向迭代器的功能,支持多遍算法random access:组合双向迭代器的功能与直接访问容器中任何元素的功能,即可向前向后跳过任意个元素
尽管你的编译器可能没有实现名字空间,你仍然可以使用他们。为了使用STL,可以将下面的指示符插入到你的源代码文件中,典型地是在所有的#include指示符的后面:using namespace std;
参考资料:
http://www.cprogramming.com/tutorial/stl/stlmap.html