C++程序设计教程 (钱能)第三章 编程练习

【1】打印浮点数的位码
float f = 19.2f;
int * pa = (int*)&f;
for (int i = 31; i >= 0; i--) {
	cout << (*pa >> i & i) << (i == 31||i== 23 ? "-" : " ");
}
cout << "\n";
【2】C-串比较的错误方式

cout << ("join" == "join" ? " " : "not ") << "equal\n";
//暂时不清楚,为啥是equal?

char * str1 = "good";
char * str2 = "good";
cout <<(void*)str1 << endl;
cout <<(void*)str2 << endl;
cout << (str1 == str2 ? " " : "not ") << "equal\n";
//以 str1 的形式打印 ,本应打印所指字符串的首地址,但由于对cout进行重载,故打印字符指针所指向的内容“good”;
//以&str1 的形式打印 ,得到该字符指针的地址,而不是所指字符串的首地址。
//将字符指针强制类型转换为 void * ,使cout输出符不认为它是一个字符指针。

char buffer1[6] = "Hello";
char buffer2[6] = "Hello";
cout << &buffer1 << endl;
cout << &buffer2 << endl;
cout << (buffer1 == buffer2 ? " " : "not ") << "equal\n";
//以 buffer1 的形式打印 ,得到数组的内容“Hello”;
//以&buffer1 的形式打印 ,得到两个数组的地址。
//由此可看出两个数组的地址不同,故结果是not equal;

运行结果:
equal
00A18B50
00A18B50
equal
0098FDE4
0098FDD4
not equal
分析:
        对Pointer对象来说,两个字符指针并没有分配相应的存储区,是后面的abc以常量的形式存储在常量区,然后把首地址赋给Pointer对象,所以Pointer对象存储的地址应该来说是一样的,所以结果是equal。
        而对Array对象来说,是运行时在栈空间上分配的内存,所以每个对象都是单独去申请内存,各自保存一份自己的Hello,所以Array对象存储的地址不一样,所以结果是not equal。

参考链接:https://blog.csdn.net/honyniu/article/details/51323255
【3】C-串操作

C-串复制问题

char * str1 = "Hello";
char * str2 = str1;
//意味着str1与str2共享“Hello”空间。

char arr1[6] = "Hello";
char arr2[6] = arr1;
//数组不能复制
char * s1 = "Hello ";//注意Hello后面有空格
char * s2 = "123";
char a[20];
//复制
strcpy_s(a, s1);	
//比较
cout << (strcmp(a, s1) == 0 ? " " : "not") << "equal\n";	
//连接
strcat_s(a, s2);  //strcat_s的值是bool型,所以要查看操作结果,就要打印数组a。
cout << a << endl;
//倒置
cout << _strrev(a) << endl;	
//设置
_strset_s(a, 'c');//strcat_s的值是bool型,所以要查看操作结果,就要打印数组a。
cout << a << endl;	
//查找串
cout << (strstr(s1, "ell") ? " " : "not") << " found\n";	
//查找字符
cout << (strchr(s1, 'c') ? " " : "not") << " found\n" << endl;		

运行结果:
equal
Hello 123
321 olleH
ccccccccc
found
not found

注意: strcmp是比较C-串的字典序大小。

【4】string操作
string a, s1 = "Hello ";
string s2 = "123";
//复制
a = s1;
//比较
cout << (a == s1 ? " " : "not") << "equal\n";
//连接
a += s2;
cout << a << endl;
//倒置
reverse(a.begin(), a.end());
cout << a << endl;
//设置
cout << a.replace(0, 9, 9, 'c') << endl;;
//查找子串
cout << (s1.find("ell") != -1 ? " " : "not") << " found\n";
//查找字符
cout << (s1.find('c') != -1 ? " " : "not") << " found\n";

运行结果:
equal
Hello 123
321 olleH
ccccccccc
found
not found


