这里写的主要是一些c/c++值得注意的地方和c++primer笔记,方便以后回顾,复习c++,当然会有一些错误,发现后再改正
//当形参引用时,数组不能转化为指针
//“\”是连接符,当宏定义用多行时常用
1:c中不可以连续赋值;
c++可以,如int a,b,c;a=b=c=1;
2:一般int main(){
...
}
如果main函数里不加system("pause");的话,程序运行后立马闪退,为了让程序暂停,也可以加
cin.get();(c++中,)c中可以加getchar();(输入后需按回车键才能发送到输出流),getche();(不 用按回车键,但输入的字符不会回显到屏幕上 ),getch();(不用按回车键,回显)
为什么呢?就已这个例子来说吧
int main(){
cout << "How many carrots do you have?" << endl;
int n=0;
cin >> n;
cout << "Here are two more;"<<endl ;
cout << "Now you have " << n + 2 << "carrots" << endl;
cin.get();
cin.get();
//system("pause");
return 0;
}
当我们根据提示输入一个数字,比如12,然后按回车,其实输入缓冲区里可能还有数据,按回车 后,第一个用来接收缓冲区里的数据,第二个用来停顿(当然用system("pause");最好)
3: c++在c的基础上又增加了long long型,sizeof(long long)=64;(一般来说);
bool型,值为false或true
4: 可以通过包含头文件climits来查看整型的最大值最小值(climits专门用于检测整型数据数据类型 的表达值范围。)
int main(){
int n_int = INT_MAX; //int型的最大值,下面依次类推
short n_short= SHRT_MAX; //这里写法注意,是SHRT_MAX;
long n_long = LONG_MAX;
long long n_llong = LLONG_MAX;
cout << "int is " << sizeof(int) << " bites" << endl;
cout << "short is " << sizeof(short) << " bites" << endl;
cout << "long is " << sizeof(long) << " bites" << endl;
cout << "long long is " << sizeof(long long) << " bites" << endl << endl;
cout << "Maximum values:" << endl;
cout << "shor:" << n_short << endl;
cout << "int:" << n_int << endl;
cout << "long" << n_long << endl;
cout << "long long:" << n_llong << endl;
cout << "The minumum int value:" << INT_MIN << endl;
cout << "Bits per byte:" <<CHAR_BIT << endl; //CHAR_BIT:char的二进制位数(bit)
system("pause");
return 0;
}
5: c++可以这样初始化:int a(10);//a=10;
int b{10};//b=10;
int c={};//c=0
int d{}; //;也可以不使用=
为什么要用大括号呢?因为大括号初始化可以初始化任何类型的变量(可以加等号,也可以不加)
6: 如果知道变量的值大于16位整数的最大可能值时,则用long,即使系统上int为32位(即sizeof (int=4)),也应该这么做,这样,
将程序移植到16位系统时,就不会突然无法正常工作。如果存储的值超过20亿,可使用long long.
7: 可以这样输出八进制,十进制,十六进制
cout<<oct<<45; //octal 八进制的
ocut<<dec<<45; //decimal 十进制的
cout<<hex<<45; //hexadecimal 十六进制的
8: 转义字符\a表示振铃字符(ASCII码为7),可以使终端扬声器振铃
9:float a = 2.34e+22f;
float b = a + 1.0f;
cout << "a=" << a << endl;
cout << "b-a=" << b - a << endl;
b-a应该为1,可是运行程序时输出的确是
a=2.34e+022;
b-a=0;
问题在于,2.34+22是一个小数点左边有23位的小数,加上1就是在第23位加1,但float类型只能表示 数字中的前6位或前7位,因此修改第23位对这个值不会有任何影响。
11: c中的宽字符基于wchar_t数据类它在几个头文件(包括wchar.h)中都有定义,像这样:
typedef unsigned short wchar_t;
因此,wchar_t数据类型与无符号短整型相同,都是16位宽
定义一个宽字节可以这样:
wchar_t c=‘A’;直接输出的话cout<<c<<endl;结果相是应的ASCII值,这里就是65了
还可以定义指向字符串的指针 wchar_t *p=L"hello";//注意要在前面加L(表示long);
//cout<<sizeof(p);结果是4
// sizeof(*p)是2(即h的字节数(宽))
也可以直接定义宽字符串wchar_t c[]=L"hello"; // sizeof(c)是12(5个字符,1个字符串结束标志,每 个占2位)
但这样直接输出cout<<c<<endl;结果是c的地址
cout<<p<<" "<<*p<<endl;输出的是p的地址和104(h的ASCII码);
宽字符想要输出对应的字符,应用wcout。
wchar_t是用两个字节存储的,例如字符串(wchar_t )“hello!”,intel将其在 内存中存储为
48 00 65 00 6c 00 6f 00 21 00
wchar_t *p=L"hello";
printf("%d\n",strlen(p));
所以如果这样的话 ,结果是1;因为strlen函数获取字符串的第一个字符后就遇到了0,即结束标志
,而单字节就不会有这种情况,为了解决这个问题,有宽字节的strlen版本,就是wcslen(wide
character string length)
12:c++中,求字符串长度不是c中的strlen();而是string.size();或string.length;
string a="hello";
cout<<a.size()<<" "<<a.length()<<endl;
13 :##表示粘贴符号
比如# define T(x) L##x
如果定义T("hello"),就相当于L"hello";
14:c++中的强制类型转换
typename(values)//更像是函数调用
c中: (typename)value
c++还引用了4个强制类型转换运算符,对它们的使用要求更为严格,例如:
static_cast<typename> (value)
===================================================================================
c++中,如果两个类型有关联,比如int型变量和float型变量可以相互转换,举个例子:
int i=3.14+1;//编译器会警告可能会丢失数据,但不会报错
相加的两个类型不同,c++不会直接将两个不同类型的值相加,而是先根据类型转换规则将类型统一后再求值,这些是自动进行的,不用程序员操心,有时甚至不用程序员了解,被称作"隐式转换";
其他类型的隐式转换:
1):数组转换为指针:
int arr[5];
int *p=arr;//arr转换为指向数组首元素的指针
当数组被用作decltype关键字的参数,或者作为取地址符(&)、sizeof
以及typeid等运算符的运算对象时,上述转换不会发生,同样的,如果
用一个引用来初始化数组,上述转换也不会发生,当在表达式中使用函
数类型时会发生类似的指针转换
2)指针的转换
c++还规定了几种其他的指针转换方式,包括常量整数0或者字面
值,nulllptr能转换成任意指针类型;指向任意非常量的指针能转换成
void *;指向任意对象的指针能转换成const void *;
3)转换成布尔类型;
存在一种从算术类型或指针类型向布尔类型自动转换的机制
4)转换成常量:
允许将指向非常量类型的指针转换成指向相应的常量类型的指针,
对于引用也是这样,比如:
int i;
const int &j=i; //可以将非常量转换成常量的引用
const int *p=&i; //可以将非常量的地址转换成const的地址
int &q=j,*r=p; //反之则错误
5)类类型定义的转换:
类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一
种类类型的转换;
string s,t="a string";//字符串字面值转换为string 类型
while(cin>>s) //cin转换为布尔值
显示转换
1)命名的强制类型转换
cast-name<type>(expression);
type是转换的目标类型而expression是要转换的值,如果type是引用
类型,则结果是左值,cast-name是static_cast,dynamic_cast,
const_cast和reinterpret_cast(reinterpret)中的一种,
a.dynamic_cast:
dynamic_cast支持运行时类型识别,cast-name指定了执行的是哪
种转换
b.static_cast:
当需要把一个较大的算术类型赋给较小的类型时,static_cast
非常有用,此时不会警告丢失数据了;
static_cast对于编译器无法自动执行的类型转换也非常有用,
例如,可以使用static_cast找回存在于void *指针中的值
void *p=*d;//任何非常量对象的地址都能存入void *
double *dp=static_cast<double *>(p);
//将void *换回初始的指针的类型
当把指针存放在void *中,并且使用static_cast将其强制转
换回原来的类型时,应该保证指针的值保持不变,因此,必须确
保转换后所得类型就是指针所指的类型,类型一旦不符,将产生
未定义的后果
c.const_cast:
const_cast只能改变运算对象的底层const,
const char *pc;//pc是指针,指向const char类型,这里
//的const是底层const,比如char *const pc,这个const就是
//顶层const,个人觉得没必要刻意去记,这些东西很好理解
char *p=const_cast<char *>(pc);//正确,但是不能改变p所
//指对象的值
d.reinterpret_cast
reinterpret_cast通常为运算符对象的位模式提供较低层
次上的重新解释,比如:
int *ip;
char *pc=reinterpret_cast<char *>(ip);
请牢记pc所指的真实对象是一个int而非char,如果把pc当
成普通的字符指使用就可能在运行时发生错误,例如:
string str(pc);//这可能导致异常的运行时行为
使用reinterpret_cast是非常危险的,上面这个例子很
好的证明了这一点,其中的关键问题是类型改变了,但编译
器没有给出任何警告或错误的提示信息,当用一个int的地址
初始化pc时,由于显示地声称这种转换合法,所以编译器不
会发出任何警告或错误信息,接下来再使用pc时就会认定它
的值是char *类型,编译器没法知道它实际是指向int,这种
错误很难发现
2)旧式的强制类型转换
早期的c++中,显示地进行强制类型转换有两种形式:
type (expr); //函数形式
(type) expr; //c语言形式
===================================================================================
15: c++11新增了一个工具,让编译器能够根据初始值的类型推断变量的类型,为此,它重新定义了auto,auto是c语言的一个关键字,但很少使用
auto x=2.1;//x是double型
16:cin可以读取一个字符串到输入流,但是如果用cin输入时,输入的字符串含有空格键,tab 键,回车键等,就会结束输入,这是面向单词的输入,
istream中的类(如cin)提供了一些面向行的类成员函数:(比如定义了一个数组name[size])getline(name,size)和get(name,size),区别是getline()输入结束后就会丢弃换行符,而get()不会(即缓冲区还有换行符)
17:char a[] ="string";
char *p = a;
cout << "a=" << a << " &a= " << &a << endl;
cout << "p=" << p << " &p=" << &p << " (int *)p=" << (int *)p << endl;
结果a=string &a=0018F7c0
p=string &p=0018F7B4 (int *)p=0018F7c0
18: 通常,cout在显示bool值之前将其转化为int,但cout.setf(ios:boolalpha)(老式c++)和cout.setf(
ios_base::boolalpha)(c++11)函数调用设置了一个标记,命令cout显示true和false,而不是1和0
int a;
cout << (a>3) << endl;
cout.setf(ios_base::boolalpha);
cout << (a>3)<<endl;
19:对于i++和++i,如for(int i=0;i<n;i++)和
for(int i=0;i<n;++i)
在c++内置类型中,没有差别,但如是用户自定义类型中,++i效率更高,直接将i+1然后进入下一循环,而
i++是先复制一个副本,将其加1,然后再将副本返回
20: 一个细节,值得注意
int i = 8;
if (i++==9||i==9)
cout << "yes" << endl;
else cout << "no" << endl;
c++规定,||是个顺序点,也就是说,先修改左侧的值,再对右侧进行判定(c++11的说法是,运算符左边
的子表达式先于右边的子表达式)冒号和逗号运算符也是顺序点
左边i++==9 这里i还是8,然后到右边,i就是9了,所以if里面为true
21:c++从c继承了一个与字符相关的,非常方便的函数软件包,它可以简化诸如确定字符是否为大写字母,数字
标点符号等工作,这些都在cctype(c中是ctype.h)中定义的,ep:ch是一个字母,则isalpha(ch)函数返回
一个非零值,否则返回0;同样,如果ch是标点符号(如逗号或句号),函数ispunct(ch)函数返回true、(
这些函数的返回类型为int,而不是bool,但通常bool转换竜让您能够将它们视为bool类型)
使用这些函数就方便多了,例如:
if(ch>='a'&&ch<='z'||ch>'A'&&ch<'Z')
可以用if(isalpha(ch))代替
还有很多函数,请看C.Primer.Plus (197页)
22:for循环可以这样用
int a[5] = {1,3,5,7,9};
for (int i : a)
cout << i << endl;
结果输出数组a里的全部元素
23:通常,在输入字符,判断何时结束时一般都是按#键结束(或者
其他的一些按键) ,这样虽然大多数情况下不会有问题,但是
如果判断结束的字符是我们要输入的呢?这不就输入不了吗?如果输
入是来自文件·,则可以使用检测文件尾(EOF)来判断是否结
束输入,不过c++还可以用键盘模拟文件尾,过程是这样的,检测到
EOF后,cin将两位(eofbit和failbit)都设置为1。可以通过成员函
数eof()看eofbit是否被设置,如果检测到EOF,cin.eof()返回ture;
同样,用cin.fail()的话,返回true;一般使用较多的是fail(),因为f
ail()可以用于更多的实现
# include <iostream>
using namespace std;
int main()
{ char ch;
int count=0;
cin.get(ch); //cin不会识别空格,tab,换行等空白键
while(cin.fail()==false)//当没有检测到EOF,循环
{
count++;
cout<<ch;
cin.get(ch);
}
cout<<endl<<count<<"characters\n";
//结束输入时,按下ctrl+z,然后按回车
system("pause");
return 0;
}
24:int *p=new int[size];
delete []p;
25:double(*p)(double a, double b);
p = max;//函数名就是函数地址
double a = p(4, 5);//函数名是函数地址,指针p指向函数的地址,所以p应该和max有相同的
double b = (*p)(4, 5);//p是函数指针,*p就是函数
cout << "a=" << a << endl << "b=" << b << endl;
结果a和b一样,这两种用法都可以
如果声明了一个函数,又定义一个指向该函数的指针,可以使用c++11的·自动类型推断功能,这样
就方便得多,上面的就可以写成
auto p=max;而不用写成
double(*p)(double a, double b);
p = max;
了,但是auto只能用于单值初始化,而不能初始化列表--
还可以用typedef简化
typedef double(*type)(double a, double b);
type p;
26: 内联函数(inline)
内联函数相比普通函数,调用时相当于直接使用函数的副本,而不用去找到函数的地址执行函数,所以
速度相对稍快点,不过计算机运行速度非常快,所以也快不了多少,而且内联函数更占用内存
声明和定义时必须都加上inline关键字,而且内联函数不能递归,关于内联,了解即可,一般用的比较少
c中使用宏-----内联代码的原始实现
# define SQUARE(X) X*X
这是通过字符串替换实现的,而不是传递参数
SQRARE(5.0); //可以实现
SQUARE(1+2); //得到的不是9,而是1+2*1+2
SQUARE(a++); //得到的是a++*a++;a会自增两次
这里不是为了说如何使用宏,而是为了说明当用c的宏实现某些功能时,应转化为内联
27: ofstream继承了ostream,ostream中的一些方法:
1)setf()
让你能够设置各种格式化状态,例如,方法调用setf(ios_base::fixed);将对象置于使用定点表 示法的模式,setf(ios_base::showpoint);将置于显示小数点的模式,即使小数部分为0
2)precision()
指定显示多少位小数(假定对象处于定点模式下)。
所有这些设置都将一直保持不变,知道再次调用相应的方法重新设置它们。
3)width()设置下一次输出操作使用的字段宽度,这种设置只在显示下一个值时有效,然后将恢复到默认设置,默认的字段宽度为0
函数file_it使用了两个调用
ios_base::fmtflags initial;
initial=ios.setf(ios_base::fixed);//save initial formatting state
...
iso.setf(initial); //restore initial formatting state
方法setf()返回调用它之前有效的所有格式化设置,ios_base::fmtflags是存储这种信息所需的数据类型
名称,因此,将返回值赋给initial将存储调用file_it()之前的格式化设置,然后便可以使用变量initial
作为参数来调用setf(),将所有的格式化设置恢复到原来的值。
28:默认参数(声明时给默认值,定义时不要加,有些编译器会报错)
例如函数声明void fun(int n=1);
如果调用函数时忘了加参数,则默认传入的参数是1
对于带参数列表的函数,必须从右向左添加默认值,例如
int fun(int n,int m=2,int j=3); //可以
int fun(int n,int m=2,int j); //不可以
实参按从左到右的顺序依次赋给相应的实参,而不能跳过任何参数
int n=fun(1,,4);//不可以
29:函数重载
当调用函数时,如果未找到参数列表匹配的函数,c++会尝试使用标准类型转换强制进行匹配,如果 可以唯一匹配则匹配,否则若有多个可以
匹配,将视为错误
匹配函数时,并不区分const和非const变量
但是将非const赋给const是合法的,反之则不行
函数重载,不可以返回值类型不同,参数相同,但可以返回值类型,参数都不同
函数重载不要滥用,仅当函数基本执行相同的任务,但是用不同形式的数据时,才采用函数重载,
30: cv-限定符:
1)const
2)volatile (不稳定的;挥发性的;爆炸的)
31: 名称空间
1)术语:
a: 声明区域(declaration region)
b: 潜在作用域(potential scope)
2)名称空间
用namespace创建名称空间
ex:
namespace Jack{
double pail;
void fetch();
int pal;
struct Well{...};
}
namespaace Jill{
double bucket(double n){...};
double fetch;
int pal;
struct Hill{...};
}
名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中;名称空间是开放的,
可以把名称加入到已有名称空间中,例如:
namespace Jack{
char *goose(const char *);
}
同样,若名称空间中为某函数提供了原型,可以再次使用该名称空间来提供该函数的代码
namespace Jack{
char *goose(const char *)
{...}
}
如果使用using namespace Jack;
局部变量会隐藏Jack里同名的变量,
31:初始化string对象的方式
string s1; s1为空串
string s2("abc"); 用字符串字面值初始化s2
string s3(s2); 将s3初始化为s2的一个副本
string s4(n,'c'); 将s4初始化为字符'c'的n个副本
string的常用操作:
s.empty() 若为空串,返回true,否则返回false
s.size() 返回s中字符的个数
但是注意!!!!!!!!
string s="ds"+"df"; 这样是非法的,不允许
32: class内部的函数优先是使用内联函数的,即使我们自己没写inline,不过要是无法用内联函数就不会用内
联函数编译了
c++远征之封装篇
33: 内存分区
栈区:int x=0;int *p=NULL;
堆区:int *p=new int;
全局区:存储全局变量及静态变量
常量区: string str="hello";
代码区:存储逻辑代码的二进制
34: 初始化列表
可以通过初始化列表的方式
class Student
{
public:
Student():m_strName("Jim"),m_iAge(19) //对于多个数据成员初始化,要用逗号隔开,赋值时
//用括号
{
...
}
private:
string m_strName;
int m_iAge;
};
初始化列表优先于构造函数执行,即先给数据成员赋值,在调用构造函数,初始化列表只能用于构造函 数,这种方式初始化速度快,效率高,推荐使用这种方式,那么,既然构造函数完全可以做这种工作,为
什么又要初始化列表呢?仅仅是为了效率高,速度快?不是的,举个例子:
class Circle
{
public:
Circle(){m_dPi=3.14;} //如果用这种方式初始化,编译器会报错
Circle():m_dPi(3.14){} //但是初始化列表可以
private:
const double m_dPi;
};
圆这个类中,Pi是不变的,因此用const,以后建议这样用
Student(string name,int age):m_strName(name),m_iAge(age)
{
...
}
35: 拷贝构造函数
class Student
{
public:
Student(string name,int age):m_strName(name),m_iAge(age)
{
cout<<" Student(string name,int age):m_strName(name),m_iAge(age)"<<endl;
}
private:
string m_strName;
int m_iAge;
};
当定义了一个Student类,然后再main函数中:
Student s1;
Student s2=s1;
Student s3(s1);
这样实例化s1调用的是我们在类中定义构造函数,而s1和s3调用的是拷贝构造函数,因为自己没定义拷贝
构造函数,所以只会打印一行"Student(string name,int age):m_strName(name),m_iAge(age)",而不是三行;如何定义拷贝函数呢?以这个Student为例:
定义格式:类名(const 类名 &变量名){ }
Student(const Student &s)
{
cout<<"Student(const Student &s)"<<endl;
}
当要传递类实例化的对象的值时就会调用拷贝构造函数,比如定义一个函数void test(Student s){}
这时由于是值传递,所以会调用拷贝构造函数
36: 析构函数
定义格式:~类名(){ } //不加任何参数
37: 对象成员
即类的成员还是类,比如定义一个线段类,两点确定一条线段,所以线段应该有两个表示点的数据成员, 而点也是一个类
38: 深拷贝与浅拷贝
浅拷贝:只是简单赋值,比如:
class Array
{
public:
Array(){m_iCount=5;m_pArr=new int[m_iCount];}
Array(const Array &arr)
{
m_iCount=arr.m_iCount;
m_pArr=arr.m_ipArr;
}<