c++ primer -笔记
1. char , wchar_t 两个类型虽然都是字符型但区别比较大:
在宽度上来说,一个是1byte,一个是2byte(在linux上实际是4byte)
在编码上来说 wchar_t表示unicode编码方式
2. 左值:左值可以出现在赋值语句的左面或者右面
右值:右值只能出现在赋值语句的右面儿不能出现在左面
先看内置类型:
int a ; // 没有初始化内容,它的值由系统分配规则是在栈区和堆区(函数内定义或者类里面定义)都取随机值,在全局区(全局常量,静态变量)都是全零值。
int b=1 ; // 这种叫赋值初始化
int c(1),d(b + 2) ; // 这种叫直接初始化()中不一定是常量可以是一个表达式或变量
这两种初始化是不一样,后面讲到类类型时就会明白一般来说直接初始化更灵活且效率更高。
需要特别强调的是未初始化的内置类型虽然也会有值但是其分配规则导致其值不确定性,所以我们定义内置类型时一定要初始化值而不依赖系统的内部非配规则。
再看类类型定义:
string a; // 隐式初始化初始化成了 “”
string b = "name" ;
string c(b) ;
string d(10,'a') ; // string类特有的初始化方法,等同于10个字符a组成字符串并赋值初始化给变量b
类类型的初始化其实就是构造函数, 隐式初始化实际上就调用类型的默认构造函数来初始化类型对象;直接初始化是调用类的拷贝构造函数;而直赋值初始化是调用赋值操作符重载和拷贝构造函数。
c++是一门奇怪的语言,我们先看如下代码
{
std::cout << v1 << std::endl;
}
int v1 = 1;
绝大部分语言中这样的写法是没有问题的,但在C++中却会编译不通过,因为C++是严格的顺序编译非类对象代码的, 当main函数编译时要用到的变量v1还没有执行定义,c++就会抛出异常说使用了未定义的变量v1,如果吧这个 int v1;语句放到函数前面就没问题。那么我们是不是只能这样做呢? 答案当然是否定的,我们可以不改变现在代码顺序而使用声明解决这个编译错误,可以这样修改代码:
{
extern int v1;
std::cout << v1 << std::endl;
}
int v1;
注意红色新增代码就是一个声明。他的意思是说:HI,系统中的某个地方定义了一个叫v1的int变量,所以你可以放心的使用。
声明是告诉编译器一些信息所以不会分配内存空间也不会产生具体的数据。 声明的对象一旦被使用就一定要在某个地方定义它,否则编译器即使暂时不抛出异常在编译完毕后发现根本没有地方找到相关定义也一样会编译失败。
以上例子只适合变量,同一个文件中常量在使用前一定要先定义,否则即使做了申明也会出错。
声明最大的好处体现在多文件的编程中,比如说我编写了一个头文件并引入到了主文件中,头文件需要使用主文件定义的变量。由于头文件一般是先于主文件编译执行的,所以就会出现未定义的编译错误,如果在头文件中先声明一下这变量再使用就没有问题了。 除了变量C++还有常量(后面会讲到),它和变量有一些不同
枚举
枚举是一组可选常量值,既然是一组可选值说明包含多个常量。枚举定义语法如下
enum val{val1 = 2, val2 = 4, val3} // 最后一个内容没有显示给值等价于 val3 = 5
如果不指定值默认第一个值从0开始下一个依次+1递增。
枚举的每一项都是一个唯一的const类型值,上面的定义有点类似于:
const val1 = 2; const val2 = 4; const val3 = 5;
由于是const的,所以 val2 = 1 或者 val a = 2; 都不允许。
枚举项和int类型值有对应关系,但是二者只能单向转换,枚举可以自动转成int,而int却不能转成枚举
val a = val2 ; // 枚举之间赋值初始化
int b = val2 ; // 枚举转成int并初始化
val a = 2 ; // int 不能转成枚举,无法初始化
typedef
这个关键字非常有用,用来给某个类型指定一个别名,比如
typedef int zx ;
int a(1) ; // 等同于 zx a(1)
标准string类型
首先要明确类型是类类型,意味着它有构造函数,也类似我们自定义的类一样的其他类对象。
它有几种初始化方式如下
string s1 ; // 调用默认构造函数初始化对象
strng s2(s1) ; // 将S2初始化为S1的一个副本
string s3("value") ; // 用一个字符串值初始化对象
string s4(n,'c') ; // 用N个字符‘C’组成字符串作为初始化s4的值
特别要注意的是第一种初始化方式,虽然默认构造函数是没有参数的但是不能因此就写成 string s1()
string s1 ; // 调用默认构造函数初始化
int i ; // 要根据定义位置来确定初始化值,全局变量一律初始化为0, 局部变量是一个随机数,称为未初始化
getline(cin,line) 是一个系统函数,可以输入标准行内容,这个函数不会忽略任何内容一直读取用户输入并保存到line,直到用户输入换行函数才结束,结束时函数会返回cin的引用。 如果用户刚开始就输入换行符那么line的内容就是"" 。
上文说过string是类类型所有有很多类成员(属性和成员函数),下面就是一些常用的操作
s.empty() ; // 判断s是否为空,相当于s.size()==0
s.size() ; // s的长度
s[n] ; // n位置的字符(左值返回) 可下标访问string中的字符。
s1+s2 ; // 返回s1和s2连接的串
s1=s2 ; // 把s1替换为s2的副本
s1==s2 ; // 判断s1,s2是否相等
!=,<,<=,>,>= // 按字典顺序比较
vector是个类模板,如果你了解JAVA或C#范型编程的话可以理解为范型类。泛型最大的好处是只需定义一个类或函数就可以提供不同类型版本的操作。
它的初始化有如下几种方式: T用一个类型来代替
vector<T> v1 ; // 默认构造函数v1为空
vector<T> v2(v1) ; // v2是v1的一个副本
vector<T> v3(n, i) ; // v3包含n个值为i的元素 参数 T 如果是类类型则一定要有拷贝构造函数(未定义的情况下系统会自动分配一个)
vector<T> v4(n) ; // v4含值初始化的元素个副本 参数 T 如果是类类型则一定要有默认构造函数(未定义的情况下系统会自动分配一个) 如果是内置类型则分配n个0
对于类类型如果不能满足红色标示的要求编译会失败。 关于类类型的拷贝构造函数和默认构造函数后续章节有介绍
Vector对象有几种最重要的操作
v.push_back(t) ; // 在数组的最后添加一个值为t的数据
v.size() ; // 当前使用数据的大小 返回vector<T>::size_type类型的长度值,其意义类似上面讲过的string::size_type
v.empty() ; // 判断vector是否为空
v[n] ; // 返回v中位置为n的元素 和string类型下标操作类似 是个左值操作
v1=v2 ; // 把v1的元素替换为v2元素的副本
v1==v2 ; // 判断v1与v2是否相等
!=、<、<=、>、>= ; // 保持这些操作符惯有含义