首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

C++ primer 读数笔记

C++ primer 读数笔记

C++初始化变量有好几种方法:

int a=0;

int a={0};

int a{0};

都可以。

如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。

如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式地初始化变量(任何包含了显式初始化的声明即成为定义。):

extern int i;   // 声明i而非定义i  

int j;          // 声明并定义j  

(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化,最好也初始化,否则容易出错)。

(2)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。

它的作用是为变量起一个别名。假如有一个变量a,想给它起一个别名b,可以这样写:

int a;

int &b=a;

int *p= &a;  这里&是取地址符。表示指针p指向(本语句等价于
int *p; p= &a;)
  

这就表明了b是a的“引用”,即a的别名。经过这样的声明,使用a或b的作用相同,都代表同一变量

int &b=a2;//企图使b变成a2的别名(引用)是不行的。这样是错误的。

引用不是一个对象,所以不能定义引用的引用。

const char *p = "hello world";   
指针指向的变量不可以改变,指针本身可以改变(即指向常量的指针)

           
这种情况下,以下语句是合法的:

                          
char c = *p;

                          
p++;

                              
以下语句不合法:

                          
*p = 'a';

                          
p[5] = 'b';

    char *const p表示指针指向的变量可以改变,但指针本身不能改变。

                     
比如




int i=4;





















int *const p = &i;

                              
那么*p = 5;是合法的,p++是不合法的

    const char  *const p
两者都不可以改变。

理解这些声明的技巧在于,查看关键字const右边来确定什么被声明为常量 ,如果该const的右边是类型,则值是常量(底层const);如果const的右边是指针变量,则指针本身是常量(顶层const)。赋值拷贝的时候要特别注意底层const,一般来说,非常量可以赋值给常量,反之则不行。

例如const int *a = int *b;  反之不行。

C++ 11中引入的auto主要有两种用途:自动类型推断和返回值占位。auto在C++ 98中的标识临时变量的语义,由于使用极少且多余,在C++ 11中已被删除。

auto自动类型推断,用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推断,可以大大简化我们的编程工作。下面是一些使用auto的例子。

auto a; // 错误,没有初始化表达式,无法推断出a的类型  

auto int a = 10 // 错误,auto临时变量的语义在C++ 11中已不存在  

auto a = 10  

auto c = 'A'

auto s("hello");  

vector<</span>int> vctTemp;  

auto it = vctTemp.begin();  

auto ptr = [](){ cout << "hello world" << endl; };

另外,在使用模板技术时,如果某个变量的类型依赖于模板参数,不使用auto将很难确定变量的类型(使用auto后,将由编译器自动进行确定)。下面是一个具体的例子。

template <</span>class T, class U>  

void Multiply(T t, U u)  

{  

auto v = t*u;  

}

auto返回值占位,主要与decltype配合使用,用于返回值类型后置时的占位。

template <</span>class T, class U>  

auto Multiply(T t, U u)->decltype(t*u)  

{  

typedef decltype(t*u) NewType;  

NewType *pResult = new NewType(t*u);  

return *pResult;  

}

至于为什么需要将返回值类型后置,这里简单说明一下。如果没有后置,则函数声明为decltype(t*u) Multiply(T t, U u),但此时模板参数t和u还未声明,编译无法通过。

如果这个表达式是个函数,decltype
给出的类型为函数返回值的类型。

[cpp] view plaincopy

int add(int i, int j){ return i+j; }  

decltype(add(5,6)) var = 5;//Here the type of var is return of add() -> which is int  

非常重要的标记一下,decltype
不会执行表达式而auto会,他仅仅推论一下表达式的类型。


int foo(){}  

decltype( foo() ) x; // x is an int and note that   

                     // foo() is not actually called at runtime  


头文件不应包含using 声明


string s1="hello", s2="world";

string s3=s1+","+s2;

string s4 =s1+",";

string s4 =s1+s2;

而+两侧运算对象至少一个是string类型:

string s6="hello"+","+s2;
//错误, 不能把字面值直接相加


基于范围的for 语句

遍历给定序列中每个元素并操作

简单例子:把string对象中的字符每行一个个的输出出来


string str("some strings");

for (auto c : str)

cout<<c<<endl;

这里使用auto 让编译器来决定c变量的类型,这里是char型


但如果想改变string 对象中的字符值,循环变量必须定义成引用

比如将string对象字符全变为大写

string str("some strings");

for (auto &c : str)

c=toupper(c);

cout<<s<<endl;

这里toupper函数是定义在〈cctype〉头文件中的处理string中的某个特定字符的集合。



标准模板库类型vector 表对象的集合。一般用{  }初始化,不容易错。

vector 〈string〉 v1{"a","ab","abc"};

除了以下情况用()初始化

vector 〈string〉 svec(10, "hi!");   表示10个string 类型的元素,每个都被初始化为"hi!"。

