数组

数组若没有指定初始值则为内存遗留值

如果指定了部分初始值,那么其余部分也默认被指定为0:

long data[100]={0};          //给data数组的所有元素赋0

字符串是附加有特殊字符(/0)的字符序列

数组的填充:

char president[]="thank you";

wchar_t president[]=L"thank you";     //Unicode字符串

const int max=80;

char name[max];

cin.getline(name,max,'\n');    //从输入读取存到name字符数组中,遇到'\n'(不存储/n)或者以读取max-1则结束

long data[2][3]={

                              {1,2},

                              {3}

                            };

char start[3][80]={

                                "hello world",

                                "thank you"

                             }; 

cout<<start[0]<<start[1]<<start[2];

long data[2][3]={0};

注:编译器自动在最后加/0;没有填充的数组默认被填充0;二维数组初始化的时候可以省略第一维的大小,让编译器自动计算;应用某个字符串的位置只需要第一个索引值

指针

&:取址运算符,或者引用运算符

*:间接寻址运算符,或者解除引用



运算符,与某个指针一起使用可以访问该指针指向的变量的内容,而访问指针所指向的变量中数据的过程称为解除引用指针

初始化:

1.

int number(0);

int* pnumber(&number);

2.

int* pnumber(nullptr);    //不指向任何对象的指针,过去使用0或者NULL,使用nullptr为了防止歧义,nullptr是std::nullptr_t类型,可以饮隐式转换为任何指针类型,不可以转换为除了bool类型以外的任何整数类型

if(pnumber==nullptr)      //也可以写成if(!pnumber)

   cout<<"pnumber does not point to anything."<<endl;

3.

char* proverb("a miss is as good as a mile");

char* pstr[]={"this is str1","this is str2","this is str3"}        //初始化指针数组

cout<<pstr[0]<<pstr[1]<<pstr[2]<<endl;   //打印指针内容,这里不是打印地址死因为输出操作以一种特殊的方式看待这种类型的指针,即将其视为字符串(即char数组),因此输出字符串本身,而不是字符串的地址

sizeof操作

int dice;

cout<<sizeof dice;

结果:4

cout<<(sizeof pstr)/(sizeof pstr[0]);       //计算数组元素的个数

指向常量对象的指针(不能修改被指向的对象,但是指针可以指向其他对象):

const char* pstring("some text");

指向某个对象的常量指针(可以修改被指向的对象,但是不能修改指针存储的地址):

char* const pstring("some text");

指向常量对象的常量指针(被指向的对象和指针中存储的地址都不能修改):

const char* const pstring("some text");

注意:如"hello world"这样的字符串字面值的类型是const char数组,所以改变指向const char数组的对象值会出错(相当于试图修改const值):

char* pstr[]={"hello world","thank you"};

*pstr[0]=*pstr[1];

cout<<pstr[0]<<endl<<pstr[1]<<endl;

而改变指针所指向的内容却没有问题:

char* pstr[]={"hello world","thank you"};

pstr[0]=pstr[1];

cout<<pstr[0]<<endl<<pstr[1]<<endl;

输出为:

thank you

thank you

指针和数组:

大多数情况下,如果单独使用一维数组的名称则该名称将自动转换为第一个元素的指针,但是用作sizeof的操作符除外

double* pdata(nullptr);

double data[5]={0};

pdata=data;   //将数组第一个元素的地址赋给指针data

pdata=&data[1];    //将数组第二个元素的地址赋给指针data

pdata++;    //指向data[2],即将指针的地址加8,加的值和指针的类型相关

*(pdata +1)=*(data+2);    //等价于data[1]=data[2];

double data[3][4];

pdata=&data[3][4];

pdata=data[3];

注意pdata=data;是错误的,因为data的类型是double [3][4],指针必须为double (*pdata)[4];

例:

代码:

int data[3][4]={0};

//int *pdata=data[0];

//int *pdata=&data[0][0];

int *pdata=*data;     //以上三种方法等价

for(int i=0;i<3;i++)

{

for(int j=0;j<4;j++)

{

data[i][j]=i*10+j;

//cout<<setw(6)<<data[i][j]<<" ";

//cout<<setw(6)<<*(pdata+i*4+j)<<" ";

//cout<<setw(6)<<*(*data+i*4+j)<<" ";

//cout<<setw(6)<<*(data[0]+i*4+j)<<" ";

cout<<setw(6)<<*(*(data+i)+j)<<" ";     //以上五种方法等价

}

cout<<endl;

}

