c++ primer笔记
第二章 变量和基本类型
在32位机器中long类型和int类型字长相同
整数值的表示:8位的signed整型 取值从-128到127,对Unsigned来说如果赋值越界 编译器就把该值和256取摸运算,对unsigned赋值越界是合法的 signed越界赋值 根据编译器决定,一般来说和unsigned一样
字面值常量:无符号长整形 1024UL 长整形1024L
字符字面值:可打印字符用单引号 'a','2'在前面 L'a'表示宽字符wchar_t
宽字符串字面值:L "he excels in muliple-choice questions" 但是结尾有个空宽字符
字符串字面值的连接:两个相邻的仅由空格,制表符,换行符分开的字符串字面值(或宽字符串字面值),可连接成新的.注意:宽字符串和字符串字面值不能连接!
浮点型:float(32位,6位有效数字) double(64位,10位有效数字) long double(32位机器中占,64位)
初始化:对于C++来说,初始化有两种形式一种是复制初始化(用=),直接初始化(用())
初始化和赋值不是一回事,复制的类才会凸显这两者之间的差别.注意:直接初始化语法更灵活更高效
String初始化 std::string all_nines(10,'9');//all_nines="9999999999"
建议:每个内置类型的对象都要初始化,会增加容易和安全。
定义和声明:定义分配内存空间,不能重复定义。声明不分配内存,能重复声明例如:extren int a; 但是extren int a= 1; 有定义的功效所以失去了声明的作用,而且必须在函数体外。 不能用在C++多文件访问一个声明了,其他文件访问本文件的变量需要声明。
定义变量最好在第一次使用它的地方,并且方便初始化。
for的执行过程:
for(表达式 1;表达式 2;表达式 3)语句
它的执行过程如下:
(1)先求表达式 1.
(2)求表达式2,若其值为真(值为非0),则执行for语句中指定的内嵌语句,然后执行下面第三步 做若为
假(值为0),则结束循环,转到第5步.
(3)求解表达式3
(4)转回上面第(2)步骤继续执行;
(5)结束循环,执行for语句下面的一个语句;
Const:
不能被其他文件访问,除非用到extren const int = 11; 初始化之后 用此文件extren const int;
引用:
Int hehe =1;
Int &haha= hehe; 初始化可以用变量初始化而且必须同类型的。
Const int &hehe = 22; const可以初始化而且可以不同类型的。
例如:double shit = 3.14; const int &haha = shit; 编译器会将它转换成类似下面:int emp = shit; const int &haha = emp; 其haha或shit值是3
头文件:
头文件用于声明而不是定义。头文件卫士(header guard)它用于已经见到的头文件的情况下重新处理该头文件的内容。预处理变量:#define WFWFW WFWFW必须在程序中是唯一的,一般用大写字母避免重复。
#ifndef WFWFW #define WFWFW #endif
可以在头文件里的实体(如类)来命名预处理变量来避免预处理器变量重名问题,一个程序只能有一个Sales_item的类,通过类名组成预处理变量
第三章 标准库类型:string,vector,bitset
String对象的定义和初始化
String si;//默认构造函数,S1为空串
String s2(s1);//将S2初始化为S1的副本
String s3(“value”);//将s3初始化为一个字符串字面值副本
String s4(n,’c’); //将S4初始化为字符’c’的n个副本
注意:字符串字面值和标准库string类型不是同一种类型
string 类型的输入操作符对空白字符的处理:读取并忽略有效字符(非空白字
符)之前所有的空白字符,然后读取字符直至再次遇到空白字符,读取终止(该
空白字符仍留在输入流中)。
getline 函数对空白字符的处理:不忽略行开头的空白字符,读取字符直至遇到
换行符,读取终止并丢弃换行符(换行符从输入流中去掉但并不存储在string
对象中)。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string word;
// 一次读入一个单词,直至遇见文件结束符
while (cin >> word)
cout << word << endl; // 输出相应单词以进行验证
return 0;
}
String对象的操作 P75页
s.Empty() s是否为空串
s.Size() 返回S中字符的个数(s.Size是string::size_type类型 是各个类库的配套类型,定义为unsinged int或者unsinged long注意:不要size赋值给int )
S[n] 返回位置为n的字符 从0开始计数
String对象的关系操作符
- 区分大小写
2.关系操作符比较两个string对象时采用了 :如果两个string对象长度不同,且短的string和长的string前面部分相匹配,则短的string对象长度小于长的,如果两个string对象字符不同则比较第一个不匹配的字符.
举例:string substr = "Hello";
String phrase = "Hello World";
String slang = "Hiya";
则substr小于phrase,而slang则大于substr或phrase
String对象的连接
非法:string s3 = "hello" + ",";//字面值左右至少一个string对象类型
不可思议的连接: string s5 = s2+"hello"+","; 它是合法的 s2先和"hello"连接。
这样却非法string s6 = "hello" + ","+s2;
下标操作可用作左值
例子:for(string::size_type ix=0;ix!=str.Size();++ix)
Str[ix]='x';
计算下标值:
Str[someotherval*someval]=someval;
虽然任何整形数值都可以作为索引,但索引的实际数据类型却是unsigned类型string::size_type
String对象中的字符的处理:
在cctype头文件中定义了一些函数
Isalnum(c) 如果c是字母或数字,则为true
Isalpha(c) 如果c是字母,则为true
Iscntrl(c) 如果c是控制字符,则为true
Isdigit(c) 如果c是数字,则为true
Isgraph(c) 如果c不是空格,但可以打印,则为true
Islower(c) 如果c 是小写字母,则为true
Isprint(c) 如果c是可打印的字符,则为true
Ispunct(c) 如果c是标点符号,则为true
Isspace(c) 如果c是空白字符 ,则为true
Isupper(c) 如果c是大写字母,则为true
Isxdigit(c) 如果c是十六进制数,则为true
Tolower(c) 如果c是大写字母,则返回其小写字母形式,否则直接返回c
Toupper(c) 如果c是小写字母,则返回其大写字母形式,否则直接返回c
去标点符号
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main()
{
string s1,s_result;
char ch;
cin>>s1;
for (string::size_type index=0;index<s1.size();index++)//逐个字取出
{
if (ispunct(s1[index])==false)//判断是否是标点符号
{
s_result+=s1[index];
}
}
cout<<s_result<<endl;
return 0;
}
标准库vector
Vector是同一种类型的对象的集合
Vector 必须指定哪种类型用尖括号,它不是一种数据类型,他是个类模板
Vector<int> ivec;
Vector<Sales_item> sales_vec;
Vector不是一种数据类型 而只是一个类模板可用来定义任意多种数据类型
Vecotr类型的每一种都指定了其保存元素的类型。因此vector<int>和vector<string>都是数据类型
Vecotr的初始化
Vecotr<T> v1;vector保存类型为T的对象,默认构造函数
Vecotr<T> v2(v1);v2是v1的一个副本
Vecotr<T> v3(n,i);v3包含n个值为i的元素
Vecotr<T> v4(n);v4含有值初始化的元素的n个副本
如果Vecotr中的对象是类类型,并且是自定义类,没有默认构造函数,不能只提供元素个数,还需要初始值
还有种可能元素类型可能是没有构造函数的类类型,这种情况标准库仍然产生一个带初始值的对象
关键概念:vecotr对象动态增长
Vector增长的效率高 在元素值已知的情况下,最好是动态地添加元素
虽然可以对给定元素个数的vector对象预先分配内存 但更有效的方法是先初始化一个空vector对象 然后在动态地增加元素
值初始化:vector<int> fvec(10);//10个INT类型的元素 值为0
Vector对象操作
类似string对象操作
Vector<int> v;
v.empty();//如果为空,则返回true,否则返回false
v.size();//返回V中的元素个数
v.push_back(t);//在V中末尾增加一个值为t的元素
v(n);//返回v中位置为n的元素
v1 = v2;//把v1的元素转换为v2中元素的副本
v1 == v2;//如果v1与v2相等 则返回true
!=,<,<=,>,>=;//保持这些操作符惯有的含义;
使用size_type类型时,必须指数该类型是在哪里定义,vector类型总是包括vector的元素类型
Vector<int>::size_type//ok
Vector::size_type//error
Vector的下标操作
类似于string的下标操作
For(vector<int>::size_type ix=0;ix!=ivec.size();ix++)
Ivec[ix]=0;
关键概念
编写循环条件c++程序员习惯用!=而不用<,这是泛型编程
Size值最好在循环体里面 保证有些动态对象增加元素而出现错误,因为没有将新加入的元素计算在内
Vector不能用下标添加元素 要用push_back(t); 但是可以重置数据
视图获取不存在的元素必然产生运行时错误,传说中的“缓冲区溢出”就打这里来的,小心你的PC中招
练习
读入到vector一组整数,计算并输出每对相邻元素的和,如果读入元素为奇数,提示用户最后一个元素没有求和,并输出其值.
#include "stdafx.h"
#include <iostream>
#include <vector>
#include <string>
#include <cctype>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
int a;
vector<int> hands;
int result(0);
cout<<"请输入一组整数"<<endl;
while(cin>>a)
hands.push_back(a);//输入一堆你想输入的整数
//cin>>s;
for(vector<int>::size_type x=0;x!=hands.size()/2;x++)//想半天原来这里和下面是倍关系
{
result=hands[2*x]+hands[2*x+1];//没怎么想 一下就写出来了真是天才
cout<<result<<endl;
}
if(hands.size()%2==1)//求于,判断是否为奇数
{
cout<<"输入个数为奇数最后一个数没求和"<<endl;
cout<<"最后一个数为"<<hands[hands.size()-1]<<endl;
}
return 0;
}
修改程序:头尾元素两两配对(第一个和最后一个,第二个和倒数第二个以此类推)计算每对元素的和 并输出
只需要修改以上两行代码即可搞定
result=hands[2*x]+hands[2*x+1] 改成 result=hands[x]+hands[hands.size()-x-1];
cout<<"最后一个数为"<<hands[hands.size()-1]<<endl;改成cout<<"最后一个数为"<<hands[hands.size()/2]<<endl;
3.4迭代器简介
迭代器是一种检查容器内元素并遍历元素的数据类型。
现在最好用迭代器访问容器的元素,每种容器都定义了自己的迭代器类型 如 vector
Vector<int>::iterator iter;
每种容器都定义了begin和end,用于返回迭代器,如果容器内有元素那么begin返回的迭代器指向第一个元素。
End操作返回的迭代器指向“末端元素的下一个”
迭代器相当于容器元素的指针 有++iter操作 有取首个元素内容操作*iter
int _tmain(int argc, _TCHAR* argv[])
{
vector<int> hands(10,1);
for (vector<int>::iterator p=hands.begin();p!=hands.end();++p)
{
*p=10;
cout<<*p<<endl;
}
return 0;
Const_iterator 相当于指针中的指针常量,就是不能改变所指向元素的值
Const 的iterator 相当于指针总的指针常量,不能改变指针所指的位置
练习:编写程序来创建有10个元素的vector对象,用迭代器把每个元素值改为当前值的2倍
int _tmain(int argc, _TCHAR* argv[])
{
vector<int> hands(10,1);
for (vector<int>::iterator p=hands.begin();p!=hands.end();++p)
{
*p=*p*2;
cout<<*p<<endl;
}
return 0;
}
迭代器的算术操作
Iter+n
Iter-n
加上或减去的值应该是vector的size_type或difference_type
Iter1-iter2 用来计算两个迭代器的距离,该距离名为difference_type的signed类型的值
他们两者必须都指向同一vector中的元素,或者指向vector末端之后的下一个元素
下面直接定位于vector的中间元素:
Vector<int>::iteratoc mid = vi.begin()+vi.size()/2
注意:任何改变vector长度的操作都会使已存在的迭代器失效。例如在调用push_back后,就不能相信迭代器的值了
练习:如果采用下面的方法计算mid会产生什么结果?
Vector<int>::iterator mid = (vi.begin()+vi.end())/2;
这样是未定义的,所以编译出错
Bitset标准库
在定义bitset时,要明确bitset含有多少位,须在尖括号内给出它的长度值:
Bitset<32> bitvec;//32bits all zero
初始化bitset对象的方法
Bitset<n> b; //b有n位,每位都为0
Bitset<n> b(u);//b是unsigned long 型u的一个副本
Bitset<n> b(s); //b是string对象s中含有的位串的副本
Bitset<n> b(s,pos,n);//b是s中从位置pos开始的n个位的副本
用string对象初始化bitset对象
String strval("1100");
Bitset<32> bitvec4(strval);
String对象和Bitset对象之间是反向转化的:string对象的最右边字符用来初始化bitset对象的低阶位
不一定要把整个string对象都做为bitset对象的初始值。相反,可以只用某个串作为初始值:
String str("1111111000000011001101");
Bitset<32> bitvec5(str,5,4);
Bitset<32> bitvec6(str,str.Size()-4);
Bitset对象上的操作
B.any() b中是否存在置为1的二进制位?
B.none() b中不存在置为1的二进制位吗?
B.size() b中二进制位的个数
B[pos] 访问b中的pos处的二进制位
B.test(pos) b中在pos处的二进制位是否为1?
B.set() 把b中所有二进制位都置为1
B.set(pos) 把b中在pos处的二进制置为1
B.reset() 把b中所有二进制位都置为0
B.reset(pos) 把b中在pos处的二进制位置为0
B.flip() 把b中所有二进制位逐位取反
B.flip(pos) 吧b中在pos处的二进制取反
B.to_ulong() 把b中同样的二进制位返回一个unsigned long值
Os<<b 把b中的位集输出到os流
Count操作的返回类型是标准库中命名为size_t的类型,它定义在cstddef头文件中
Size_t bits_set = bitvec.count();
第四章数组和指针
p108 指针和数组省略以后加上
指针直接的距离是ptrdiff_t
C风格字符串
C++程序不应该,常常带来很多错误,是完全问题的根源
字符串字面值就是c风格字符串
C风格字符串是是以空字符null结束的字符数组
标准库函数 cstring是string.h头文件的c++版本 string.h是c语言提供的的标准库
Strlen(S) 返回s的长度,不包括字符结束符
Strcmp(s1,s2)比较两个字符串s1和s2是否相同,大于返回正数,小于返回负数,等于返回0
Strcat(s1,s2) 将字符串s2连接到s1后 ,并返回s1它必须足够大
Strcpy(s1,s2)将s2复制给s1,并返回s1它必须足够大
Strncat(s1,s2,n)将s2的前n个字符连接到s1后面,并返回s1
Strncpy(s1,s2,n)将s2的前n个字符复制给s1,并返回s1
使用strncat和strcpy一定记住算上结束符null
尽可能使用标准库string 这样不会担心每次修改字符串时涉及的大小问题
创建动态数组:数组长度虽然是固定的,它不必再编译时知道其长度,可在运行时才确定数组长度。与数组变量不同,动态分配的数组将一直存在,直到程序显式释放他为止。
动态数组的定义:只需指定类型和数组长度,不必为数组对象命名,new表达式返回指向新分配数组的第一个元素指针
Int *pia = new int[10];
动态数组的初始化:如果对象类型是类类型,那么调用默认的构造函数进行初始化。如果是内置类型对象,无初始化。
String *psa = new string[10]; //初始化空串
Int *pia = new int[10];//无初始化
Int *pia2 = new int[10](); //进行初始化都为0
注解:对于动态分配的数组,其元素只能初始化为元素类型的默认值,而不能像数组变量一样,用初始化列表为数组提供不同的的初值。
Const对象的动态数组:const动态数组必须有初始化,因为数组元素都是const对象,无法赋值。唯一方法就是初始化
Const int *pci_ok=new const int[10](); 类类型的也可以 但是必须有默认构造函数
动态数组可以为空数组:c++虽然不允许定义长度为0的数组变量,但明确指出,调用new动态创建长度为0的数组时合法的
动态空间的释放:动态分配的内存最后必须进行释放,否则内存将会逐渐耗尽
Delete [] pia;
习题:编写程序由从标准输入设备读入的元素数据建立一个int型vector对象,然后动态创建一个与该vector对象大小一致的数组,把vector对象的所有元素复制给新数组
#include "stdafx.h"
#include <iostream>
#include <cstring>
#include <vector>
#include <iterator>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
vector<int> hands;
int a;
while(cin>>a)
hands.push_back(a);
int *p = new int[hands.size()];
int *q=p;//间接操作数据
for (vector<int>::iterator b=hands.begin();b!=hands.end();++b,++q)
{
*q=*b;
cout<<*q;
}
delete [] p;//这里有个小插曲,起初我直接操作p,后来运行到这里出现中断,释放堆数组出错。所以访问动态数组里数据的时候一定要间接访问,这样就不破坏堆的位置,从而不会delete的时候出现错误
return 0;
}
混合使用标准库类string和c风格字符串
可以使用 c风格字符串对string赋值 反过来不成立 char *str =st2;//错误,但是string提供个成员函数c_str
Char *str =st2.c_str();//初始化失败,因为返回的指针是指向const char类型的数组。
正确的应该为 const char *str=st2.c_str()
C_str返回的数组并不找证一定有效,接下来对st2的操作可能会改变st2的值,使刚才返回的数组失效。如果程序需要持续访问该数据,则应该复制c_str函数返回的数组
使用数组初始化vector对象
看起来数组初始化有点陌生,使用数组初始化vector对象,必须指出用于初始化的第一个元素以及数组最后一个元素的下一位位置的地址
Const size_t arr_size=6;
Int int_arr[arr_size]={0,1,2,3,4,5};
Vector<int> ivec(int_arr,int_arr+arr_size);
第五章 表达式(省去部分)
++i和i++的区别:
简单的来说,++i 和 i++,在单独使用时,就是 i=i+1。
而 a = ++i,相当于 i=i+1; a = i;
而 a = i++,相当于 a = i; i=i+1;
Iter++->empty();//先访问emptry(),在自加
小练习:编写程序定义一个Vector对象,其每个元素都是指向string类型的指针,读取该vector,输出每个string对象的内容和长度
这个题起初觉得还蛮简单,但是仔细一看,不是想象中那样滴。不是很容易,研究了半天。
#include "stdafx.h"
#include <iostream>
#include <string> //忘了CString是MFC的了,前面章节说到这个我就用了,结果编译不通过
#include <vector>
#include <iterator>
#include <cstddef>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
vector<string*> hands;//之前不知道这么定义指针类型的vector对象
string strr;
while(cin>>strr)
{
string *haha = new string;//这里是动态分配string对象 不是数组
*haha=strr;
hands.push_back(haha);
}
vector<string*>::iterator ff=hands.begin();
while(ff!=hands.end()){
cout<<**ff<<(**ff).size()<<endl;//ff指针内容的内容
++ff;
}
ff=hands.begin();
while(ff!=hands.end()){ //释放各个动态分配的stirng对象
delete *ff;
++ff;
}
return 0;
}
New和Delete表达式
4.3.1介绍了New和Delete动态创建数组,这两种表达式也可以用于创建单个对象
例如:int i; int *pi= new int;
初始化:Int i(1024); int *pi= new int(1024);string str(10,'9') string *ps=new string(10,'9')
提供默认构造函数的类类型可以不初始化,但是对于没有定义默认构造函数的类类型和内置类型采用不同种方式初始化有显著的差别
Int *pi=new int;//没有初始化
Int *pi=new int();//初始化为0
Const对象的动态分配和回收
Const int *pci= new const int(1024);
对于类类型的const动态对象,如果该类型提供了默认的构造函数,则此对象可隐式初始化:
Cont string *pcs=new const string;
注意:内置类型对象或未提供默认构造函数的类类型对象必须显示初始化
动态内存管理容易出错:delete失败之后,不能释放堆中的内存,导致“内存泄露”,很难被发现,程序运行一段时间后等内存不够时才会显露出来,delete两次对指向同一个动态创建的对象指针,删除时会发生错误
类型转换
考虑例子:int ival=0;
Ival=3.541+3; //这属于隐式转换,如果表达式的操作数分别为整形和浮点型,则转化成浮点型,然后在转换成左操作数的类型
当发生隐式转换的时候:
Int ival; double dval; ival>=dval;//这时候转换为相同类型 都转变成double。
用作条件表达式被转换为Bool型的
用一表达式初始化某个变量,或者将一表达式赋值给某个变量,则该表达式转换为该变量的类型
Int ival=3.14;//转换为int类型。
Int *ip;
Ip=0;// 0转换为int指针类型
算术转换:算术转换保证在执行操作之前,将二元操作符(如算术或逻辑操作符)两个操作数转换为统一类型,并使表达式的值具有相同的类型,算术转换规定类型转换层次,规定应该按什么次序转换为表达式中最宽的类型。多种类型表达式中,要确保计算值精度。如果一个操作数得类型是long double,则无论另一个操作数是什么类型,都将被转换为long double。
对于所有比int小的类型包括char,signed char,unsigned char、short和unsiged short。如果该类型所有可能的值都能包容在int内,它们就被提升为int,否则将提升为unsigned int。
3.14159L+'a' ;//'a'是char类型,包含于Int,所以先提升为int ,然后在提升为long double,最后和3.14159相加。指向任意类型的指针都可转换为void *;整形数值常量0可转换为任意类型。
强制类型转换(cast)
操作符为staic_cast、dynamic_cast、const_cast和reinterpret_cast
Double dval;
Int ival;
Ival*=dval;//ival=ival*dval
如果这样放着不管的话,ival将转换为double类型,然后进行乘法,结果转换为int
如果用强制类型转换就可避免上面重复的转换来转换去的
Ival*=staic_cast<int>(dval);
dynamic_cast:支持运行时识别指针或引用所指向的对象。在18.2详细讨论。
const_cast:顾名思义,将转换掉表达式的const性质
staic_cast:比较常用,编译器隐式执行的任何类型转换都可以用它来完成,当较大的算术类型赋值较小的算术类型时,用强制类型转换时明智的选择
Double d=97.0; char ch = static_cast<char>(d);
reinterpret_cast: 通常为操作数的位模式提供较低层的重新解释,他本质非常依赖机器,必须完全理涉及到的数据类型,以及编译器强制类型转换的细节。reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的(这句话是C++编程思想中的原话)。reinterpret_cast 只能在指针之间转换。
例如: int *p;
Char *pc = reinterpret_cast<char*>(ip);
程序员必须记得PC所指向的对象其实是int型 ,并非字符数组。
如果像这样 string str(pc); 强制类型转换类型正确的话编译器不会警告,会导致运行时错误。
第六章 语句
这章简单的看完了,只是没有做笔录,主要内容是一些常用语句比如if,switch,default标号,while,for,
Do while,break,continue,goto
异常语句:try catch throw,使预处理器进行调试
- 函数
求两个数的最大公约数函数
int gcd(int v1,int v2)
{
while (v2)//
{
int temp = v2;
v2=v1%v2;
v1=temp;
}
return v1;
}
比如v1=6,v2=9; 那么 这个算法经过思考为下面的现象(小学没学好真遗憾)
6%9=6
9%6=3
6%3=0
经过查询资料这原来是经典的辗转相除法
1. a ÷ b,令r为所得余数(0≤r<b)
若 r = 0,算法结束;b 即为答案。
2. 互换:置 a←b,b←r,并返回第一步。
非引用形参
非引用形参通过复制实参实现初始化,函数实际没调用,实参本身,修改的只是副本
指针形参
与非引用类型一样,也是复制实参实现初始化,不同的是非const形参,如果函数体内改变指针指向的内容,那么,实参也跟真改变。一句话指针值不变,指针所指的内容可以变
保护指针指向的值,就用const类型的形参
const形参
如果使用非引用非const形参,既可以传递const实参也可以传递非const实参
由于实参是以副本形式传递 ,所以无所谓实参是非const还是const
事实上尽管函数形参是const,但编译实际将函数的形参视为非const类型,为了跟c兼容
复制实参的局限性
当需要在函数中修改实参值的时候
当需要以大型对象作为实参传递时,对实际的应用而言,因为复制时消耗的代价太大
当没有办法实现对象的复制时
有效地解决以上问题的办法就是将形参定义为指针或引用类型
引用形参
引用形参不会复制实参,直接代表实参,作为实参的另一个名字
void longlong(int &v1, int &v2)//直接修改其实参
{
int temp;
temp=v1;
v1=v2;
v2=temp;
}
传递指向指针的引用
Void ptrswap(int *&v1,int*&v2)
{
Int *tmp=v2;
V2=v1;
V1=tmp;
}只是实参指针的传递
Vector和其他容器类型的形参
让他们作为非引用形参,貌似有些不妥,应该避免复制vector,将之声明为引用类型
C++程序员更倾向于用迭代器来声明参数
Void print(vector<int>::const_iterator beg,vector<int>::const_iterator end)
{ while(beg!=end){
Cout<<*beg++;
If(beg!=end)cout<<" ";
}
Cout<<endl;
} 这个函数将输出从beg指向的元素开始到end指向的元素(不含)为止的范围内所有元素。
除了最后一个元素外,每个元素后面都输出一个空格。
数组形参
数组有两个特点1是不能复制2是使用名字时是指向数组第一个元素的指针
数组的定义:输出int型数组的内容,可用三种指定数组形参
Void printValules(int*) void printValues(int[]) void printValues(int[10])
这三种方法是等价的,虽然不能直接传递数组,但可以形参可以写成数组形式
非引用的数组形参只是复制数组的指针而非本身
通过引用传递数组
如果形参是数组的引用,编译器不会将数组实参转化为指针,而是引用本身
VOdi printValues(int (&arr)[10])
16.1.5节重新编写此函数,允许传递指向任意大小数组的形参
多维数组的传递
Void printValues(int(matrix*)[10]),int rowsize)
上面的语句将matrix声明为指向含有10个int型元素的数组的指针
注意:int *matrix[10] //10个指针的数组
Int (*matrix)[10] // 指向含有10个INT型元素的数组的指针
还可以定义多维数组,编译器会忽略第一维的长度,不要把它包括在形参表内
Void printValues(int matrix[][10],int rowSize)
数组参数的安全问题:
有三种技巧可以避免超出数组实参的边界第一种是放置结束标记就像C风格字符串似的
第二种是像标准库的迭代器用指针来操作,第一个元素和最有元素的下一个。
Void printValues(const int *beg,const int *end)
{ while (beg!=end)
{cout<<*beg++<<endl;}
}
Int mian()
{
Int j[2] = {0,1};
printValues(j,j+2);
Retrun 0;
}
第三种是显示传递数组大小的形参
Void printValue(const int ia[],size_t size)
{ for(size_t i=0;i!=size;++i)
{cout<<ia[i]<<endl;}
}
Int mian()
{
Int j[]={0,1}
printValues(j,sizeof(j)/sizeof(*j));
Return 0;
}
练习编写程序计算数组元素,要求编写函数二次,每次以不同方式处理数组界的题
#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
#include <iostream>
using namespace std;
void PrintValues1(const int *begin,const int *end)
{
int jieguo=0;
for(;begin!=end;++begin)
{
jieguo=jieguo+*begin;
}
cout<<jieguo<<endl;
return 0;
}
void PrintValues2(const int ia[],size_t size)
{
int jieguo=0;
for(size_t i=0;i!=size;++i)
{
jieguo=jieguo+ia[i];
}
cout<<jieguo<<endl;
return 0
}
int main()
{ int haha[]={3,5,7,9,2,7};
PrintValues1(haha,haha+sizeof(haha)/sizeof(*haha));
PrintValues2(haha,sizeof(haha)/sizeof(*haha));
return 0;
}
省略符形参
当无法列举出传递给函数的所有实参的类型和数目时,省略符形参暂停了类型检查机制,可以为0个或多个实参
Void foo(parm_list...)
Void foo(...)
主函数main的返回值
#include<cstdlib> 头文件里预处理了成功和失败
Int main()
{
If(some_failure)
Return EXIT_FAILURE;
Else
Return EXIT_SUCCESS;
}
返回非引用类型
函数返回值初始化临时对象与实参初始化形参的方法一样。如果返回不是引用,在调用函数的地方会将函数返回值复制给临时对象。
返回引用
返回引用类型时,没有复制返回值。相反,返回的是对象本身
千万不要返回局部对象引用
比如:
Const string &manip(const string& s)
{string ret =s;
Return ret;
}
引用返回左值
返回引用的函数返回一个左值
Char &get_val(sring &str,string::size_type ix)
{
Return str[ix];
}
Int main()
{
Get_val(s,0)=’A’;//改变s[0]到A
}
递归
典型的例子就是递归运算 阶乘
Int factorial(int val)
{
If(val>1)
Return factorial(val-1)*val;
Return 1;
}
静态局部对象
这种对象一旦被创建,在程序结束前都不会被撤销。在函数多次被调用过程中,静态局部对象会持续存在并保持它的值。例子:
Size_t conut_calls()
{
Static size_t ctr=0;
Return ++ctr;
}
Int main()
{
For(size_t i=0;i!=10;++i)
Cout<<cout_calls()<<endl;
Return 0;
}
内联函数避免额外开销,应该把内敛函数放到头文件中定义
指向函数的指针
Bool(*pf)(const string &,const string&);
cmpFcn pf1=0;
cmpFcn pf2=lengthCompare;
pf1=lengthCompare;
pf2=pf1;
用typedef简化函数指针的定义
Typedef bool (*cmpFcn)(const string&,const string&);
该定义表示cmpFcn是一种指向函数的指针类型的名字,只需要直接使用cmpFcn即可。
函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值
如果指向函数的指针没有初始化,或者具有0值。则该指针不能在函数调用中使用。只有当指针已经初始化,或被赋值为指向某个函数,方能安全地用来调用函数。
函数指针形参
Void useBigger(const string&,const string &,bool(const string&,const string &));
Void useBigger(const string&,const string &,bool(*)(const string&,const string &));
返回指向函数的指针
Int (*ff(int))(int*,int); //阅读函数指针声明的最佳方法是从声明的名字开始由里而外理解。
首先观察ff(int)是个函数,返回int(*)(int*,int);是这个函数指针类型
使用typedef
Typedef Int (*pf)(int*,int);
PF ff(int);
函数放在形参所对应的实参将自动转换为指向相应函数类型的指针,但当返回函数是不会进行转换导致出错
Typedef int func(int*,int);
Void f1(func);//ok
func f2(int)//error;
func *f3(int);//ok;
指向重载函数的指针
Extern void ff(vector<double>);
Extern void ff(unsigned int);
Void(*pf1)(unsigned int)=&ff;
指针类型必须与重载函数的一个版本精确匹配,如果没有精确匹配的函数,则对该指针的初始化或赋值都将导致编译错误
第二部分 容器和算法
第九章 顺序容器
标准库定义了3种容器vector(支持快速随即访问),list(支持快速插入删除) deque(双端队列)。还有三种容器适配器包括stack(后进先出),queue(先进先出)和priority_queue(有优先级管理的队列)类型
利用默认构造函数初始化容器
Vector<stinrg> svec; list<int> ilist; deque<sales_item> items;
初始化:和前面vector类似 C<T> c; C c(n,t);C c(n); 后两个只适用顺序容器
容器内元素必须1.元素必须支持赋值运算2.元素类型的对象必须可以复制3除了引用类型不可以之外全部内置或复合类型4.IO库类型不可以
在对类类型操作注意:vector<foo> emtry; 正确 vector<foo> bad(10) 错误
容器的容器
Vector< vector<string> > lines; OK 注意尖括号的位置
迭代器和迭代器的范围