一直在用C语言开发,期间也接触过C++写的android omx框架,发现复杂问题简单化,之后想往服务器方向发展,就有了学习C++的理由,也就有了这学习过程的记录,接下来就是学习C++的一些笔记和一些坑的记录。
C++语言基础
一、命名空间
1、为了防止名字冲突而引入的一种机制。系统中可以定义多个命名空间,每个命名空间都有自己的名字,不可以同名;
2、可以把这个命名空间看成一个作用域,我们在这个命名空间定义函数,跟你另外一个命名空间里定义的函数,即便同名,也互不影响;
3、命名空间的定义:
namespace xxx
{
.....
}
4、命名空间的定义可以不连续,甚至可以写在多个文件中。如果你遗忘没有定义这个命名空间,那么“namespace 命名空间名”这种写法就相当于定义了一个命名空间,如果以往定义了这个命名空间,那么“namespace 命名空间名”这种写法,就相当于打开已经存在的命名空间,并为其添加新成员的声明;
5、外界访问这个定义好的命名空间中的某个实体呢,需要加上作用域运算符"::"。格式为:命名空间名::实体名;或者在使用:using namespace 命名空间名使用;
二、基本输入输出cin、cout
1、前言:在C++中,我们一般不用printf,还是使用c++提供的标准库。不要排斥c++标准库,与c++语言一起学习。
2、头文件:iostream库,什么叫流:流可以理解为一串字符序列。
3、std:: 命名空间,标准命名空间。
4、输出缓冲区:一段内存。cout输出的时候实际是往输出缓冲区输出内容,那么输出缓冲区什么时候把内容输出到屏幕去呢? 1)缓冲区满了;2)程序执行到main的return语句;3)调用了std::endl了,能够强制刷新输出缓冲区(把缓冲的内容往屏幕上写) 4)当系统不太繁忙的时候,系统也会查看缓冲区内容,发现新内容也会正常输出到屏幕。
5、<<:定义: ostream &std::cout.operator<<(); 返回的是一个写入了给定值的cout对象。
6、std::endl:是一个模板函数名,相当于函数指针,可以暂时理解为函数。因为涉及到模板函数知识,以后再解答。
三、局部变量及初始化
1、随时用到随时定义;不像之前写linux驱动代码时,必须在函数前端定义。
2、初始化:
int a { 1 };//等同于int a = 1;
int abc[] { 1, 2, 3};//等同于int a[] = { 1, 2, 3};
同时有个优点:
int a = 3.5f;//强制类型转换为3
int a {3.5f};//编译器报错
四、auto
1、auto:变量的自动类型推断;
2、auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型;(声明时要赋予初值)
3、auto自动类型推断发生在编译期间,所以使用auto不会造成程序效率降低。
auto value1 = true;//value1为bool类型
auto value2 = 'a';
auto value3 = 3.5f;
auto value4 = 5;
五、头文件防卫式声明
1、实现:
#ifndef __TEST_H__
#define __TEST_H__
...
#endif //__TEST_H__
六、引用
1、可以理解为:为变量取了另一个名字,一般用&符号表示。取完别名后,这别名和变量本身我们就看成是同一个变量;
int value = 10;
int &refval = value;
value的别名就是refval,&在这里不是求地址运算符,只是起标识作用; 定义引用,并不额外占用内存,或者理解成引用和原变量占用同一块内存; 定义引用的时候必须初始化。
2、不能绑定到常量上去,const修饰的变量也不行;
3、左右类型要相同。
4、形参引用类型:
void function(int &value1, int &value2)
{
... //若是修改其值,其原变量值也会被改变
}
七、常量
1、const关键字:表示不能改变的意思;
const int value = 1;//一种承诺,表示这个变量的值我不会去改变(命名常量)
2、有趣现象:
const int value = 1;
//int &value1 = value;//编译报错
int &value2 = (int &)value;
value2 = 2;
cout << value << endl;// 输出1
cout << value2 <<endl; //输出2
cout << &value << endl;
cout << &value2 << endl;//两个地址相同
3、constexpr关键字 :c++11才引入的,也是个常量的概念。在编译的时候求值,能提升性能。
八、范围for语句
1、作用:除了C语言中常用的用法,c++还扩展了for的能力,遍历一个序列;
int var[] {12, 13, 14, 15, 16, 17, 18};
//for(auto x : var) //数组中var中每个元素,依次放入x中并打印x值。把var每个元素拷贝到x中。
for(auto &x : var) //省了拷贝动作,提高效率。
{
...
}
九、动态内存分配问题
1、c语言中供程序使用的存储空间:1)程序区;2)静态存储区(全局变量、静态变量);3)动态存储区(局部变量);
2、c++中,我们把内存进一步细分为5个区域:1)栈:一般函数内的局部变量都会放在这里,由编译器自动分配释放;2)堆:由程序员malloc/new分配,用free/delete来释放(若是忘记释放,在程序结束时由系统来回收);3)全局/静态存储区:放全局变量和静态变量,程序结束时释放;4)常量存储区:存储常量,例如:“Hello world”;5)程序代码区;
3、栈和堆不同的用途和区别:1)栈-空间有限的,大小由系统分配,分配速度快,程序员控制不了;2)堆:只要不超过实际拥有的物理内存,也在操作系统允许你能够分配的最大内存大小之内,都可以分配给你。分配速度比栈慢,可以随时使用new/malloc来分配,delete/free来释放,非常灵活;
4、new/delete一般使用格式:
1)指针变量名 = new 类型标识符;
2)指针变量名 = new 类型标识符(初始值);
3)指针变量名 = new 类型标识符[内存单元个数];//释放delete [] 指针变量名;
4)类名 *对象名 = new 类名;
十、nullptr
1、c++11中引入新关键字,表示空指针;
2、使用nullptr能够避免在整数和指针之间发生混淆;
3、NULL和nullptr实际上是不同类型,但是if判断是相等的;
4、注意事项:今后实现函数重载时,NULL(int)与nullptr(nullptr)的类型不同,会导致进入不同的重载函数;
5、结论:对于指针的初始化,以往用到的和指针有关的NULL场合,能用nullptr的全部用nullptr取代NULL;
十一、结构体回顾
1、结构:自定义的数据类型;
2、c++的结构和c中的结构有什么区别呢?
1、c++中的结构除了具备c中结构所有的功能外,还增加了很多扩展功能,其中最突出的功能之一是:c++中的结构不仅仅有成员变量,还可以在其中定义成员函数(或叫方法);
十二、权限修饰符
1、public和private权限修饰符:public(公有)、private(私有)和protected(保护);
十三、类简介
1、类也是用户自定义的数据类型;
2、定义了一个该类的变量,我们叫其为对象;
3、结构变量也好,对象也好,它们都是能够存储数据并且有某种类型的内存空间;
4、c++类内部的成员变量以及成员函数,默认的访问级别是private; 而c++的结构体内部的成员们默认访问级别为public;
5、c++结构体的继承默认的是public;c++类的继承默认是private;
6、类的定义代码一般放在.h中,头文件名可以跟类名相同。类的具体实现一般放在.cpp中。
十四、函数新特性
1、回顾:1)函数定义中,形参如果在函数体内用不到的话,则可以不给形参变量名字,只给其类型;2)函数声明时,可以只有形参类型,没有形参名;3)把函数返回类型放到函数名字之前,这种写法叫前置返回类型。
2、c++11新增后置返回类型:
int func(int a);//前置返回类型
auto func1(int a)-> void;//后置返回类型
十五、内联函数
1、在函数定义前增加关键字 inline,可以让函数变成内联函数;
2、当函数体很小,调用又很频繁的这些函数,我们可以引入inline;
3、inline影响编译器,在编译阶段对inline这种函数进行处理,系统尝试将调用该函数的动作替换为函数本体,通过这种方式来提升性能。
4、inline只是我们开发者对编译器的一个建议,编译器可以尝试去做,也可以不去做,这取决于编译器的诊断功能,也就是说决定权在编译器,我们控制不了;
5、内联函数就要放在头文件中,这样需要用到这个内联函数的.cpp文件都能够通过#include把这个内联函数的源代码包含进来,以便找到这个函数的本体源代码并尝试将该函数的调用替换为函数体内的语句。
6、优缺点:1)代码膨胀:所以内联函数体尽量要少;
7、注意:各种编译器对inline的处理各不相同。inline函数尽量简单,代码尽可能少。循环、分支、递归调用尽量不要出现在inline函数中,否则的话,编译器很可能会因为以上原因拒绝让这个函数成为一个inline函数;
8、constexpr可以看成更加严格的内联函数,其函数体要尽可能的短小;
9、#define宏展开也类似于inline。
十六、函数杂合用法总结
1、返回指针和返回引用的注意事项:
例一:
int *myFunc()
{
int tmpvalue = 9;
return &tmpvalue;//错误,因为这个函数执行完毕后,tmpvalue所在的栈会回收栈内资源,返回出去的
//指针已经是一个野指针,不能再使用。
}
int main()
{
int *p = myFunc();//p指向已经释放的内存,错误
...
}
例二:
int &myFunc()
{
int tmpvalue = 9;
return tmpvalue;
}
int main()
{
int &p = myFunc();//p指向已经释放的内存,错误
...
}
例三:
int &myFunc()
{
int tmpvalue = 9;
return tmpvalue;
}
int main()
{
int p = myFunc();//安全,因为tmpValue的值拷贝到p的内存中去了。
...
}
2、如果一个函数我们如果不调用的话,则该函数可以只有声明部分,没有定义部分(编译不报错);
3、普通函数,定义只能定义一次(定义要放在.cpp中),声明可以声明多次。一般函数定义.cpp文件会#include自己的函数声明文件(.h);
4、函数形参为引用,可以改变实参的值;在c++中更习惯使用引用类型的形参来取代指针类型的形参(避免值拷贝的问题);
十六、const详解
1、const char *, char const *,char *const三者的区别:
const char *p;//p指向的内存空间中的内容不能通过p来改变
char const *p;//与上面等价
char * const p;//定义的时候必须初始化。p一旦指向了一个内存地址后,就不可以再指向其他内存地址了
const char * const p;//p指向不能变,p指向的内容不能变
int i = 100;
const int &p = i;//代表p的内容不能通过p自己来修改
const int &b = 1;//合理,系统给分配了内存
十七、函数形参中带const
1、可以明确形参中那些能修改,哪些不能修改;
2、可以防止无意中修改了形参而修改了实参;
3、实参类型可以更加灵活;
十八、string类型简介
1、string:c++标准库中的类型,代表一个可变长字符串;
十九、定义和初始化string对象
char str[] = "I Love China";//c语言用法
#include <string>
using namespace std;
string s1;//默认初始化,s1 = ""; ""表示空串,表示里边没有字符
string s2 = "I Love China";//把字符串内容拷贝到了s2代表的一段内存中,拷贝时不包括末尾的\0;
string s3("I Love China");//跟s2的写法效果一样
string s4 = s2;//把s2中的内容拷贝到了s4所代表的一段内存中。
string s5(num, 'a');//把s5初始化为连续num个字符'a'组成的字符串,但这种方式不推荐,会在系统内部创
//建临时对象;
二十、string对象上的操作
#include <string>
using namespace std;
string s1;
if(s1.empty())//判断是否为空
{
...
}
s1.size();//返回字节数量
s1.length();//返回字符数量 与上一样
s1[n];//返回s1中第n个字符(n是个整形值),n代表一个位置,从0开始
string s2 = "I Love";
string s3 = "China";
string s4 = s2 + s3;//字符串的连接,返回连接后的结果,其实就是得到一个新的string对象
s1 = s2;//字符串对象赋值,用s2中的内容取代原来s1中的内容
s1 == s2;//判断两个字符串是否相等,大小写敏感。
s1.c_str();//返回一个字符串s1中内容常量指针,返回一个指向正规的c字符串的指针常量,也就是以\0结尾
二十一、vector类型
1、简介:可以理解为集合或者动态数组,可以把若干对象放在里边,也被称为容器;
#include <vector>
using namespace std;
vector<int> vec;//表示这个vec里边保存的是int型数据(int型对象)
2、<int>:模板,vector本身就是个类模板,<int>实际上就是类模板的实例化过程;
3、模板中不能使用引用,因为引用是个别名,不是个对象。不是对象不能往vector中放;
4、用法:
vector<string> mystr;//创建一个string类型的空的vector对象(容器),目前是空的
mystr.push_back("I");//向末尾添加,实际上是[0]中添加‘I’
vector<string> mystr2(mystr);//把mystr元素拷贝给了mystr2
vector<string> mystr3 = mystr2;//把mystr2元素拷贝给了mystr3
vector<string> mystr4 = {"I", "LOVE", "CHINA"};//用列表初始化方法赋值
vector<int> i(15, -200);//创建15个int类型的元素。每个元素的值是-200
vector<string> s(5, "hello");//创建5个string类型的元素,每个元素的值是hello;
vector<int> i1(20);//创建20个元素,[0]...[19],每个元素的值为0
vector<string> s1(5);//创建5个元素,每个元素的值为""
vector<int> i2 {10};//创建一个元素,值为10
vector<string> s2 {"hello"};//创建一个元素,值为"hello"
vector<string> s3 { 10 };//10不能作为string的内容,所以识别为创建10个元素
vector<string> s4 { 10,"hello" };//创建10个元素,每个元素的内容是"hello"
vector<int> i3 {10, 1};//创建2个元素,第一个元素值为10,第二个元素值为1
vector<int> i4 {"hello"};//语法错误
5、vector对象上的操作
1)判断是否为空empty(),返回类型是bool;
2)push_back():常用方法,用于向vector中的末尾增加一个元素;
3)size():返回元素个数;
4)clear():移除所有元素,将容器清空;
5)v[n]:返回v中第n个元素,n是整形值,防止越界,编译器检测不出来;
6)=:赋值,将一个容器的元素拷贝到指定容器中,注意目标容器中的元素会被清空
7)== !=:相等或不等,元素个数相同且内容相同则表示两个容器==,否则!=
8)范围for的应用:(注意不要在for内进行push_back,会导致值改变)
vector<int> vecvalue {1, 2, 3, 4, 5};
for(auto vecitem : vecvalue)
{
...
}
for(auto &vecitem : vecvalue)
{
...
}
二十二、迭代器
1、迭代器简介:是一种遍历容器内元素的数据类型,这种数据类型有点像指针,我们可以理解为迭代器用来指向容器中的某个元素。
c++中很少用[]来访问元素,更加常用的访问方式就是用迭代器;
2、容器的迭代器定义:
vector<int> iv = {1, 2, 3};
vector<int>::iterator iter;//定义迭代器,也必须是vector<int>
3、迭代器begin()/end()操作,反向迭代器rbegin()/rend()操作:begin()/end()用来返回迭代类型,rbegin()/rend()用来返回迭代类型
1)begin():
iter = iv.begin();//如果容器中有元素,则begin返回的迭代器,指向的是容器中的第一个元素
//相当于iter指向了iv[0]
2)end():
iter = iv.end();//end返回的末端元素的后边,指向一个不存在的元素iv[3]
3)如果一个容器为空,那么begin()和end()返回的迭代器就相同
4)rbegin():
iter = iv.rbegin();//指向容器中最后一个元素
5)rend():
iter = iv.rend();//指向容器前一个元素,相当于[-1]
4、用法:
1)常规用法:
vector<int> iv = {1, 2, 3};
for(vector<int>::iterator iter = iv.begin(); iter != iv.end(); iter++)
{
...
}
2)反向/逆向迭代器:
vector<int> iv = {1, 2, 3};
for(vector<int>::reverse_iterator riter = iv.rbegin(); riter != iv.rend(); riter++)
{
...
}
5、迭代器运算符
1)*iter:返回迭代器iter所指向元素的引用。必须要保证这个迭代器指向的是有效的容器元素,不能指向end();
2)iter++:让迭代器指向容器中下一个元素;
3)iter--:指向容器中的上一个容器;
4)==:判断两个迭代器是否相等,如果两个迭代器指向的是同一个元素,就想等,否则就不等;
5)
class student
{
public:
num;
}
vector<student> sv;
student mystu;
mystu.num = 100;
sv.push_back(mystu);
vector<student>::iterator iter;
iter = sv.begin();
cout << (*iter).num << endl;
cout << iter->num << endl;
6、const_iterator迭代器:表示值不能改变,与常量指针类似,容器中用cbegin(),cend()返回常量迭代器;
7、在操作迭代器的过程中(如使用了迭代器的for循环),千万不要改变vector容器的容量,也就是不要增加或者删除容器中的元素。可能会使指向容器元素的指针、引用、迭代器失效。可能会导致程序崩溃;
8、灾难程序演示:
1)例一:
vector<int> iv {1, 2, 3, 4, 5, 6};
auto beg = iv.begin();
auto end = iv.end();
while(beg != end)
{
iv.insert(beg, 100);//插入新值,第一个参数为插入位置,第二个参数为插入的元素
/*此处插入,会导致迭代器失效,比如begin、end失效。
*具体哪个迭代器失效,取决于这个容器vector内部的实现原理
*不管哪个迭代器失效了,最明智的做法就是插入数据时,插入完毕后立即break处循环体即可
*/
++beg;
}
2)例二:
vector<int> iv {1, 2, 3, 4, 5, 6};
for (auto iter = iv.begin(); iter != iv.end(); ++iter)
{
iv.erase(iter);//移除iter位置上的元素,返回下一个元素的内存地址
}
//解决:
while(!iv.empty)
{
auto iter = iv.begin();
iv.erase(iter);
}
9、范例演示:
1)例一:
string str("i love china!");
for(auto iter = str.begin(); iter != str.end(); ++iter)
{
*iter = toupper(*iter);//转大写
}
2)例二(vector容器常用操作与内存释放):
struct conf
{
char itemname[40];
char itemcontent[100];
}
char *getInfo(vector<conf *> &conflist, const char *pitem)
{
for(auto pos = conflist.begin(); pos != conflist.end(); ++pos)
{
if(_stricmp((*pos)->itemname, pitem) == 0)
{
return (*pos)->itemcontent;
}
}
return nullptr;
}
int main()
{
conf *pconf1 = new conf;
strcpy_s(pconf1->itemname, sizeof(pconf1->itemname), "ServerName");
strcpy_s(pconf1->itemcontent, sizeof(pconf1->itemcontent), "1区");
conf *pconf2 = new conf;
strcpy_s(pconf2->itemname, sizeof(pconf2->itemname), "ServerID");
strcpy_s(pconf2->itemcontent, sizeof(pconf2->itemcontent), "100000");
vector<conf *> conflist;
conflist.push_back(pconf1);//[0]
conflist.push_back(pconf2);//[1]
char *p_tmp = getInfo(conflist, "ServerName");
if(p_tmp != nullptr)
cout << p_tmp << endl;
//内存回收,容器中保存的是指针的话,可以使用如下去灵活释放
vector<conf *>::iterator pos;
for(pos = conflist.begin(); pos != conflist.end(); ++pos)
{
delete (*pos);
}
conflist.clear();
}
二十三、类型转换
1、static_cast<type>(express):静态转换,变异的时候就会进行类型转换的检查,与C语言中强制类型转换差不多,显示隐示都可以用其来转换;
2、dynamic_cast<type>(express):主要应用于运行时类型识别和检查。主要用来父类型和子类型之间的转换用的(父类型指针指向子类型对象,然后用其把父指针类往子指针类转);
3、const_cast<type>(express):主要作用是去除指针或引用的const属性;
4、reinterpert_cast<type>(express):随意转,在编译的时候回进行类型转换的检查,一般用于1)将一个整形地址转换成指针,一种类型指针转换成另一种类型指针,按照转换后的内容重新解释内存中的内容;2)也可以从一个指针类型转换成一个整形等;
5、以上这四个类型转换都被称呼为“命名的强制类型转换”