结果:

     0      1      2      3

    10     11     12     13

    20     21     22     23

总结:

data等价于&data[0],data为引用第一行的地址

*data等价于*&data[0]等价于data[0],*data为解除data的引用,存放第0行的地址

**data等价于*data[0]等价于*&data[0][0]等价于data[0][0]

可以把[]理解为解除引用,和*一样,[]中的数字为指针移动的单位距离

动态分配内存

计算机未使用的内存存放在堆中,堆也被称作空闲存储器

new操作从堆中为特定类型的新变量分配内存,返回分配内存的地址

delete操作释放new操作分配的内存

分配内存

double* pvalue(nullptr);

pvalue=new double;

*pvalue=9999.0;

等价于

double* pvalue(nullptr);

pvalue=new double(9999.0);

等价于

double* pvalue( new double(9999.0) );

删除分配的内存为delete pvalue;

为数组分配内存

pstr=new char[20];

pstr=new char[count];       //注意count可以是变量,但是char str[count]中的count必须为常量(const int count)

pstr=new char[20][30];

pstr=new char[count][30];  //对于多维数组来说只能最左边的一维可以是变量,其余必须为常量

使用

*pstr='a';

*(pstr+1)='b';

删除

delete [] pstr;   //[]为了之处要删除的是一个数组,不管删除多少维数组都一样

pstr=nullptr;     //把指针重新设置为空可以防止试图访问已经删除的内存

引用(reference)

分为lvalue引用和rvalue引用

lvalue引用

lvalue引用为另一个变量的别名

lvalue引用的是一个可以出现在赋值操作左边的持久存储位置

声明应用的时候必须指出对应的变量

不能修改引用使其表示另一个变量(指针可以)

指针需要解除引用才能参与表达式,而引用不必解除引用,某些方面引用就像被解除引用的指针

lvalue引用完全等价于被引用的变量

&在变量声明时候加为引用,否则为取地址

long number(0L);

long & rnumber(number);    //这里的&为引用

long* pnumber(&number);    //这里的&为取地址

rnumber+=10L;    //等价于number+=10;

*pnumber+=10L;   //这里解除引用(*)的指针相当于引用

const int & refData=5;  //注意因为5为常量不能改变,所以必须使用const引用

rvalue引用

类型名后面用&&来指定一个rvalue引用类型

是一个临时值

可以用一个lvalue来初始化一个rvalue引用,这时rvalue引用能够像lvalue引用一样工作

int x(5);

int&& rx=x;  //用lvalue(即x,为一个持久存储位置)初始化rvalue引用

int && rExpr=2*x+3;    //2*x+3为临时值(没用存储位置,不能放在=左边),所以2*x+3不能初始化lvalue引用

字符串的本地C++库函数

cstring头文件中包含以空字符结尾的字符串函数

例:

#include <cstring>

#include <cerrno>    //错误代码值EINVAL和ERANGE都是在cerrno头文件中定义  

char* str1="my name is clc";

char* str2=" hello world";

char str3[50]="thank you";

1.strlen()

计算字符串的长度

    cout<<strlen(str1)<<endl;

    cout<<strnlen(str1,20)<<endl;

结果:

14

14

说明:

strnlen为strlen的安全版本,因为通过查找空字符(/0)来判断长度,所以接受不收信任的指针的时候不安全,strnlen的第二个参数用于指定最大长度

wcslen()和wcsnlen()为strlen()和strnlen()的长字符版本,用于计算wchar_t*类型的字符串长度

2.strcat()

拼接两个以空字符结尾的字符串

cout<<strcat(str3,str1)<<endl;          //直接拼接,有风险(如找不到str3的空字符串或者超过缓冲区)

errno_t error=strcat_s(str3,50,str1);    //strcat_s为安全版本,第二个为最大缓冲区大小,返回值为errno_t类型,注意要#include <cerrno>

if(error==0)    //0表示成功

cout<<"string joined successfully:"<<str3<<endl;

else if(error==EINVAL)    //EINVAL表示源或目的指向null

cout<<"error!source or destination string is null."<<endl;

else if(error==ERANGE)   //缓冲区超出

cout<<"error!destination string is too small."<<endl;

结果:

thank youmy name is clc

string joined successfully:thank youmy name is clcmy name is clc