注意:

  • string类型自身没有字串的逆反操作,但通过C++的STL库中的reverse函数,便可以实现字串的逆反。
  • reverse函数的两个参数以一头一尾的形式描述一个容器的一个区间,其功能是将该容器中一头一尾区间内的所有元素颠倒位置。
  • string实体也是一种容器,其一头一尾的标准表示就是对其实体作begin()和end()操作。reverse操作不返回颠倒位置后的内容结果,所以为了查看颠倒结果,要独立输出a。
  • replace(0, 9, 9, ‘c’)表示a字串从下标0开始,长度是9的子串用9个‘c’字符代替。

【5】string实体的8种定义方式

string的构造函数
a) string s; //生成一个空字符串s
b) string s(str) ;//拷贝构造函数 生成str的复制品
c) string s(num, c); //生成一个字符串,包含num个c字符
d) string s(str, len) ;//将字符串str的前len个字符当作字符串的初值
e) string s(str, stridx, strlen) ;//将字符串str内"始于stridx且长度顶多strlen"的部分作为字符串的初值
f) string s(cstr) ;//将cstr字符串作为s的初值
g) string s(chars, chars_len); //将C字符串前chars_len个字符作为字符串s的初值
h) string s(beg, end) ;//以区间beg;end(不包含end)内的字符作为字符串s的初值

string s1 = "Hello", t;  //第一种
string s2("C++");//第二种
string s3(15, 'H');//第三种
string s4("abcdefg", 7);//第四种
string s5("abcdefg", 2, 3);//第五种
char * cstr = "zhongguojiayou";
string s6(cstr);//第六种
string s7(cstr, 8);//第七种
string str = "jingshuiliushen";
string s8(str.begin(),str.end());//第八种

cout << "s1:" << s1 << endl;
cout << "s2:" << s2 << endl;
cout << "s3:" << s3 << endl;
cout << "s4:" << s4 << endl;
cout << "s5:" << s5 << endl;
cout << "s6:" << s6 << endl;
cout << "s7:" << s7 << endl;
cout << "s8:" << s8 << endl;

运行结果:
s1:Hello
s2:C++
s3:HHHHHHHHHHHHHHH
s4:abcdefg
s5:cde
s6:zhongguojiayou
s7:zhongguo
s8:jingshuiliushen


【6】完成下列字符序列的输入,并显示。

          Hello,How are you?

//string版
for (string s; cin >> s; ) {
	cout << s << " ";
}
cout << endl;

//C-串版
for (char a[10]; cin >> a; ) {
	cout << a << " ";
}
cout << endl;

//string的全局函数getline,一次性输入
string s;
getline(cin, s);
cout << s << endl;

//cin.getline,一次性输入
char a[40];
cin.getline(a, 5);
cout << a << endl;

//逐个字符输入
for (char ch; (ch = cin.get()) != '\n';) {
	cout << ch;
}
cout << endl;

string的getline与cin.getline的区别:

string类中的getline(),为全局函数,默认结束符为回车符。
格式:getline(cin,str,char);
cin是进行读入操作的输入流,str存储读入的内容,char是指定的结束符。

cin.getline() 针对数组字符串,以指定的地址存放第一个读取的字符, 依次向后存放读取的字符,直到读满N-1个或遇到指定的结束符为止。一次读取多个字符,包括空白字符。
格式: cin.getline(char *,int N,char)
char *是字符指针,int是字符个数,char是指定的结束符。

注意: cin>>总是将前导的空格(空格、回车、水平或垂直制表符等)滤掉,将单词读入,当遇到空格时结束本次输入。

【7】输出文件"aaa.txt"中每行的整数和。
//整行读入再分解读入
ifstream in("aaa.txt");
for (string s; getline(in, s);) {
	int a, sum = 0;
	for (istringstream sin(s); sin >> a; sum += a);
	cout << sum << endl;
}

aaa.txt
12 3 45 67 8 9
56 232 12 23
12 1
8
1212 2312

运行结果:
144
323
13
8
3524

说明:
        istringstream是输入string流,它在sstream资源中说明。该语句类似文件流操作,不过创建sin流时,参数为string对象。它将string实体看作是一个输入流,sin>>a即是从string流中输入整数到a中,一直输到string中的最后一个整数。

【8】数组

定义: 类型名 数组名[常量表达式];

1)常量表达式的值只要是整数或整数子集就行。如:
int a [‘a’];//表示int a [97];
2)数组定义是具有编译确定意义的操作,它分配固定大小的空间。因此元素个数必须是由编译时就能够定夺的常量表达式。

