1 第 7 第 C++ 第第 第 8 第第第!
Jan 27, 2016
1
第 7 章 C++ 指针
第 8 次见面!
2
学习新内容了,准备好了吗?
记住 :
动态分配• 在 C++ 语言中,每个程序需要用到几个变量,
在写程序前就应该知道。每个数组有几个元素也必须在写程序时就决定。
• 有时我们并不知道我们需要多大的数组元素直到程序开始运行。因此希望能在程序中根据某一个当前运行值来决定数组的大小。如设计一个打印魔阵的程序,我们希望先输入魔阵的阶数,然后根据阶数定义一个矩阵
动态分配方法
• 这些问题的解决方案就是内存的动态分配。我们定义一个指针,并让它指向一个合适的内存。如:
int *scores; scores = 内存的起始地址;
动态内存分配与回收• 用C语言编程,动态分配是通过系统函数malloc(),free() 和运算符 sizeof 来实现动态分配
• C++中由 new 和 delete 两个运算符替代- 运算符 new 用于进行内存分配: 申请动态变量: p = new type; 申请动态数组: p = new type[size];
申请动态变量并初始化: p = new type( 初值 ) ; - 运算符 delete 释放 new 分配的内存:
释放动态变量: delete p;
释放动态数组: delete [] p;
动态内存分配与回收
// 为简单变量动态分配内存,并作初始化int main()
{ int *p;
p = new int(99);
// 动态分配内存,并将 99 作为初始化值赋给它 cout<< *p; delete p;
return 0;
}
动态内存分配与回收// 动态字符串的使用int main(){int *p; char *q;
p = new int(5); q = new char[10]; strcpy(q, "abcde"); cout << *p << endl; cout << q << endl; delete p; delete q; return 0;}
输出结果:5
abcde
动态分配的检查
• new 操作的结果是申请到的空间的地址
• 当系统空间用完时, new 操作可能失败
• New 操作失败时,返回空指针
动态内存分配与回收// 动态分配检查int main(){int *p; p = new int; if(!p) { cout << "allocation failure\n"; return 1;} *p = 20; cout << *p; delete p; return 0;}
assert 宏
• assert ()宏在标准头文件 cassert 中 • assert() 有一个参数,表示断言为真的表
达式,预处理器产生测试该断言的代码。如果断言不是真,则在发出一个错误消息后程序会终止。
#include <iostream>#include <cassert> // 包含 assert 宏的头文
件using namespace std;
int main(){ int *p;
p = new int; assert (p != 0); //p 等于 0 ,则退出程序 *p=20; cout << *p; delete p;
return 0;}
内存分配的进一步介绍• 静态分配:对全局变量和静态变量,编译器为
它们分配空间,这些空间在整个程序运行期间都存在
• 自动分配:函数内的局部变量空间是分配在系统的栈工作区。当函数被调用时,空间被分配;当函数执行结束后,空间被释放
• 动态分配:在程序执行过程中需要新的存储空间时,可用动态分配的方法向系统申请新的空间,当不再使用时用显式的方法还给系统。这部分空间是从被称为堆的内存区域分配。
OS
Program
Heap 动态分配
Stack 自动分配
Globe variables
静态分配
内存泄漏• 动态变量是通过指针间接访问的。如果该指针被修改,这个区
域就被丢失了。堆管理器认为你在继续使用它们,但你不知道它们在哪里,这称为内存泄露。
• 为了避免出现孤立的区域,应该明白地告诉堆管理器这些区域不再使用。可以采用 delete 操作,它释放由 new 申请的内存。
• 当释放了内存区域,堆管理器重新收回这些区域,而指针仍然指向堆区域,但不能再使用指针指向的这些区域。
• 要确保在程序中同一个区域释放一次。• 释放内存对一些程序不重要,但对有些程序很重要。如果你的
程序要运行很长时间,而且存在内存泄漏,这样程序会耗尽所有内存,直至崩溃。
动态空间分配示例
• 输入一批数据,计算它们的和。数据个数在设计程序时尚无法确定。
• 存储一批数据应该用数组,但 C++ 语言的数组大小必须是固定的。该问题有两个解决方案:– 开设一个足够大的数组,每次运行时只使用一部分。缺点:浪费空间
– 用动态内存分配根据输入的数据量申请一个动态数组
#include <iostream>using namespace std;
int main(){ int *p, i, n, sum=0;
cout << "An array will be created dynamically.\n\n" ; cout << "Input an array size n followed by n integers:";
cin >> n;
if (!(p = new int [n])) exit(1); for (i=0; i<n; ++i) cin >> p[i]; for(i=0; i<n; ++i) sum += p[i];
delete [] p;
cout << "Number of elements:" << n << endl; cout << "Sum of the elements:" << sum << endl;
return 0;}
可改为:p = new int [n];
assert( p != NULL);
看看字符串的再讨论
• 真正的用处• 大数的处理
字符串再讨论• 字符串的另一种表示是定义一个指向字符的
指针。然后直接将一个字符串常量或字符串变量赋给它
• 如 char *String , ss[ ] =“abcdef”;
String = “abcde”; String = ss;
String
“abcde”
Program
OS
数据段或代码区
栈
堆
String = “abcde”; 的执行结果
•字符串常量存储在一
个称为数据段的内存区
域里
•将存储字符
串” abcde” 的内存的
首地址赋给指针变量
String。
String
“abcdef\0”
Program
OS
数据段
栈
堆
String = ssString = ss 的执行过程的执行过程
将字符数组 ss 的起始地址存入
String
String = new char[5];
strcpy(String, “aaa”)
String
Program
OS
数据段
栈
堆“aaa\0”
•动态变量存储在堆工
作区
•将存储字符
串” aaa” 的内存的首
地址赋给指针变量
String。
用指针表示的字符串的操作• 可以直接作为字符串操作函数的参数。但必须注意,如果该指针指向的是一个字符串常量时,则使用是受限的。如不能作为 strcpy 的第一个参数
• 由于在 C++ 中,数组名被解释成指向数组首地址的指针。因此,字符串是用一个指针变量表示,我们可以把此指针变量解释成数组的首地址,通过下标访问字符串中的字符。如string[3] 的值是 d 。
用指针处理串
• 目的:编写一个记录串中单词的个数的函数。
• 关键技术:要传递一个字符串给函数
字符串作为函数的参数• 字符串作为函数的参数和数组名作为参数传递
一样,可以有两种方法– 作为字符数组传递– 作为指向字符的指针传递
• 两种传递方式的本质是一样的,都是传递了字符串的首地址
• 字符串作为字符数组传递时不需要指定长度。因为字符串操作的结束是依据‘ \0’
#include <ctype>
Using namespace std;
int word_cnt(const char *s)
{ int cnt = 0;
while (*s != '\0')
{ while (isspace(*s)) ++s; // 跳过空白字符if (*s != '\0')
{ ++cnt; // 找到一个单词 while (!isspace(*s) && *s != '\0')
++s; // 跳过单词 }
}
return cnt;
}
统计字符串中单词数的函数
Eg. 试设计一个函数,计算两个 128位的整数的和,结果作为函数值返回
设计思想:用一个由数字组成的字符串来表示数据。如允许的最大长度为 128位,则字符数组的长度为 128。如数字 123,可表示为
127 … 5 4 3 2 1 0
‘0’ ‘0’ ‘0’ ‘0’ ‘0’ ‘1’ ‘2’ ‘3’
加法函数的设计void add(const char *p1, const char *p2, char *s)
{ int i, j = 0; /* j 为进位 */
for (i=0; i < 128; ++i)
{ s[i] = p1[i] - '0' + p2[i] - '0' + j;
if ( s[i] >= 10) j = s[i] / 10; else j = 0;
s[i] = s[i] % 10 + '0';
}
}
辅助函数
• 为了测试这个函数,设计了两个辅助函数• Set 函数将一个字符串存储为所设计的格式• Show 函数显示一个字符串表示的整型数
int set(const char *s1, char *s) { int i= strlen(s1) - 1, j = 0; while (i>= 0) { if (s1[i] > '9' || s1[i] < '0')
{cout << "set error\n"; return -1;} else {s[j] = s1[i]; --i; ++j;}
}
for ( ; j < 128; ++j ) s[j] = ‘0’; // 前置空格 return 0;}
void show(const char *s)
{ int i=127;
while (s[i] == '0') --i; // 跳过前置的 0
while (i>=0)
{ cout << s[i];
--i;
}
cout << endl;
}
测试程序的设计int main()
{char num1[128], num2[128], sum[128];
set("123", num1);
set("345", num2);
show(num1); show(num2);
add(num1, num2, sum);
show(sum);
return 0;
}
执行结果:
123
345
468
23/4/21 31
Primary Arithmetic(Primary Arithmetic( 初等算术) 初等算术) pku2562pku2562 , , nefunefu 也有也有
题目描述:题目描述:小学生在学多位数的加法时,是将两个数右对齐,然后从右往左一位一位地加。多位数的加法经常会有进位。如果对齐的位如果对齐的位相加结果大于或等于十就给左边一位进一相加结果大于或等于十就给左边一位进一。对小学生来说,进位的判断是比较难的。你的任务是:给定两个加数给定两个加数,统计进位统计进位
的次数的次数,从而帮助老师评估加法的难度。
23/4/21 32
输出描述:输出描述:对输入文件 ( 最后一行除外 ) 每一行的两个加数,计算它们进计算它们进行加法运算时进位的次数并输出行加法运算时进位的次数并输出。具体输出格式详见样例输出。
样例输入:样例输入:123 456555 555123 594
0 0
样例输出:样例输出:No carry operation.3 carry operations.1 carry operation.
输入描述:输入描述:输入文件中的每一行为两个无符号整数,少于少于 1010 位位。最后一
行位两个 0 ,表示输入结束。第二种输入方
式 !!!!!
23/4/21 33
分析• Int 型的上限是 2000000000 ,可以保存
9位整数,因此本题可以用整数来保存输入,每次把 a 和 b 分别模 10 ,就能获取他们的位数。
23/4/21 34
• long long a,b,c,sum;• while(cin>>a>>b)• {• if (a==0&&b==0) break;• c=0;sum=0;• for(int i=0;i<=9;i++)• {• if ((a%10+b%10+c)>=10)• {sum++;• c=1;• } • else• c=0;• a=a/10;b=b/10;• • }
23/4/21 35
计算 N! 72 题• 输入不超过 10000 的正整数 n, 计算 n! 的具体值?
• 输入: 30
• 输出:265252859812191058636308480000000
23/4/21 36
分析:• 10000!的有多大? 用计算器算 =5*1035658 ,
可以用 50000 个元素的数组 f 来保存,为了防止进位溢出,我们让 f[0] 保存结果的个位, f[1] 表示十位,等等。输出的时候,数组高位上的 0 要忽略。
• 例如: 11 ! =39916800• 则, 12 ! =11! *12 ,也就是 39916800 的
每位都乘 12 ,再进位,从低到高,所以数组要用 f[0] 表示低位,输出时再逆序输出!
23/4/21 37
先定义变量• const int maxn=50000;
• int n,c,k;
• int f[maxn+1];
• 如果不知道 10000!的阶乘有多少位,那肯定 WA!
23/4/21 38
• while(cin>>n)• {• • memset(f,0,sizeof(f));• f[0]=1;• for (int i=1;i<=n;i++)• {• c=0;// 代表进位• for(int j=0;j<=maxn;j++)• {• int s=f[j]*i+c; • f[j]=s%10;• c=s/10;• }• }
23/4/21 39
看输出部分• for(k=maxn;k>=0;k--)
• if (f[k]!=0) break;
• for(int j=k;j>=0;j--)
• cout<<f[j];
• cout<<endl;
• }
23/4/21 40
能不能再快点? 思考• 发现 f[i] 里面的值只有 1 个数字,但 f[i] 是
INT型啊,能存 9位数啊,利用一下。• int s=f[j]*i+c;
• f[j]=s%100000;//好好看看• c=s/100000;// 每 5位数字再进位• 这回数组 f 开 20000 就够了,时间也快了 ~
• 运行时间是 1778ms!
23/4/21 41
如何打印输出?• 12 ! =479001600 ,后 5 位 01600 ,输出是
1600 ,要处理好这个问题就行了!• for(k=maxn;k>=0;k--)
• if (f[k]!=0) break;
• printf("%d",f[k]);
• for(int j=k-1;j>=0;j--)
• printf(“%05d”,f[j]);//5位的数字,不足前面添 0
• printf("\n");
42
Welcome to HDOJ
Thank You ~
• 课后一定要练习之!
43