《c++新经典》笔记
第一章c++语言课程详细介绍
第二章
2-1语言特性、工程构成、可以执性
2-2auto、头文件防卫、引用、常量
- 局部变量及初始化
int a[] {11,12,34};//注意这里没有用等号,{}里包含一组数据的情形
//int abc = 3.5f;//丢了.5, 被系统截断了
//int abc{3.5f}; //无法编译成功,系统执行了一个从浮点数到整数的转换
- auto
//二:auto:变量的自动类型推断
//auto可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型;(声明时要赋予初值[初始化])
//auto自动类型推断发生在编译期间。所以使用auto不会造成程序效率降低。
auto bvalue true;//bool
auto ch ='a';//char
auto dv =1.2f;
auto iv 5;
- 头文件防卫式声明
//#ifdef,ifndef条件编译
//#ifndef标识符 //当标识符没有定义过(没有用define定义过),则对程序段进行编译。
//程序段
//#endif
- 引用
//四:引用
//引用理解成:为变量起了另外一个名字,一般用&符号表示。起晚别名后,这别名和变量本身我们就看成是同一个变量;
int value = 10;
int &refval = value://value的别名就是refval,&在这里不是求地址运算符,只是起标识作用。
//定义引用,并不额外占用内存,或者理解成,引用和原变量占用同一块内存。
//定义引用的时候必须初始化,不然你给谁起别名。
refval = 3;
cout <<value <<endl;
cout <<refval <<endl;
//int &refval2:
//int &refva13=10://引用必须绑定到变量上去,绑定到对象上也可以。不能绑定到常量上去。
//f1oat &refval.2=value://不可以,类型要相同。
int a =3;
int &b=a://引用,注意&符号在=的左边。
int p=a://注意&符号在=右边。
- 常量
//五:常量:不变的量
//13;24.5
//consti关键字:表示不变的意思
const int var = 7;//一种承诺,表示这个变量的值我不会去改变(命名常量)。
//var=15;编译时报错,编译器会检查这个const.承诺
int &var2 = (int &)var;
var2 = 18;
cout <<var <<endl;//7
cout <<var2 <<endl;//18,
//constexpr关键字:c++11才引入,它也是个常量的概念,在编译的时候求值,肯定能提升性能。
constexpr int var = 1;
int b = 5
constexpr int func(int abc)
{
abc = 14;
int a4 = 6;
for(int i = 0;i < 100; i++){
cout<<i<<endl;
}
}
//constexpr int var2 = func(12)//这样调用编译器会报错//如果去掉cout<<i<<endl;就不报错
int k2 = func(56);//这样正常不报错
2-3命名空间简介、基本输入输出精解
- 命名空间
//一:命名空间概念简介
//radius():radius();
//命名空间就是为了防止名字冲突而引入的一种机制。系统中可以定义多个命名空间,每个命名空间都有自己的名字,不可以同名:
//大家就可以把这个命名空间看成一个作用域,我们在这个命名空间里定义函数,跟你另外一个命名空间里定义的函数,即便同名,也互不影响。
//(1)命名空间的定义:
//namespace命名空间名
//{
// ......
//}
//(2)命名空间的定义可以不连续,甚至可以写在多个文件中。如果以往没有定义这个命名空间,那么“namespace命名空间名”这种写法
//就相当于定义了一个命名空间,如果以往你已经定义了这个命名空间,那么“namespace命名空间”这种写法 就相当于 打开已经存在的
//命名空间并向其添加新的成员声明
//(3)外界如何访问这个 某个命名空间中的radius()函数
//格式:命名空间名::实体名 -----其中这::叫“作用域运算符”
//zhangsan::radius();
- 基本输入输出
//二:基本输入输出cin、cout精解
//c+中,我们不用printf,而是用c++提供的标准库。
//不要排斥c+标准库,与c++语言一起学习。
//iostream库(输入输出流)什么叫流:流就是一个字符序列。
std::cout<<"很高兴大家和老师一起学习c++n“:
//(1)td:命名空间,标准库命名空间。大家要记这个名字
//(2)cout,发音c out(console output),是个对象,“标准输出”对象,我们就认为这个对象是屏幕。
//c语言中叫结构,c++中我们叫类
//c语言中我们定义一个结构变里,在c++中,我们不叫结构变里,我们就他对象。
//(3)<<:“输出”运算符。直接扎到cout去了,就表示将《<右边的值写到cout去了;。
//<<可以当成函数,有参数。第一个参数在左边,就是cout对象。
//“很高兴大家和老师一起学习c++“"当成<<的第二个参数,在<<右边
//(4)\n:换行符,跟c语言中一个意思
int x 3;
//std::cout<<x<<"的平方是"<<x*x<<"n":/f“%d..
std::cout<<x<<“的平方是"x*x<<std::endl:
x++:
std::cout<<x<<“的平方是“<<x*x<<std::endl:
//std::endl:也是个对象,也可以当成一个操作符 是一个模板函数名,相当于函数指针 之后会详细讲解
//能看到std::cout的地方,就能看到std::endl
//std::endl一般都位于std::cout语句的末尾。
//作用:
//(1)输出换行符\n
//(2)强制刷新输出缓冲区,缓冲区中所有数据都被系统清除了。
//输出缓冲区:一段内存。cout输出的时候实际是往输出缓冲区输出内容,那么输出缓冲区什么时候把内容输出到屏幕去的呢?
//a)缓冲区满了
//b)程序执行到main的return语句;
//c)调用了这个std::endl了,能够 强制 刷新 输出缓冲区(把缓冲区内的内容往屏幕上写)
//d)当系统不太繁忙的时候,系统也会查看缓冲区内容,发现新内容也会正常输出到屏幕
//ostream &std::cout.operator<(); //<<定义,<<返回的是一个写入了给定值的cout对象。
//std::cout<<"很高兴大家和老师一起学习c++\n"://返回的是cout
/*std::cout<<x<<“的平方是"<<xx<<std::endl:等价于
(std::cout<<x)<<“的平方是”<<x*x<<std::endl:等价于
(std::cout<<x)<<“的平方是“)<<x*x<<std::endl:等价于
((std::cout<x)<<“的平方是“)<<x*x)<<std::endl:*/
int i=3;
//std:cout<<i--<<i--; //2,3,其他编译器中
//要尽量避免 在一个表达式中多次的(超过1次的)改变一个变量值;
//i--;
//std::cout<<i;
//i--;
//std::cout<<i;
//std::cin 基本输入;
//cin:(c in),这也是个对象,叫标准输入。scanf &
std::cout<<"请输入两个数:"<<std::endl;
int value1 = 0, value2 = 0;
std::cin >> value1 >> value2;
std::cout<<value1 <<"和"<< value2 << "相加结果为:"<< value1 + value2<<std::endl;
//(1)cin也是一个iostream相关对象。叫“标准输入”。理解为键盘
//(2) >> 是一个“输入”运算符
//(3)返回其左侧运算对象作为其计算结果
/*std::cin >> value1 >> value2; 相当于
(std::cin >> value1) >> value2;*/
//<<:运算符重载
2-4范围for、new内存动态分配、nullptr
- 范围for语句:用于遍历一个序列
int v[]{12, 13, 14, 16, 18}
//for(auto x: v)//数组v中每个元素,依次放入x中并打印x值。把v每个元素拷贝到x中,打印x值;
for(auto &x:v)//省了拷贝这个动作, 提高了系统效率
{
cout<<x<<endl;
}
- 动态内存分配问题:供程序使用的存储空间,有程序区,静态存储区,动态存储区。
//c++中,我们把内存进一步详细分为5个区域;
//(1)栈:一般函数内的局部变量都会放在这里,由编译器自动分配和释放
//(2)堆:程序员malloc/new分配,用free/delete来释放。忘记释放后,系统会回收
//(3)全局/静态存储区:放全局变量和静态变量static。程序结束时系统释放。
//(4)常量存储区:"I love China"
//(5)程序代码区
//堆和栈不同的用途和区别
//(1)栈 空间有限。这是系统 int a = 4; 分配速度快,程序员也控制不了
//(2)堆 只要不超过实际的物理内存,也在操作系统允许能够分配的最大内存大小之内,都可以分配给你
//分配速度比栈慢,可以随时使用new/malloc来分配,free/delete。非常灵活
//malloc和free 在c语言中,用malloc和free从堆中来分配和释放内存用,malloc()和free()是函数
//malloc(memory allocation):动态内存分配
//一般形式:
//void *malloc(int MumBytes);//NumBytes:要分配的字节数。分配成功则返回指向被分配内存的指针,分配失败则返回NULL。
//当这段分配的内存你不使用的时候,你应该用free()函数来将内存释放掉,供其他地方使用。
//free:
//void free(void FirstByte);将之前用malloc分配的内存空间还给程序(操作系统》,也就是说释放了这块内存,这样这块内存
//就被系统回牧,并在需要的时候由系统分配出去再给其他释放。
int p = NULL://c语言写法―等价于数字0
p = (int *)malloc(sizeof(int));//在堆中分配4个字节
if(p != NULL)
{
//分配成功
*p = 5;
cout<<*p<<endl;
free(p);
}
char *point = NULL:
point = (char *)malloc(100 * sizeof(char)) ;//100个位置
if (point != NULL)//if(point)
{
//strcpy(point,"hello world!");
strcpy_s(point,100,"hello world!");//比strcpy安全的多
cout << point << endl;
free(point);
}
int*p= (int*)malloc(sizeof(int) * 100);//分配可以放得下100个整数的内存空间
if (p != NULL)
{
int*q= p;
*q++ = 1;//==>*(q++); -->*q= 1; q++;
*q++= 5;
cout << *布p << endl;//1
cout<<*(p +1]<< endl;//5
free(p):
}
//new和delete :是运算符(标识符)。c++t中就用new/delete分配和释放内存,不再使用malloc和free来分配和释放内存
//new, delete也和malloc, free干了一样的事就是分配和释放内存。同时new, delete还干了更多的事。
//new—般使用格式:
//(1)指针变量名= new类型标识符;
//(2)指针类型名= new类型标识符(初始值);//注意这里是圆括号括起来,表示初始值
//(3)指针类型名= new类型标识符[内存单元个数]://注意,这里[]
int *myint = new int: //int p = (int *)malloc(sizeof(int)):
if (myint != NULL)
{
*myint = 8;//*myint带包指针指向的变量
cout<<*myint<< endl;
delete myint;//释放单个myint的空间
}
int *pa = new int[100];//开辟一个大小为100的整型数组空间
if (pa != NULL)
{
int *q= pa;
*q++ =12://[0]=12
*q++ = 18;//[1] =18 ,执行完这行,这个q其实已经指向[2]
cout<<*pa << endl;//12
cout<<*(pa+1)<< endl;//18
//释放内存了
delete[] pa; //释放int pa数组空间
//new时候我们用[],那么delete就必须用[],[]不写数组大小
}
//额外补充知识
//(1)配对使用 有malloc成功必然有free,有new成功必然有delete
//(2)free/delete,不要重复调用
//malloc/free和new/delete区别;
3.nullptr
//nullptr代表的也是空指针
char *p = NULL;//NULL实际就是0
char *q = nullptr;
int *p = nullptr;
//int a = nullptr;//直接报错,nullptr与NULL并不相同,不能混用
//int b = NULL;//可以
if(p == nullptr)
{
cout<<"good"<<endl;
}
if(q == NULL)
{
cout<<"good"<<endl;
}
//使用nullptr能够避免在整数和指针之间发生混淆。
cout << typeid(NULL).name() << endl;
cout << typeid(nullptr).name() << endl;
//NULL和nullptr实际上不是同一类型
//结论:对于指针的初始化,和大家以往用到的和指针有关的NULL的场合,能用nullptr的大家全部用nullptr取代NULL
int *myint = new int(18); //int p = (int *)malloc(sizeof(int));
if(myint != nullptr)
{
cout<"good2"<< endl;
}
2-5结构、权限修饰符、类简介
- 结构回顾
//结构:自定义的数据类型
//回顾
struct student{
//成员变量
int number;//学号
char name[100];//学生名
void func()
{
number++;
return;
}
};
void func(student &tmpstu)//结构体引用作为参数
{
tmpstu.number = 2000;
strcpy_s(tmpstu.name, sizeof(tmpstu.name), "who");
return;
}
void func(student *ptmpstu)//指向结构体的指针做函数参数
{
ptmpstu->number = 2000;
strcpy_s(ptmpstu->name, sizeof(ptmpstu->name), "who");
return;
}
student student1;
student1.number = 1001;
strcpy_s(student1.name, sizeof(student1.name), "zhangsan");
student1.func();
//c++中的结构和c中的结构有什么区别呢
//c++中的结构除了具备c中的结构的所有功能外,还增加了很多扩展功能,其中最突出的扩展功能之一就是:
//c++中的结构不仅仅有成员变量,还可以在其中定义成员函数(方法);
- public和private权限修饰符
//二:public和private权限修饰符public(公有),private(私有),protected(保护),本节课只谈public,private。
//pub1ic:公共的意思,用这个修饰符修饰结构/类中的成员变量/成员函数,就可以被外界访问。
//一般我们需要能够被外界访问的东西,就定义为pub1ic。就好像是该类的外部接口一样。
//private:私有的意思,用这个☒饰符修饰结构/类中的成员变量成员函数,只有被内定义的成员函数才能使用。
- 类简介
//三:类简介:类也是用户自定义的数据类型
//大家已经道了:
//(1)不管c还是c++,结构都用struct定义
//(2)老师为了方便大家理解,曾经说过:大家就把c语言中的结构当成c++中的类;这个话并不全面,知识为了方便大家理解。
//那么结构和类到底有什么区别呢?
//(2.1)类这个东西,只在c++中才有这个概念,c中没有类这个概念
//(2.2)结构用struct定义,类用class定义。
//在c中,我们定义一个属于该结构的变量,我们叫结构变量。
//在c++中,我们定义了一个属于该类的变量,我们叫对象。
//说白了,结构变量也好,对象也好,他们都是一块能的够存储镂数据并且具有某种类型的内存空间。
//(2.3)c++中,结构和类极其类似,区别有两点:
//a)c++结构内部的成员变量以及成员函数 默认的防问级别都是pub1ic。
//c++类内部的成员变量以及成员函数 默认的防问级别是private。
//b)c++结构体继承默认的是public,而c++类的继承默认都是private
- 类组织
//四:类的组织,书写规苑:
//类的定义代码会放在一个.h头文件中,头文件名可以眼类名相同,student.h
//类的具体实现代码,放在一个.cpp文件中,student.cpp
2-6函数新特性、内联函数、const详解
- 函数回顾与后置返回类型
//void func123(int, int);//函数声明(函数原型)
//void func123(int a, int b)//函数定义
//auto:变量自动类型推断
auto func123(int a, int b) -> void;//函数声明
auto func123(int a, int b) -> void;//函数定义
{
return;
}
//函数定义中,形参如果在函数体内用不到的话,可以不给形参变量名字,只给其类型
//函数声明,可以只有形参类型,没有形参名
//把函数返回类型放到函数名字之前,这种写法,叫前置返回类型
//func123(12, 13);
//c++11中,后置返回类型, 就是在函数声明和定义中,把返回类型写在参数列表之后
//前面放auto,表示函数返回类型放到参数列表之后,而放在参数列表之后的返回值类型是通过->开始的
- 内联函数
inline int myfunc(int testv)//函数定义前加inline,这个普通函数就变成了内联函数
{
return 1;
}
//二:内联函数:在函数定义前增加了关键字inline,导致该函数变成内联函数。
//函数体很小,调用有很频繁的这种函数。咱们引入inline(内联函数)
//(1)inline响编译器,在编译阶段对inline这种函数进行处理,系统尝试将调用该函数的动作替换为函数本体。通过这种方式,来提升性能
//int a = func(5);//int a = 1;
//(2)inline只是开发者对编译器的一个建议,编译器可以尝试去做,也可以不去做,这取决于编译器的诊断功能,也就是说,决定权在编译器,我们控制不了。
//(3)内联函数的定义就要放在头文件。这样需要用到这个内联函数的.cpp文件能够通过#include把这个内联函数的源代码#include。把这个内联函数的源代码#include进来。
//以便找到这个函数的本体源代码并尝试将该函数的调用替换为函数体内的语句
//优缺点:
//代码膨胀的问题;所以内联函数函数体尽量要小。
//注意:各种编译器对nline的处理各不相同。inline函数尽量简单,代码尽可能少。循环,分支,递归调用尽量不要出现在inlinel函数中。
//否则的话,编译器很可能会因为你写这些代码的原因拒绝让这个函数成为一个inline函数。
//constexpr函数,可以看成是更严格的一种内联函数
//#define宏展开也类似于inline
- 函数杂合用法总结
//(1)函数返回类型为void,表示函数不返回任何类型,但是我们可以调用一个 返回类型是void的函数 让 它作为另一个 返回类型是void的函数的返回值
void func()
{
}
void funcb()
{
return func(); //这也可以
}
//(2)函数返回指针和返回引用的情况
//(3)没有形参可以保持形参列表为空(),或者int myfunc(void)
//(4)如果一个函数我们不调用的话,则该函数可以只有声明部分,没有定义部分
//(5)普通函数,定义只能定义一次(定义放在.cpp文件中),声明可以声明多次。 一般函数定义.cpp文件会#include自己的函数声明文件(.h)
//(6)void func(int &ta, int &tb), 在c++中,更习惯用引用类型的形参来取代指针类型的形参。提倡在c++中,多使用引用类型的形参。
//(7)c++中,函数允许重名,但是形参列表的参数类型或者数量应该用明显的区别。
//void fs(const int i){}
//void fs(int i){}//这两个函数会冲突,会报错
- const char*、char const*、char* const三者的区别
//四:const char*、char const*、char*const三者的区别
//const int abc 12:
//char*p; const混用;
//4.1:const char *p;
//char str[]="I Love China!";
//const char *p;//p指向的东西不能通过p来修改(p所指向的目标,那个目标中的内容不能通过p来改变):
//char const *p;
//p =str;
p* = 'Y';//语法错
//p+:
//p+:
//stx[0]='Y:/这是oK的。
char const *等价于 const char*p:
char str[] = "I Love China!";
char * const p = str; //定义的时候必须初始化
//p一旦指向了一个东西之后,就不可以在指向其他的东西了
//p++; //不可以,p一旦指向了一个东西之后,就不可以再指向其他的东西了
*p = 'Y';//但可以修改p指向的目标的内容。
//const char * const p = str;
char const * const p = str; //p的指向也不能变,p指向的内容也不能通过p来改变
//--
int i = 100;
const int &a = i;//代表a的内容不能通过a自己来修改
i = 100;
//a = 500;//不合法
//int &b = 31;//错误
const int &b = 31; //分配了内存的
cout<<b<<endl;
- 函数形参中带const
//函数形参中使用const
//把形参写成const 的形式有很多的好处
//(1)可以防止你无意中修改了形参值导致实参值被无意修改
//(2)实参类型更加灵活
void fs2(const student &sut)
{
}
student abc;
abc.num = 200;
fs2(abc);
cont<< abc.num << endl;
const student &def = abc;
fs2(def);//这里若是fs2的定义为void fs2(student &sut)就会报错,出现无法从“const student”转化为“student &”
fs2(12);//这里也是同上面,void fs2(const student &sut)这种定义方式是对的,void fs2(student &sut)这种就会报错
2-7string类型介绍
- 前言
//一:前言int ,float, char ,c++标准库:string, vectoro
//string:可变长字符串的处理。vector一种集合或者容器的概念,。
- string类型简介
//二:string类型简介:c++标准库中的类型,代表一个可变长字符串;
//string这个类型,看成一个类类型(类似于struct) ;
//char str[100]="I Love China"; //c语言中用法
- 定义和初始化string对象
//三:定义和初始化string对象
string s1;//默认初始化,s1 ="":""空串,表示里边没有字符;
string s2 = "I Love China!";//把I Love China!这个字符串内容拷贝到了s2代表的一段内存中。拷贝时不包括末尾的\0。
string s3("I Love China");//跟s2的写法效果—样
string s4 = s2;//把s2中的内容拷贝到了s2所代表的一段内存中。
int num = 6;
string s5(num, 'a');//aaaaaa, 把s5初始化为连续num个字符'a'组成的字符串,这种方式不太推荐,因为在系统内部创建临时对象。
- string对象上的操作
//四:string对象上的操作
//a)判断是否为空empty(),返回的布尔值
//string s1;
//if(s1.empty())//成立
//{
//cout <<"s1为空”<< endl ;
//}
//b)size()/length():返回字节/字符数量代表该字符串的长度。unsigned int
string s1;
cout << s1.size()<< endl;
cout << s1.length(<< endl;
string s2="我爱中国";
cout << s2.size() << endl;
cout << s2.length() << endl;
//c)s[n]:返回s中的第n个字符(n是个整型值),n代表的是一个位置,位置从0开始,到.size()-1;
//如果用下标n超过这个范围的内容,或者本来人家一个空字符串,你也用s[n]去访问,都会产生不可预测的结果;
string s3 ="I Love China!":
if (s3.size( >4)
{
cout < s3[4]<< endl;//vs3[4] = 'w';
cout << s3<< endl;//I Lowe China!
}
//d)s1+s2;字符串的连接,返回连接后的结果,其实就是得到一个新的string对象。
string s4 = "abcd";
string s5 = "hijk";
string s5 = s4 + s5;
cout < s6 << endl;
//e)s1 = s2;字符串对象赋值,用s2里边的内容取代原来s1里的内容
string s7 = "abcd";
string s8 = "de";
s7 = s8;
cout << s7 << endl;
//f) s1 == s2:判断两个字符串是否相等。大小写敏感:也就是大小字符跟小写字符是两个不同的字符。abc abC
//相等:长度相同。字符全相同。
string s9 = "abc";
string s10 = "abc";
if (s9 == s10)
{
cout <<"s9 == s10"< endl:
}
//g) s1 != s2:判断两个字符串是否不相等
//h)s.c_str();返回一个字符串s中的内容指针。返回一个指向正规的c字符串的指针常量,也就是以\0结尾的。
//这个函数的引入是为了与c语言兼容,在c语言中没有string类型,所以我们得通过string类对象的c_str(成:
//员函数把string对象转换成c语言中的字符串样式;
string s10 = "abc";
const char *p = s10.c_strO://abC
char str[100] ;
strcpy_s(str,sizeof(str), p);
cout<< str << endl;//"abcT
//这也是一种初始化string的方式,用的是c语言形式的字符数组(字符串)来初始化string
string s11(str);//用c语言的字符数组初始化string类型
//i)读写string对象
//string s1;
//cin >>s1; //从键盘输入:abc
//cout << s1 <<endl; // abc
//j)字面值和string相加+
string s1 = "abc";
string s2 = "defg";
string s3 = s1 + " and" + s2 + 'e';
cout << s3 << endl; //abc and defge
//string s5 = "abc" + "def"://语法上不允许这么加
//string s5 = "abc"+ sl +"def"//中间夹一个string对象,语法上就允许。
//string s5 = "abc”+"def”+ s2:错误,看来两个字符串不能挨着相加,否则就会报语法错
//k)范围for针对string的使用: c++11中提供了范围for:能够遍历一个序列中的每一个元素//string可以看成是一个字符序列;
string s1 = "I Love China";
//for (auto c : s1)//auto:变量类型自动推断char ,二章的第三节
//{
// cout<< c << endl; //每次输出一个字符,后边跟一个换行符
//}
for(auto &c : s1)
{
//toupper()把小写字符转换为大写,大写字符没变换
c = toupper(c); //因为c是一个引用,所以这相当于改变s1中的值;
}
cout << s1 << endl; //I LOVE CHINA
2-8vector类型介绍
- vector类型简介
//一;vector类型简介:标准库:集合或者动态数组。我们可以把若干对象放在里边。
//vector它能把其他对象装进来,也被称为容器。
vector<int> vjihe;//表示这个vjihe 里边保存的是int型数据(int型对象);
//<int>:类模板,vector本身就是个类模板,<int>实际上就是类模板的实例化的过程。
//vector当成类型(残缺的类类型)
//vector <int>:在vector之后加一对<>,<>内部放上类型信息。(完整的类类型)
struct student{
int num;
};
vector<student> studlist;
vector< vector<string> > strchuan; //可以,该集合里面的每一个元素又都是一个vector<string>对象:集合套集合。
vector <int *> vjihe2;
//不能往vector里面装引用;
//vector<int &> vjihe3; //引用是个别名,不是个对象。不是对象不能往vector里边放。
- 定义和初始化vector对象
//二:定义和初始化vector对象
//a)空vector
vector<stping>mystr;//创建一个string类型的空的vector对象(容器),目前这个mystr里不包含任何元素;
// push_backO:
mystr.push_back("abcde");
mystr. push_back("def") ;
//b)元素拷贝的初始化方式
vector<string> mystr2(mystr);//把mystr元素拷贝给了mystr2;
vector<string> mystr3 = mystr;//把mystr元素拷贝给了mystr3:
//c)c++11标准中,用列表初始化方法给值,用0括起来;
vector<string> mystr4 = {"aaa","bbb", "ccc"};
//d)创建指定数量的元素。有元素数量概念的东西,一般会用圆括号;
vector<int> ijihe (15,-200);//创建15个int类型的元素,每个元素的值是-200;
vector<string> sjihe(5,"hello");//创建5个string类型的元素,每个元素的值是hello.
vector<int> ijihe2(20); //20个元素,【0】。。【19】,每一个元素的值都是0;
vector<string> sjihe2(5); //5个元素,下标【0】。。。【4】,每个元素的值为“”;
//e)多种初始化方式,()一般表示对象中元素数量这么个概念。{} 一般表示元素内容这么个概念。但不绝对。
vector<int> i1(10);//表示10个元素,每个元素值是缺省的0;
vector<int> i2{ 10 };//表示是一个元素,该元素的值为10
vector<string> snor{"hello" };//一个元素,内容是"hello";
vector<string> s22{ 10 };//10个元素,每个元素""。
vector<string> s23{ 10,"hello" };//10个元素,每个元素的内容都是"hello"
vector<int> i3(10, 1);//10个元素,每个元素是1;
vector<int> i4{10, 1};//2个元素,第一个元素是10,第二个元素是1;等同于初始化列表
//要想正常的通过{}进行初始化,那么{}里边的值的类型,必须要跟vector后边这个<>里边的元素类型相同。
//vector<int> i22{"hello"};//语法错误
- vector对象上的操作
//三:vector对象上的操作:最常用的是不知道vector里有多少个元素。需要动态增加/减少。
//所以,一般来讲,先创建空vector
//vector很多操作string很类似。
//a)判断是否为空emptyO,返回的是布尔值的。
vector<int> ivec;
if (ivec.empty())
{
cout <<"ive为空”<< endl;
}
//b)push_back:常用方法,用于向vector中的末尾增加一个元素;
ivec.push_back(1);
ivec.push_back(2);
for (int i = 3; i= 100; i++)
{
ivec.push_back(i);
}
//d)clear; 移除所有元素,将容器清空
/*ivec.clear();
cout<< ivec.size() << endl;*/
//e)v[n]:返回v中第n个元素(n是个整型值)代表位置,位置是从0开始,必须小于.size(),如果引用的下标超过这个范围,
//或者用叮访问了一个空的vector,那么就会产生不可预测的结果。编译器发现不了。
//f)=赋值
vector<int> ivec2;
ivec2.push_back(111):
ivec2 = ivec; //ivec2得到了100个元素,ivec2原来的元素就消失(被冲掉了);
ivec2= {12,13,14,15]://用中的值取代了ivec2原有值;
cout << ivec2.size() << endl;
//g)==, != : 相等,不先等;
//两个vector相等:元素数量相同,对应位置的元素值也得一样。否则就是不等。
vector<int> ivec2;
ivec2 = ivec;
if (ivec2== ivec)
cout << "ivec2== ivec"<< endl;//成立
ivec2.push_back(12345);
if (ivec2 != ivec)
cout <<"ivec2 != ivec"<< endl;//成立
ivec.clear();
ivec2.clear();
if (ivec2== ivec)
cout<< "ivec2== ivec"<< endl;//成立
//h)范围for的应用
vector<int> vecvalue{1, 2, 3, 4, 5};
for(auto &vecitem : vecvalue)
{
vecitem *= 2;//扩大一倍
}
for(auto &vecitem : vecvalue)
{
cout<< vecitem << endl;
]
//范围for进一步讲解
for(auto vecitem : vecvalue)
{
vacvalue.push_back(888);//导致输出彻底乱套
cout<< vecitem << endl;
}
//结论:在for语句中(遍历一个容器等等类型操作中),千万不要改动vector容器的容量,增加/删除都不可以
//千万不要写出这种代码
2-9迭代器精彩演绎、失效分析及弥补、实战
- 迭代器简介
//一:迭代器简介
//vector ,
//迭代器是一种遍历容器内元素的数据类型,这种数据类型感觉有点像指针,我们理解的时候就理解为迭代器用来指向容器中的某个元素。
//string,vector,[],很少用[],更常用的访问方式就是用迭代器(更通用)。
//通过迭代器,我们就可以读容器中的元素值,读string中的每个字符,还可以修改某个迭代器所指向的元素值。
//++,--
//list, map ,尽量学会用迭代器来访问容器中的元素。
- 容器的迭代器类型
//二:容器的迭代器类型
vector<int> iv = {100,200,300];
vector<int>::iterator iter://定义迭代器,也必须是 vector<int>
//大家在理解的时候,就把整个的vector<int>::iterator理解成一个类型,这种类型就专门应用于迭代器。
//当我们用这个类型定义一个变量的时候,这个变量,就是个迭代器,这里iter这个变量就是个迭代器。
- 迭代器begin()/end()操作,反向迭代器rbegin()/rend()操作
//三:迭代器begin(/end(操作,反向迭代器rbegin()/rend()操作
//begin()/end()用来返回迭代类型。rbegin()/rend()用来返回迭代类行。
//a)begin(返回一个迭代器类型(大家就理解成返回一个达代器);
iter = iv.begin()://如果容器中有元素,则begin返回的迭代器,指向的是容器中的第一个元素。
//相当于iter指向了iv[0]:
//b)end():返回一个迭代器类型(大家就理解成返回一个迭代器);
iter = iv.end();//end返回的迭代器指向的并不是末端元素。而是末端元素的后边,那这个后边怎么理解?
//我们就理解成end()指向的是一个不存在的元素。
//c)如果一个容器为空,那么begin(和end(返回的迭代器就相同。
/*vector<int> iv2;
vector<int>::iterator iterbegin = iv2.begin();
vector<int>::iterator iterend = iv2.end();
if (iterbegin = iterend)
{
cout<<“容器iv2为空”<< endl;
}*/
//d)
vector<int> iv = {100, 200, 300};//定义一个容器
for(vector<int>::iterator iter = iv.begin(); iter != iv.end(); iter++)
{
cout << *iter << endl; //100, 200, 300;
}
//反向迭代器:大家想从后往前遍历一个容器,那么反向迭代器就比较方便。
//反向迭代器(逆向迭代器),用的rbegin(), rend();
//rbegin():返回一个反向迭代器,指向反向迭代器的第一个元素;
//rend():返回一个反向迭代器,指向反向迭代器的最后元素的下一个位置。
for (vector<int>::reverse_iterator riter = iv.rbegin(); riter != iv.rend() ; riter++)
{
cout<<*riter << endl://300,200,100
}
- 迭代器运算符
//四:迭代器运算符
//a)*iter:返回迭代器iter所指向元素的引用。必须要保证这个迭代器指向的是有效的容器元素,:
//不能指向end(),因为end()是末端元素的后边。也就是end()指向的是一个不存在的元素。
//vector<int>::iterator iter = iv.end();
//vector<int>::iterator iter = iv.begin();
//cout << *iter << endl ;
//b)++titer;iter++;让迭代器指向容器中下一个元素;已经指向end()的时候你不能再++;
//vector<int>::iterator iter = iv.end();
vector<int>::iterator iter = iv.begin();
//iter++;
iter++;
cout << *iter << endl;//200
//c)++iter;iter++ 让迭代器指向下一个元素;已经指向end()的时候你不能再--
vector<int>::iterator iter = iv.end();
iter--;
cout<< *iter << endl;//300
//d)iter1 == iter2, iter1 != iter2 判断两个迭代器是否相等
//如果两个迭代器指向的是同一个元素,就相等,否则就不等
//e)
vector<student> sv;
student mystu;
mystu.num = 100;
sv.push_back(mystu);//把对象mystu赋值到了sv容器中;
vector<student>::iterator iter;
iter = sv.begin();//指向第一个元素
cout<< (*iter).num <<endl;
cout<< iter->num << endl;
- const_iterator迭代器
//五:const_iterator迭代器, const:常量
//const_iterator迭代器,表示值不能改变的意思,这里的值不能改变表示这个迭代器指向的元素值不能改变,:
//而不是表示这个迭代器本身不能改变,也就是说,迭代器本身是可以不断指向下一个元素;
//只能从容器中读元素,不能通过这个迭代器改写容器中的元素。感觉起来更象常量指针。
//iterator是能读能写;
vector<int> iv = ( 100, 200,300 };
vector<int>::const_iterator iter;//如果容器是常量的容器 例如 : const vector<int> iv = { 100, 200, 300},那么迭代器也得是const_iterator
for (iter = iv.begin(); iter != iv.end(); ++iter)
{
//*iter = 4;//报错。
cout<<*iter<< endl;//可以正常读
}
//(5.1)cbegin()和cend()操作
//c++11引入的两个新函数cbegin(),cend(),跟begin, end类似,cbegin,cend,返回的都是常量迭代器;
for(auto iter = iv.cbegin(); iter != iv.cend(); ++iter)
{
//*iter = 4;//报错,不能给常量赋值,这说明cbegin返回的是常量迭代器
cout<< *iter << endl;
}
- 迭代器失效
//六:迭代器失效
vector<int> vecvalue{ 1,2,3,4,5 };
// for (auto vecitem : vecvalue)
//{
// vecvalue.push_back(888);
// cout << vecitem <<endl;
//}
//for (auto beg = vecvalue.begin(), end = vecvalue.end(); beg != end; ++beg)
//{
// vecvalue.push_back(888);
// cout << *beg << endl;
//}
//在操作迭代器的过程中(使用了迭代器这种循环),千万不要改变vector容器的容量,也就是不要增加或者删除vector容器中的元素。
//往容器中增加或者从容器中删除元素,这些操作可能会使指向容器元素的指针、引用、迭代器失效。
//失效就表示不能再代表任何容器中的元素。一旦使用失效的东西,就等于犯了严重的程序错误,很多情况下,程序会直接崩溃。
//vector,list, map
//for(auto beg = vecvalue.begin(), end = vecvalue.end(); beg != end; ++beg)
//{
// vecvalue.push_back(888);
// break;
//}
//for(auto beg = vecvalue.begin(), end = vecvalue.end(); beg != end; ++beg)
//{
// cout<<*beg << endl;
//}
//如果要在迭代器中添加元素,一旦添加完成之后立即退出循环,重新开启新的迭代器,避免出错
//(6.1)灾难程序演示1
vector<int> vecvalue { 1,2,3,4,5 };
auto beg = vecvalue. begin() ;
auto end = vecvalue.end();
while (beg != end)
{
cout<<*beg < endl;
//假如我们想往begin这个位置插入新值,可以用insert
vecvalue.insert(beg,80);//插入新值,第一个参数为插入位置,第二个参数为插入的元素。
//咱们这一插入,肯定会导致迭代器失效。比如begin, end失效。
//具体的哪个迭代器失效,取决于这个容器vector内部的实现原理,我们可以查资料详细研究。
//现在我们不太明确哪个迭代器失效了,最明智的做法,就是我们只要一插入数据,插入完毕就break出循环体
break;//最明智的防止迭代器失效的方法,否则程序极有可能崩溃
++beg:;//不要忘记,并且要放在循环末尾
}
beg = vecvalue.begin();
end = vecvalue.end();
while(beg != end)
{
cout << *beg << endl;
++beg;//不要忘记,并且放在循环末尾
}
while (beg != vecvalue.end())//每次更新end防止end失效。
{
beg = vecvalue.insert(beg, icount + 80);//insert的返回结果我就要接着。
icount++;
if (count > 10)break;
++beg;
}
beg = vecvalue.begin();
auto end = vecvalue.end();
while (beg != end)
{
cout<<*beg << endl;
++beg;//不要忘记,并且要放在循环末尾
}
//(6.2)灾难程序演示2
vectbr<int> iv = { 100,200,300 };
//.....下面是错误的写法
//for (auto iter = iv.begin(): iter != iv.end(); ++iter)
//{
// iv.erase(iter); //erasel函数,移除iter位置上的元素,返回下一个元素位置。
//}//使用迭代器将容器内的元素释放的错误写法
vector<int>::iterator iter = iv.begin();
vector<int>::iterator iterend = iv.end();
//while(iter != iterend)//这种方法是错的,相当于固定了位置,但是容器的大小容量是变化的
while(iter != iv.end())//这种方式是对的,因为在循环中不断的改变了容器的大小了,这个iv.end()也在动态的变化
{
iter = iv.erase(iter);
}
//这是一种更简单的方式删除容器内的元素
while(!iv.empty())
{
auto iter = iv.begin();//因为不为空,所以返回begin()是没问题的
iv.erase(iter);//删除该位置上的元素
}
- 范例演示(用迭代器遍历一下string类型数据)(vector容器常用操作与内存释放)
//七:范例演示
//7.1)用迭代器遍历一下string类型数据/*string str("I Love China!"):
/*for (auto iter = str.begin(); iter != str.end(); ++iter)
{
*iter = toupper(*iter);
}
cout << str << endl;*/
struct conf{
char itemname[40];
char itemcontent[100];
};
char *getinfo(vector<conf *> &conflist,char *pitem)
{
for (auto pos = conflist.begin(); pos != conflist.end() ; ++pos)
{
if (_stricmp((*pos)->itemname,pitem)== 0)
{
return (*pos)->itemcontent;
}
}
return nullptr;
}
//(7.2) vector容器常用操作与内存释放//实践程序:
//ServerName =1区//表示服务器名称
//ServerID = 100000//表示服务器ID
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(gconf2->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;
}
//我们要释放内存,自己new的就要自己释放,否则会造成内存泄漏;
std::vector<conf *>::iterator pos;
for (pos = conflist.begin(); pos != conflist.end(); ++pos)
{
delete (*pos);//*pos才是那个指针。
}
conflist.clear(); //这个要不要都行工
2-10类型转换:static_cast等
- 隐式类型转换
//隐式类型转换:系统自动进行,不需要程序开发人员介入
int n = 3 + 45.6;
double m = 3 + 45.6;
- 显示类型转换
2.1. static_cast
2.2. dynamic_cast
2.3. const_cast
2.4. reinterpret_cast
//二、显示类型转换(强制类型转换)
//int k = 5 % 3.2;//语法错误
//int k = 5 % (int)3.2;//ok,c语言风格的强制类型转换
//int k = 5 % int(3.2);//函数分割的强制类型转换(c语言风格的强制类型转换)
//c++强制类型转换分为4种:我们现在写程序应该使用c++风格的强制类型转换。
//这4种强制类型转换,分别用于不同的目的,每一个都有一个不同的名字,提供4种类的目的:提供更丰富的含义和功能,更好的类型检查机制,方便代码的书写和维护。
//a)static_cast
//b)dynamic_cast
//c)cosnt_cast
//d)reinterpret_cast
//这四个强制类型转换都被称呼为“命名的强制类型转换(因为他们每一个都有一个名字并且名字各不相同);
//通用形式
//强制类型转换名<type>(express);
//强制类型转换名,是上边的a,b,c,d四个名字之一。
//type:转换的目标类型
//express:你要转换的值(你要转换的是谁);
//(2.1)static_cast:静态转换﹐大家就理解成“正常转换”,编译的时候就会进行类型转换的检查。
//代码中要保证转换的安全性和正确性,static_cast合义跟c语言中的强制类型转换这种感觉差不多。
//c风格的强制类型转换,以及编译器能够进行的隐式类型转换,都可以用statip_cast类显式完成。
//可用于:
//a)相关类型转换,比如整型和实型之间的转换:
//double f = 100.34f;
//int i = (int)f;//c风格的
//int i2 = static_cast<int>(f);//c++风格的类型转换
//b)后续学习类的时候子类转成父类类型(继承关系),也能用这个static_cast转。/
//class A{};
//class B : public A{};
//B b;
//A a = static_cast<A>(b); //把子类转成父类的对象
//A a;
//B b = static_cast<B>(a);//父类转子类就不行了
//c)void * 与其他类型指针之间的相互转换,void *:无类型指针:可以指向任何指针类型(万能指针);
//int i = 10;
//int *p = &i;
//void *q = static_cast<void *>(p);
//int *db = static_cast<int *>(q);
//不可用于:
//a)一般不能用于指针类型之间的转换比如int 科车专double *, float 转double *等等。
double f = 100.0f;
double*pf =&f;
//int *i = static_cast<int *>(pf);//不可以
//float *fd = static_cast<float *>(pf);//不可以
//2.2)dynamic_cast:主要应用于 运行时类型识别和检查。主要用来父类型和子类型之间转换用的(父类型指针指向子类型对象,然后用dynamic_cast把父类型指针往子类型转
//第三章,第十节里详细的讲解dynamic_cast;这里就不多说了
//2.3)const_cast: 去除指针或者引用的const属性,该转换能够将const性质转换掉
//编译时就会进行类型转换
//const int ai = 90;
//int ai2 = const_cast<int>(ai); //ai不是指针也不是引用不能转;
//const int *pai = &ai;
//int *pai2 = const_cast<int *>(pai);//语法正确
//*pai2 = 120; //这种写值行为 是属于一种未定义行为,不要这么干
//cout<< ai << endl;
//cout<< *pai << endl;
//(2.4)reinterpret_cast:编译的时候就会进行类型转换的检查
//reinterpret:重新解释。(将操作数内容解释为另一种不同的类型【能把操作数的类型都转了】);
//处理无关类型的转换。也就是两个转换类型之间没有什么关系。就等于可以乱转,自由转。怎么转都行,很随意。
//常用于如下两种转换:
//a)将一个整型(地址)转换成指针,一种类型指针转换成另一种类型指针。按照转换后的内容重新释放内存种的内容。
//b)也可以从一个指针类型转换成一个整型。
/*int i = 10;
int *pi = &i;
int *p2 = reinterpret_cast<int *>(&i);
char *pc = reinterpret_cast<char *>(pi);*/
//int i = 10;
//int *pi = &i;
//void *pvoid = reinterpret_cast<void *>(pi);
..
//int *pi2 = reinterpret_cast<int *>(pvoid);
//被认为是危险的类型转换;随便转,怎么搞都行,编译器都不报错。int iv1 = 100;
long long lv1 = 8898899400;//8字节的。 十六进制:2 126A 6DC8
int *piv1 =(int *)iv1;//c语言风格;0x00000064{???}
int *piv2 = reinterpret_cast<int *>(iv1);//0x00000064 {???}
piv2= reinterpret_cast<int *>(lv1);//0x 126a 6dc8 {???}把前边的2丢了,因为piv2是4字节。
long long ne = reinterpret_cast<long long>(piv2);//指针类型转整型 =308964808 = 126A 6DC8
- 总结
//总结
//(1)强制类型转换,不建议使用。强制类型转换能够抑制编译器报错。
//(2)学习的:认识这些类型转换符,方便大家阅读别人代码。
//(3)资料说: reinterpret_cast危险。使用const_cast意味着设计缺陷。
//(4)如果实在o要使用类型转换,不要再使用c语言风格的类型转换了,而用c++风格的类型转换。
//(5)一般static_cast和 reinterpret_cast 就能够很好的取代c语言风格的类型转换。
//reinterpret_cast只要好好用,合乎规则的用,不要乱用,其实reinterpret_cast很好用