1、命名空间
使用情况,一个项目中不同类定义了相同的函数(名称和形参完全一样,但是实现的目的不一样),可以使用命名空间加以区分和调用。
例如:
Person.h文件
#ifndef PERSON_H
#define PERSON_H
namespace A{
class Person{
private:
char* name;
int age;
public:
Person();
Person(char* name,int age);
~Person();
void printInfo(void);
friend void addAgeOne(Person *per);
friend void addAgeOne(Person &per);
};
void printHello(void);
}
#endif // PERSON_H
Person.cpp文件
#include "person.h"
#include "iostream"
using namespace std;
namespace A {
Person::Person()
{
cout << "Person()" <<endl;
}
Person::Person(char* name,int age){
cout << "Person(char* name,int age)" <<endl;
this->name = name;
this->age = age;
}
Person::~Person(){
cout << "~Person()" <<endl;
if(this->name){
delete this->name;
}
}
void Person::printInfo(void){
cout << "the name is " << name << endl;
cout << "the age is " << age << endl;
}
void addAgeOne(Person *per){
per->age++;
}
void addAgeOne(Person &per){
per.age++;
}
void printHello(void){
cout << "Hello" << endl;
}
}
-----------------------------------------------------------------------
Dog.h文件
#ifndef DOG_H
#define DOG_H
#include "iostream"
using namespace std;
namespace D {
class Dog
{
private:
char *name;
int age;
public:
Dog();
Dog(char *name,int age):name(name),age(age){
cout << "Dog(char *name,int age):name(name),age(age)" <<endl;
}
~Dog();
void printInfo(void);
friend void addAgeOne(Dog *per);
};
void printHello(void);
}
#endif // DOG_H
Dog.cpp文件:
#include "dog.h"
namespace D {
Dog::Dog()
{
cout << "Dog()" <<endl;
}
Dog::~Dog(){
cout << "~Dog()" <<endl;
}
void Dog::printInfo(void){
cout << "the name is " << name << endl;
cout << "the age is " << age << endl;
}
void addAgeOne(Dog *dog){
dog->age++;
}
void printHello(void){
cout << "hi" << endl;
}
}
-----------------------------------------------------------------------
main.cpp文件
#include <iostream>
#include <person.h>
#include "dog.h"
using namespace std;
using namespace A;
using namespace D;
int main()
{
Person *per1 = new Person("asan",40);
per1->printInfo();
addAgeOne(per1);
per1->printInfo();
Dog *dog1 = new Dog("wangcai",5);
dog1->printInfo();
addAgeOne(dog1);
dog1->printInfo();
printHello();
delete per1;
delete dog1;
return 0;
}
需要将main函数中的printHello()改成A::printHello()
上面一长段代码涉及到了构造函数、析构函数、友元函数等知识点
2、标准库类型string
标准库类型string表示可变长的字符序列,使用string实现需要包含string头文件(#include <string>)
#include <string>
using std:string
2.1 定义和初始化string对象
初始化string对象的方式:
string s1 | 默认初始化,s1是一个空串 |
string s2(s1) | s2是s1的副本 |
string s2 = s1 | 等价于s2(s1),s2是s1的副本 |
string s3("value") | s2是字面值"value",除了字面值最后的那个空字符串外 |
string s3 = value | 等价于s3("value"),s3是字面值"value"的副本 |
string s4(n,'c') | 把s4初始化为由连续n个字符c组成的串 |
直接初始化和拷贝初始化:
拷贝初始化:编译器把等号右边的值拷贝到新创建的对象中去,即使用等号初始化一个变量。与之相反,如果不使用等号就是直接初始化。
#include <string>
using namespace std
string s5 = "hiya"; //拷贝初始化
string s6("hiya"); //直接初始化
string s7(3,'c'); //直接初始化,s7的内容是ccc
对于多个值的初始化,使用拷贝初始化的话,需要显式地创建爱你一个临时对象用于拷贝,显然会浪费时间。
2.2 string对象的基本操作
string对象上的操作:
os << s | 将s写到输出流os当中,返回os |
is >> s | 从is中读取字符串赋给s,字符串以空白分割,返回is |
getline(is,s) | 从is中读取一行赋给s,返回is |
s.empty() | s为空返回true,否则返回false |
s.size() | 返回s中字符的个数 |
s[n] | 返回s中第n个字符的引用,位置n从0计起 |
s1+s2 | 返回s1和s2连接后的结果 |
s1 = s2 | 用s2的副本代替s1中原来的字符 |
s1 == s2 | 如果s1和s2中所含的字符完全一样,则它们相等:string对象的相等性判断对字母的大小写敏感 |
s1 != s2 | 等性判断对字母的大小写敏感 |
<,<=,>,>= | 利用字符在字典中的顺序进行比较,且对大小写敏感 |
读取未知数量的string对象
cin >> str:会自动忽略开头的空白(即空格符、换行符、制表符等),从第一个真正的空白开始读起,直到遇到下一处空白为止。比如键入" hello word",结果输出将是:"hello"。
//键入Ctrl+z回车即能结束输入
int main(){
string word;
while(cin >> word){
cout << word << endl;
}
}
使用getline读取一整行
函数原型:
头文件:#include <string>
原型 :istream getline(istream &is,string &str,char delim);
① : 表示一个输入流,如cin
② : 表示把输入流读入的字符串存放到str中
③ : 表示遇到这个字符停止读入,默认是'\n',也就是回车符
用以保留输入时候的空白符。getline函数的参数是一个输入流(cin)和一个string对象,函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后将所读的内容存入string对象之中(注意不存换行符)。getline只要一遇到换行符就能结束读取操作并返回结果。如果输入开始就是一个换行符,那么所得的结果是个空的string。
string str1;
while(getline(cin,str1)){
cout << str1 <<endl;
}
size()函数:
对于size函数和来说,使用非常简单。其返回的是一个string::size_typde类型的值,而不是int类型。size_type它是一种无符号的值,而且能足够大存放下任何string对象的大小。
在C++11标准之中,允许使用auto或者decltype来推断变量类型,故可以写成
auto len = line.size(); //len的类型是string::size_type
两字符串相比较:
- 如果两个string对象的长度不同,而且短的那个每个字符都与长的那个对应位置上的相同,呢个就说短的string对象小于长的string对像。
- 如果两个string对象在某些对应位置不一致,则比较结果是是这两个对象中第一对相异字符的比较结果。
- 'A'的ASCII是65,a是97。随着字母的增加,其ASCII的在+1增大,其中对应的大小写字母转换是大到小+32。
由于某些历史原因,为了与C相兼容,所以C++中的字符串字面值并不是标准库类型string的对象。切记,字符串字面值与string是不同的类型。
Q:请说明string类的输入运算符和getline函数分别是如何处理空白字符的?
Q:编写一段程序从标准输入中读入多个字符串并将它们连接在一起,输入连接成的大字符串。然后修改上述程序,用空格把输入的多个字符串分割开来。
2.3 处理string对象中的字符
在cctype头文件中定义了一组标注库函数处理string中的字符
isalnum(c) | 当c是字母或数字时为真 |
isalpha(c) | 当c为字母时候为真 |
iscntrl(c) | 当c是控制符(如回车、换行、删除)时为真 |
isdigit(c) | 当c是数字时为真 |
isgraph(c) | 当c不是空格但可打印时为真 |
islower(c) | 当c是小写字母时为真 |
isprint(c) | 当c是可打印字符时为真(即c是空格或c具有可视形式) |
ispunct(c) | 当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种) |
isspace(c) | 当c是空白时为真(即c时空格、横向制表符、纵向制表符、回车符、换行符、进制符中的一种) |
isupper(c) | 当c是大写字母时为真 |
isxdigit(c) | 当c是十六进制时为真 |
tolower(c) | 如果c是大写字母,输入对应的小写字母;否则原样输入c |
toupper(c) | 如果c是小写字母,输出对应的大写字母;否则原样输出c |
cctype头文件和ctype.h头文件的内容是一样的,只不过从命名规范更加符合C++语言的要求。特别的,在名为cname的头文件中定义的名字从属于命名空间std,而定义在名为.h的头文件中的则不然。
处理每个字符:使用基于范围的for语句
范围for语句:
for(declaration : expression)
statement
说明:
① expression是一个对象,用以表示一个序列
② declaration部分负责定义一个变量,用于访问序列中的基本元素,每次迭代,
declaration部分的变量会被初始化为expression部分的下一个元素值
#include <string>
using namespace std;
int main(){
string str1("buaa");
cout << str1 <<endl;
for(auto c : str1){
cout << c << endl;
}
}
如果要想改变string对象中字符的值,必须把循环变量定义成引用类型
for(auto &c : str1){
c = 'A';
}
只处理一部分字符串?
要想访问string对象中的单个字符串有两种方式,一个是使用下标,另外一个是使用迭代器。
下表运算符([ ])接受的参数是string::size_type类型的值,这个参数表示要访问的字符的位置,返回值是该位置上字符的引用。
string对象下标从0开始,s[s.size()-1]是最后一个字符。因此,为了下标的正确使用,总是设下标的类型是string::size_type,保证此类型是无符号数,确保下表不小于0。此时,只要保证下标小于size()就可以了。
C++标准并不要求标准可检测下标是否合法。一旦下标超过范围,就是引起不可预知的结果。
3、向量
标准库类型vector表示对象的一个集合,其中所有对象的类型都完全相同,集合中的每个对象都有一个与之对应的索引,索引用于访问对象。因为vector容纳着其他对象,所以它也被称为容器(container)。vector是一个类模板。
对于类模板来说,我们通过提供一些额外信息来指定模板到底实例化什么样的类,需要提供哪些信息由模板决定。提供信息的方式总是这样:即在模板名字后面跟一对尖括号,在括号内放上信息。
#include <vector>
using std::vector;
------------------
vector<int > ivec; //ivec保存int类型的对象
vector<Sales_item > Sales_vec; //保存Sales_item 类型的对象
vector<vector<string>> file; //该向量的元素是vector对象
vector是模版而不是类型,由vector生成的类型必须包含vector中元素的类型,例如vector<int>
如果vector元素还是vector,早期C++标准是:vector<vector<int >>,C++11之后的写法可以不带那个空格,即vector<vector<int>>
3.1 定义和初始化vector对象
初始化vector对象的方法:
vector<T> v1 | v1是一个空vector,它潜在的元素是T类型的,执行默认初始化 |
vector<T> v2(v1) | v2中包含有v1所有元素的副本 |
vector<T> v2 = v1 | 等价于v2(v1),v2中包含有v1所有元素的副本 |
vector<T> v3(n,val) | v3包含了n个重复的元素,每个元素的值都是val |
vector<T> v4(n) | v4包含了n个重复地执行初始化的对象 |
vector<T> v5{a,b,c,...} | v5包含了初始值个数的元素,每个元素被赋予相应的初始化值 |
vector<T> v5 = {a,b,c,...} | 等价于v5{a,b,c,...} |
3.2 vector基本操作
向vector中添加元素:
push_back:负责把一个值当成vector对象的尾元素“压到(push)”vector对象的“尾端(back)”,例如:
vector<int > v2;
string word;
vector<string > text;
for(int i = 0;i < 100;i++){
v2.push_back(i);
}
while(cin << word){
text.push_back(word);
}
C++标准要求vector应该能在运行时高校快速地添加元素。因此既然vector对象能高效地增长,那么定义vector对象的时候设定其大小也就没是没必要了,事实上如果这么做性能可能更差。只有一个情况例外,就是所有(all)元素的值都是一样的。
不能通过范围for语句增加vector独享(或者其他容器)的元素:在范围for语句中,预存了end()的值。一旦序列中添加(删除)元素,end函数的值就可能变得无效了。
范围for应该不改变其所遍历序列的大小
v.empty() | 如果v不含任何元素,返回真;否则返回假 |
v.size() | 返回v中元素的个数 |
v.push_back(t) | 向v的尾端添加一个值为t的元素 |
v[n] | 返回v中第n个位置上的元素(从0开始) |
v1 = v2 | 用v2中元素的拷贝替换v1中的元素 |
v1 = {a,b,c,..} | 用列表元素的开呗替换v1中的元素 |
v1 == v2 | v1和v2相等当且仅当他们的元素数量相同且对应位置的元素值都相同 |
v1 != v2 | 顾名思义,以字典顺序比较 |
<,<=,>,>= |
使用size_type,需要现制定它是由哪种类型定义的。vector对象的类型总是包含着元素的类型
vector<int >::size_type //正确 vector:size_type //错误
不能使用下标形式添加元素
vector<int > ivec; //空vector对象
for(decltype(ivec.size()) ix = 0;ix != 10;++ix)
ivec[ix] = ix; //严重错误:ivec不包含任何元素
这段代码是错误的:ivec不包含任何元素,当然就不能使用下标去访问任何元素。确保下标合法的一种有效途径就是使用范围for语句
4、迭代器
迭代器(iterator):作用类似于下标运算符。
所有标准库容器都可以使用迭代器,但是只有其中几种才同时支持下标运算符。
严格来说,string对象不属于容器类型,但是string支持很多与容器类型相似的操作。
有效迭代器或者指向某个元素,或者指向容器中尾元素的下一位置;其他情况都属于无效。
4.1 使用迭代器
begin():返回指向第一个元素(或者字符)的迭代器。
end():尾后迭代器(off-the-end iterator)简称尾迭代器,负责返回容器(或者string对象)尾元素的下一位置(ine past the end)
迭代器运算符:
*iter | 返回迭代器iter所指元素的引用 |
iter->mem | 解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem |
++iter | 令iter指示容器中的下一个元素 |
--iter | 令iter指示容器中的上一个元素 |
iter1 == iter2 | 判断两个迭代器是否相等,如果两个迭代器指示的是同一个元素或者他们是同一个容器的为后迭代器,则相等;繁殖,不相等 |
iter1 != iter2 |
因为end返回的迭代器并不实际只是某个元素,所以不能对其进行递增或者解引用操作
将string对象中的第一个单次改写成为大写:
#include <iostream>
#include <string>
using namespace std;
int main()
{
getline(cin ,word);
cout << word << endl;
for(auto i = word.begin();i != word.end() && !isspace(*i);i++)
*i = toupper(*i);
cout << word << endl;
return 0;
}
迭代器的类型:
C++11标准中引入两个新函数,分别是cbegin()和cend(),作用和begin()和end()一样,所不同的是不论vector对象(或者string对象)本身是否是常量,返回值都是const_iterator。
对于一个由字符串组成的vector对象,it->empty等价于(*it).empty,判断元素对象是否为空,但是*it.empty是错误的。
一旦使用了迭代器的循环体,都不要向迭代器所属的容器添加元素
4.2 迭代器运算
iter + n | 迭代器加上一个整数值仍得一个迭代器,迭代器的位置相比原来向前一栋楼若干个元素 |
iter - n | |
iter1 += n | |
iter1 -= n | |
iter1 - iter2 | 两个迭代器相减的结果是他们之间的距离。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置 |
>,>=,<,<= | 如果某一迭代器指向容器的位置在另一迭代器所指位置之前,则说前者小于后者。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置 |
两个迭代器相减:相减结果是两个迭代器的距离。所谓距离是指右侧的迭代器向前移动多少位置能够追上左侧迭代器,其类型是difference_type的带符号整型数。
C++没有定义两个迭代器的加法运算,实际上直接把两个迭代器加起来是没有意义的
使用迭代器于是男的一个经典算法是二分搜索。二分搜索是从一个有序序列中寻找某个给定的值。二分搜索从序列中间的位置开始搜索,如果该元素为目标值,则返回;如果该元素小于目标值,则在后半序列继续搜索;如果该元素大于目标值,则在前半序列继续搜索。如此循环。
5. 数组
数组和vector的异同:
vector | 数组 | |
相同点 | ① 都是对同一类型的数据进行存储 ② 都可以用下表操作进行处理 ③ 都可以用迭代器进行操作(在C++中每个容器都配有各自的迭代器) | |
不同点 | 可以用size()获取vector长度 | 不可以获取,在定义时候已经确定了长度 |
长度不固定,可以随时增加,可在莫为增加vector元素(push)back | 长度固定,定义后不可更改 不能增加,在长度意外地长度 | |
可以确定长度,节约空间 | 不能确定长度,必须在定时定义一个很大的空间留给数组,造成内存浪费 |
数组下标:size_t类型
5.1 C风格的字符串
尽管C++支持c风格的字符串,但在c++程序中最好还是不要使用他们。一个使用起来不方便,而是极易引起程序漏洞,是诸多安全问题的根本原因。
strlen(p) | 返回p的长度,空字符不计算在内 |
strcmp(p1,p2) | 比较p1和p2的相等性。如果p1 == p2,返回0;如果p1>p2,返回一个正值;如果p1 < p2,返回一个负值 |
strcat(p1,p2) | 将p2附加到p1之后,返回p1 |
strcpy(p1,p2) | 将p2拷贝给p1,返回p1 |