vector 〈int〉svec(10);   表示10个int 类型的元素,每个都被初始化为0。

vector 〈int〉svec(10,1);   表示10个int 类型的元素,每个都被初始化为1。

如果这时使用{}

vector svec{10};

表示1个int类型元素,该元素是10。

vector 〈int〉svec{10,1};   表示2个int类型元素,分别是10和1。

它的成员函数push_back()

vector 〈int〉 v2;

for(int i=0; i!=100; ++i)

v2.push_back(i);  //依次将整数值放到v2的尾端 。。使用C++,在for循环中要习惯使用!=而不是


迭代器

(1) 每种容器类型都定义了自己的迭代器类型,如vector:
vector::iterator iter;这条语句定义了一个名为iter的变量,它的数据类型是由vector定义的iterator类型。
(2) 使用迭代器读取vector中的每一个元素:
vector ivec(10,1);
for(vector::iterator iter=ivec.begin();iter!=ivec.end();++iter)
{
*iter=2; //使用 * 访问迭代器所指向的元素
}


其中begin()成员函数指向容器中的第一个元素,而end()成员函数其实指向最后一个元素的下一个元素,所以没什么真正含义,只是一个标记而已,如果v.begin()=v.end() ,则容器为空。
const_iterator:
只能读取容器中的元素,而不能修改。
for(auto it=ivec.cbegin();it!=ivec.cend();it++)
{
cout<<*it;
}


这里cbegin()和cend()是常量迭代器,是const_interator的类型,只能用于读取容器中的元素,不能修改容器中的元素。


注意,但凡是使用了迭代器的循环体,都不要想迭代器所属的容器中添加元素。




解引用操作符:所有迭代器都提供了解引用操作符(*),用于获取迭代器所指向的元素。以下代码都是合法的。

std::cout
<<
*iter;

*iter
=
5;

*iter
=
*iter
+
5;

取后继元素操作符:所有迭代器都可以通过 iter++、++iter 操作符获取其后继元素的迭代器。


迭代器运算


it+n  表示迭代器所指示的位置相比原来的向右(前)移动了n个元素,


it-n  表示迭代器所指示的位置相比原来的向左(后)移动了n个元素,






数组的维度必须是一个常量表达式:


int  a[10];


int  a[var];  //除非var是constexpr,否则错误。


在C++中,数组大小固定,,对某些程序性能较好,但灵活性较差,如果不清楚元素的确切个护士,请使用vector.



不能把一个数组直接赋值给另一个数组。也不允许用vector对象来初始化数组。但允许使用数组来初始化vector 对象。


不存在引用的数组。


数组和指针关系密切:


string nums[] ={"one","two","three"};  


//这里没声明数组的大小,但编译器会根据初始值的数量推算出为3


string *p=&nums[0];






多维数组的初始化
允许使用花括号括起来的一组值初始化多维数组,这点和普通的数组一样。下面的初始化形式中,多维数组的每一行分别用花括号括了起来:

int ia[3][4] = {      
// 三个元素,每个元素都是大小为4的数组

   
{0, 1, 2, 3},      
// 第1行的初始值

   
{4, 5, 6, 7},      
// 第2行的初始值

   
{8, 9, 10, 11}      
// 第3行的初始值

};
其中内层嵌套着的花括号并非必需的,例如下面的初始化语句,形式上更为简洁,完成的功能和上面这段代码完全一样:
// 没有标识每行的花括号,与之前的初始化语句是等价的
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};


程序中经常会用到两层嵌套的for循环来处理多维数组的元素:


constexpr size_t rowCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt]; // 12 个未初始化的元素
// 对于每一行
for (size_t i = 0; i != rowCnt; ++i) {
   
//对于行内的每一列

   
for (size_t j = 0; j != colCnt; ++j) {

      
// 将元素的位置索引作为它的值

      
ia[j] = i * colCnt + j;

   
}

}
外层的for循环遍历ia的所有元素,注意这里的元素是一维数组;内层的for循环则遍历那些一维数组的整数元素。此例中,我们将元素的值设为该元素在整个数组中的序号。
使用范围 for语句处理多维数组
由于在C++11新标准中新增了范围for语句,所以前一个程序可以简化为如下形式:

size_t cnt = 0;
for (auto &row : ia)           
// 对于外层数组的每一个元素

   
for (auto &col : row) {   
// 对于内层数组的每一个元素

      
col = cnt;            
// 将下一个值赋给该元素

      
++cnt;                 
// 将 cnt加1

}
因为要改变元素的值,所以得把控制变量row和col声明成引用类型。
标准库函数 begin和end  跟容器的bengin和end 成员函数功能差不多。不过毕竟数组不是类类型,所以不太一样。
int ia[] = {0,1,2,3,4,5,6,7};
int *beg = begin(ia); //指向ia的首元素
int *last = end(ia);  // 指向数组ia的最后一个元素的下一个位置。

返回列表