int n = 100;
int a[n];//错:数组元素个数必须是常量

      根据上下文,编译似乎已经知道n的值,但编译动作因变量性质而完全不同。变量性质就是具有空间占用的可访问实体,编译每次碰到一个变量名称就对应一个访问空间的操作。
      因此,int a[n]实际上要在运行时,才能读取变量n的值,才能确定其空间大小。这与数组定义的编译时确定意义的要求相违背,因而编译时报错。

const int n = 100;
int a[n];

      这样定义是允许的。因为编译中,常量虽也占空间,甚至也涉及内存访问,但因数据确定,而可以被编译用常数替代。
      事实上,常量在编译时经常是优化的目标,能省略内存空间访问就应该省略。

初始化
定义时初始化

int arr1[10] = { 0,1,2,3,4,5,6,7,8,9 };

初始值不能通过逗号的方式省略

int arr2[6] = { 0,1,2,3,4};

初始值也不能为空

int arr3[6] = {};

但在总体上,初始值可以少于数组定义的元素个数,后面元素全补0。

int arr4[6] = { 1,2,3 };
for (int i = 0; i < 6; i++) {
      cout << arr4[i] <<" ";
}
//1 2 3 0 0 0

省略数组定义中方括号内的表达式

int arr5[5] = { 1,2,3,4,5 };
for (int i = 0; i < sizeof(arr5) / sizeof(arr5[0]); i++) {
      cout << arr5[i] << " ";
}
cout << “\n”;
//1 2 3 4 5
//sizeof(arr5):arr5数组的字节数。
// sizeof(arr5[0]):第一个元素所占空间字节数。
// sizeof(arr5) / sizeof(arr5[0]):数组元素个数

字符数组的三种初始化形式

char chls1[6] = { "hello" };
//缺少C-串结束符,不能用数组名做C-串操作
char chls2[5] = { 'h','e','l','l','o' };
char chls3[6] = "hello";
cout << chls1 << endl;
cout << chls2 << endl;
cout << chls3 << endl;
//运行结果:
//hello
//hello烫烫烫烫烫蘦ello
//hello

默认值
全局数组和静态数组,元素总被清0;
局部数组,它们的值总是不确定的。

int arr1[5] = { 1,2,3 };
int arr2[5];
int main() {
	int arr3[5] = { 2 };
	int arr4[5];
	cout << "arr1:";
	for (int i = 0; i < 5; ++i)
		cout << arr1[i] << " ";
	
	cout << "\narr2:";
	for (int i = 0; i < 5; ++i)
		cout << arr2[i] << " ";

	cout << "\narr3:";
	for (int i = 0; i < 5; ++i)
		cout << arr3[i] << " ";

	cout << "\narr4:";
	for (int i = 0; i < 5; ++i)
		cout << arr4[i] << " ";
	cout << endl;
}

//运行结果:
//arr1:1 2 3 0 0
//arr2:0 0 0 0 0
//arr3:2 0 0 0 0
//arr4: - 858993460 - 858993460 - 858993460 - 858993460 - 858993460

注意:数组的初始化方式源于程序运行的空间布局,局部数组随函数调用而创立,全局数组在整个程序运行中起作用,驻于全局数据区,在运行起始时而被初始化为0。

二维数组

//依次初始化全部六个元素,最后一个默认为0
int arr1[2][3] = { 1,2,3,4,5 };
//2行3列,分别初始化
int arr2[2][3] = { {1,2},{4} };
cout << "arr1:";
for (int i = 0; i < 2; i++) {
	for (int j = 0; j < 3; j++) {
		cout << arr1[i][j] << " ";
	}
}
cout << "\narr1:";
for (int i = 0; i < 2; i++) {
	for (int j = 0; j < 3; j++) {
		cout << arr2[i][j] << " ";
	}
}
//运行结果:
//arr1:1 2 3 4 5 0
//arr1:1 2 0 4 0 0

【9】向量
//vector的四种定义方式
vector<int> a(10);
vector<int> b(10,1);
vector<int> c(b);
//b[0]-b[2]共三个元素
vector<int> d(b.begin(), b.begin()+3);
//b.begin() = b[0], b.end() = b[length]
vector<int> e(b.begin(), b.end());