说明:

strcat_s()、wcscat_s()、strncat_s()、wcsncat_s()为安全的替换方法

3.strcpy()

从原位置复制到目的位置

cout<<strcpy(str3,str1)<<endl;

errno_t error=strcpy_s(str3,50,str1);

if(error==0)

cout<<"string copyed successfully:"<<str3<<endl;

else if(error==EINVAL)

cout<<"error!source or destination string is null."<<endl;

else if(error==ERANGE)

cout<<"error!destination string is too small."<<endl;

结果:

string copyed successfully:my name is clc

说明:

strcpy_s()和wcscpy_s()分别是strcpy()和wcscpy()的安全版

4.strcmp()

比较两个以空字符结尾的字符串的长度

int result=strcmp(str1,str2);

if(result<0)

cout<<str1<<" is less than "<<str2<<"."<<endl;

else if(0==result)

cout<<str1<<" is equal to "<<str2<<"."<<endl;

else

cout<<str1<<" is greater than "<<str2<<"."<<endl;

结果:

my name is clc is greater than  hello world.

说明:

wcscmp()是strcmp()的宽字符版

5.strspn()

搜索字符串中不包含在给定集合中的第一个字符

char* str="I agree with everything.";

char* vowels="aeiouAEIOU ";             //注意有个空格

int index=strspn(str,vowels);

cout<<"the first character that is not a vowel is '"<<str[index]<<"' at position "<<index<<endl;

结果:

the first character that is not a vowel is 'g' at position 3

说明:

wcsspn()为strspn()的宽字符版

6.strstr()

返回一个指针,指向第一个参数中第二个参数指定的子字符串的位置

char* str4="I agree with everything.";

char* str5="ever";

char* psubstr=strstr(str4,str5);

if(!psubstr)

cout<<"not found"<<endl;

else

cout<<str5<<" is at position "<<psubstr-str4<<" of "<<str4<<endl;

结果:

ever is at position 13 of I agree with everything.

C++/CLI编程

C++/CLI维护独立于本地C++的内存堆,自动进行垃圾回收(不用delete),自动压缩堆的内存区域(消除了碎片,但是堆中变量对象的地址可能发生变化)

因为堆中变量对象的地址可能发生变化,所以用跟踪句柄(简称句柄)代替本地C++的指针,跟踪引用代替本地C++的应用

跟踪句柄

堆的垃圾回收(garbage collection,gc)自动更新跟踪句柄所包含的地址,跟踪句柄不能进行本地C++指针那样的算术操作,也不能强制转换跟踪句柄

所有属于引用类类型的对象都存储在堆中,引用这些对象儿创建的变量都必须是跟踪句柄(如String类属于引用类类型,因此引用String对象的变量必须是跟踪句柄)

数值类类型使用的内存默认在栈上分配,但可以使用gcnew操作将数值存储在堆上

在CLR堆上分配的对象,包括所有CLR引用类型,都不能在全局作用域中声明

String^ saying(L"hello world");    //声明String引用类类型的句柄并且初始化,也可以用nullptr初始化

int^ value=99;    //创建int^类型的句柄,并将堆内该句柄指向的值初始化为99

int^ result(nullptr);

result=2*(*value)+5;   //注意句柄不能直接参与算数运算(指针可以),必须用*解除引用,然而左边使用句柄可以不用显式解除引用,编译器自动解除

*result=2*(*value)+5;  //和上等价

CLR数组

array<int>^ data=gcnew array<int> (100);   //声明数组变量(一个跟踪句柄)并且创建CLR数组

data[0]=0;

array<int>^ values={3,5,6};                             //声明数组变量并且初始化

array<String^>^ names={"hello world","thank you"};      //数组类型为字符串类型(为引用类类型所以要String^)

array<String^>^ names;

names=gcnew array<String^>{"hello world","thank you"};    //如果声明数组变量时没有初始化或者创建CLR数组,那么之后要使用必须显式创建

array<String^>^ names;

names=gcnew array<String^>(2);

names[0]="hello world";

names[1]="thank you";

清除数组

Array::Clear(data,0,data->Length);     //所有int元素都被设置为0

Array::Clear(str,0,str->Length);     //所有String^元素都被设置为nullptr

例:生成随机数,寻找最大值

array<double>^ data=gcnew array<double>(50);

Random^ generator=gcnew Random;       //Random对象具有一些生成伪随机数的函数