//向量从数组中获得初值
int a1[7] = { 1,2,5,3,7,9,8 };
vector<int> va(a1,a1+7);// 实际是[a1,a1+7)
cout << "向量从数组中获得初值:";
for (int i = 0; i < va.size(); i++) { 
	cout << va[i] << " ";
}
cout << endl;
//向量从数组中获得初值:1 2 5 3 7 9 8

vector <int>c(&a1[1], &a1[6]);//实际是[a1[1],a1[6])
//向量c的初值为:2 5 3 7 9

//向量的两种遍历方式
cout << "向量下标访问方式:";
for (int i = 0; i < b.size(); i++) {
	cout << b[i] << " ";
}
cout << "\n向量的迭代器访问:";
for (vector<int>::iterator it = b.begin(); it < b.end(); it++) {
	cout << *it << " ";
}
cout << endl;
//运行结果:
//向量下标访问方式:1 1 1 1 1 1 1 1 1 1
//向量的迭代器访问:1 1 1 1 1 1 1 1 1 1

向量的常用操作

int a1[7] = { 1,2,5,3,7,9,8 };
vector<int> va(a1,a1+7);//va为1 2 5 3 7 9 8
vector<int> a(10);//每个元素的默认值为0
//va向量的0-2元素构成向量赋给a
a.assign(va.begin(), va.begin() + 3);//1 2 5		
//使a向量只含0-3元素,且赋值为2
a.assign(4, 2);//2 2 2 2	
int x = a.front();//取a向量的第一个元素1
int y = a.back();//取a向量的最后一个元素5
a.pop_back();//删除向量a末尾元素5
a.push_back(48);//插入元素48到末尾
a.resize(5);//将向量元素个数调至5,少则删,多则补,增补元素值默认为0
a.resize(5,64);//将向量元素个数调至5,少则删,多则补,增补元素值为64
if (a == b)//向量比较操作,还有!= < <= > >=
	cout << "Equal!" << endl;
a.clear();//清空向量a
if (a.empty())//判断向量是否为空
	cout << "Empty!" << endl;

添加元素
【向量操作】读入文件aaa.txt的数据到向量中,文件中为一些整数,判断向量中的元素有多少个俩俩相等的数对。

aaa.txt内容
12 3 45 67 8 9
56 232 12 23
12 1
 8
1212 2312

ifstream in("aaa.txt");
vector<int> s;
for (int a; in >> a; ) {
	s.push_back(a);
}
int pair = 0;
//每次循环时,j从i开始,找到最后。
//所以j最大可取i最大可取 s.size-2, s.size()-1
for (int i = 0; i < s.size() - 1; i++) {
	for (int j = i + 1; j < s.size(); j++) {
		if (s[i] == s[j])
			pair++;
	}
}
cout << pair << "\n";

二维向量
将文件aaa.txt中的行看成是一个向量,再将整个文件看成是一组向量,可以得到二维向量。设计一个排序程序,使得按从短到长的顺序输出每个向量。

typedef vector<vector<int>>Mat;//二维向量
Mat input();
void mySort(Mat& a);
void print(const Mat& a);

int main() {
	Mat a = input();
	mySort(a);
	print(a);
}

Mat input() {
	ifstream in("aaa.txt");
	Mat a;
	for (string s; getline(in, s); ) {
		vector <int> b;
		istringstream sin(s);
		for (int ia; sin >> ia; ) {
			b.push_back(ia);
		}
		a.push_back(b);
	}
	return a;
}

void mySort(Mat& a) {
	//冒泡排序(稳定):排序n个数,共进行n-1趟,每趟比较n-i次。
	for (int i = 1; i < a.size(); i++) {
		for (int j = 0; j < a.size() - i;j++) {
			if (a[j].size() > a[j + 1].size())
				a[j].swap(a[j + 1]);

		}
	}
}

void print(const Mat& a) {
	for (int i = 0; i < a.size(); i++) {
		for (int j = 0; j < a[i].size(); j++) {
			cout << a[i][j] << " ";
		}
		cout << endl;
	}
	
}

//运行结果:
//	8
//	12 1
//	1212 2312
//	56 232 12 23
//	12 3 45 67 8 9
//	因为冒泡排序是稳定的,所以文件中“12 1 ”在
//  “1212 2312”的前面,排序后顺序不变。

【10】指针

1)定义格式:数据类型 * 指针变量名;
2)一个 * 只能修饰一个指针;
3)指针定义中的*可以居左、居中、居右。

空间实体的理解

//空间实体的理解
float f = 34.5;
int * ip = reinterpret_cast<int*>(&f);
//reinterpret_cast<int*>是重解释转换。
cout << "float address:" << &f << " => " << f << endl;
cout << "int address:" << ip << "=>" << *ip << endl;
*ip = 100;
cout << "int:" << *ip << endl;
cout << "float:" << f << endl;

/*	运行结果:
	float address:00B3F88C => 34.5
	int address:00B3F88C=>1107951616
	int:100
	float:1.4013e-43
*/

指针运算
指针值表示一个内存地址,因此它内部表示为整数。指针变量所占的空间大小总是等同于整型变量的大小。
数组名本身就是表示元素类型的地址,所以可以直接将数组名赋给指针。

/*
1)指针值表示一个内存地址,因此它内部表示为整数。
2)指针变量所占的空间大小总是等同于整型变量的大小。
3)数组名本身就是表示元素类型的地址,故可直接将数组名赋给指针。
4)指针的增减是以该类型的实体大小为单位的。
*/

int arr[6];
for (int i = 0; i < 6; i++) {
	arr[i] = i * 2;
}
for (int * ip = arr; ip < arr + 6; ip++) {
	//注意:ip每次加1,元素地址以4递增。
	cout << ip << ":" << *ip << endl;
}

/* 运行结果:
	00BCF7C4:0
	00BCF7C8:2
	00BCF7CC:4
	00BCF7D0:6
	00BCF7D4:8
	00BCF7D8:10
*/

指针限定

/*
指针常量:指针值(地址)不能修改的指针。
常量指针:指向常量的指针。
区分:看const修饰,若const修饰指针本身,则为指针常量;若修饰的是指针类型(指向的实体类型),则为常量指针。
1)指针常量不能修改指针值;
2)常量指针不能修改指向的实体;
3)常量指针常量不能修改指针值和指向的实体。
4)但读取访问总是可以的。
5)const修饰只是限定指针的操作,但不能限定空间上的实体的可改变性。
因为一个实体可能被不止一个变量所关联,所以实体被其他关联变量的改变是可能的。
*/

int a = 100,c = 200;
const int* ip = &a;
const int*const icp = &c;
a = 1000;
c = 2000;
cout << "ip=>" << *ip << endl;
cout << "icp=>" << *icp << endl;

/*运行结果:
	ip=>1000
	icp=>2000
*/
【11】引用
/*	
1)引用是个别名(alias);
2)引用必须初始化;
3)修改引用值就是修改实体值,就是修改对应的变量值;
4)引用与指针的区别:指针可操纵指针值和指向的值;而引用只可以操作指向的值;
5)引用一旦诞生,就确定了它与一个实体的关系,这种关系直到引用自身灭亡才会消失;也就是说引用的地址永不改变;
6)引用不能操作自身的地址值;
7)引用也可以限定,可阻止引用做写操作,但不妨碍实体值可能被修改;
8)高级编程多用引用,低级编程多用指针,主要着眼于安全因素。
*/

//引用也可以限定
int int2 = 5;
const int &r = int2;
int2 = 49;
cout << "int2 = " << r << endl;
//运行结果:int2 = 49

//引用及其地址
int int1 = 5;
int &rInt = int1;
cout << "&int1:" << &int1 << " int1:" << int1 << endl;
cout << "&rInt:" << &rInt << " rInt:" << rInt << endl;
int int2 = 8;
rInt = int2;
cout << "&rInt:" << &rInt << " rInt:" << rInt << endl;

/*
运行结果:
	&int1:00AFF90C int1:5
	&rInt:00AFF90C rInt:5
	&rInt:00AFF90C rInt:8
*/
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值