double max(0);

for(int i=0;i<data->Length;i++)

{

data[i]=100*generator->NextDouble();  //生成伪随机数,Next()为生成整数的随机数,一个参数返回小于给定参数的随机非负数,两个参数返回之间的随机整数

Console::Write("{0,6:F2}",data[i]);

if(!((i+1)%5))

Console::WriteLine();

}

for each(double sample in data)   //for each循环依次处理数组中的元素

{

if(max<sample)

max=sample;

}

Console::WriteLine("the max digit is {0,6:F2}",max);

结果:

 51.10 63.24 16.45 60.89  4.91

 11.16 44.22 83.01 22.77  8.79

  8.12 76.74 85.09 48.53 38.54

  0.11 17.28 77.86 91.16 59.33

  6.61 85.89 86.50 83.99 60.40

 58.79 80.91 35.31 39.55 32.12

 91.96 76.91 83.30 13.15 47.29

 37.33 30.24 12.34 40.78 43.04

 26.02 28.07 86.95 35.20 13.00

 92.04 39.87 60.36 15.29 64.24

the max digit is  92.04

一维数组的排列

Array::Sort()函数

Array::Sort(data);       //对数组data进行升序排列

Array::Sort(data,2,3);  //从索引2开始(第三个元素),对之后的三个元素进行排序(第3~5元素)

例:

array<wchar_t>^ letter={L'c',L'b',L'd',L'a',L'e'}; 

array<int>^ data={3,2,4,1,5};

Array::Sort(letter,data);                    //对数组letter进行排序,data的对应位置也跟着变化

for(int i=0;i<letter->Length;i++)

{

Console::Write("{0,3}",letter[i]);

}

Console::WriteLine();

for(int i=0;i<data->Length;i++)

{

Console::Write("{0,3}",data[i]);

}

Console::WriteLine();

结果:

  a  b  c  d  e

  1  2  3  4  5

一维数组的搜索

Array::BinarySearch()函数

int position=Array::BinarySearch(values,start,x,toBeFound);

从value数组中的start索引开始,往后x个元素个数,找toBeFound元素,返回找到的索引位置

如果没有start和x则在整个value数组中查询toBeFound元素

索引位置(position)为第一个大于搜索目标元素索引位置(start+x)的按位补码(~position即等于start+x),如果没有范围(即搜索的是整个数组)则该整数是数组长度(Length)的按位补码

CLR多维数组

CLR二维数组是真正的二维数组,而本地C++的二位数组为数组的数组,因此不能对CLR二维数组使用单一的索引值

int nrows(4);

int ncols(5);

array<int,2>^ data=gcnew array<int,2>(nrows,ncols);   //声明变量并创建CLR二维数组(未赋值元素为0),2代表二维数组 

data[0][1]=1;

array<int,2>^ data={{1,2,3},{4,5}};        //声明变量并初始化(没初始化的部分为0)

Console::WriteLine("{0}  {1}",data[0,0],data[1,2]);   //输出为1  0

数组的数组

array<>的<>中的元素类型可以是任何类型,所以也可以是引用数组的跟踪句柄

array<array<String^>^>^ str=gcnew array<array<String^>^>   //array<String^>^为引用数组的跟踪句柄

{

gcnew array<String^>{"hello world","thank you"},

gcnew array<String^>{"hello world1","thank you1","clc"},

gcnew array<String^>{"hello world2","thank you2","clc2","good"}

};

int lineNum(0);

for each(array<String^>^ line in str)

{

lineNum++;

Console::WriteLine("this is line {0}:",lineNum);

for each(String^ element in line)

Console::Write("{0,20}",element);

Console::WriteLine();

}

/*for(int i=0;i<str->Length;i++)               //和上面的输出模块同

{

lineNum++;

Console::WriteLine("this is line {0}:",lineNum);

for (int j=0;j<str[i]->Length;j++)

Console::Write("{0,20}",str[i][j]);

Console::WriteLine();

}*/

结果:

this is line 1:

         hello world           thank you

this is line 2:

        hello world1          thank you1                 clc

this is line 3:

        hello world2          thank you2                clc2                good

字符串

String类表示由System::Char类型的字符序列组成的字符串

String类的对象固定不变,不能被修改

String^ str(L"hello world");   #创建String类对象

str->Length为str对象的长度

str[2]表示str中第三个System::Char类型的字符

连接字符串