1、char[] 数组
char * str = "01234";
str存储时需要6个字节,包括末尾的’\0’。
2、strcpy
库函数strcoy拷贝字符串时,从源地址一直向后拷贝,直至遇见末尾的’\0’为止。没有遇到结束符导致越界访问,程序崩溃。
3、实现strcpy函数
//返回值为目的地址,方便链式操作;形参加const表明输入
char * strcpy(char *strDest, const char *strSrc)
{
//对两个地址加非0断言
assert((strDest != nullptr)&&(strSrc != nullptr));
char * address = strDest;
while((*strDest++ = *strSrc++) != '\0');
return address
}
4、局部变量和文字静态区
如下代码会有什么问题:
char *GetMemory( void )
{
char p[] = "hello world";
return p;
}
void Test( void )
{
char *str = NULL;
str = GetMemory();
printf( str );
}
存在问题:
(1)函数生成一个局部变量p,存储在栈中,指向字面常量的一个拷贝副本的栈地址。字面常量存在静态存储区,生命周期为整个程序执行期。函数结束,p被回收,副本消失。p返回的地址,指向栈存储区被取消标记,随时被系统修改。因此p指向的内容不确定。
(2)返回p时,会产生一个临时变量,把p的值传递到临时变量,同时p被回收。因此可以修改为
char *p = "hello world";
让p直接指向静态区。这个地址不会变。因此返回值可用。
5、Getmemory
如下代码存在哪些问题?
void GetMemory( char **p, int num )
{
*p = (char *) malloc( num );
}
void Test( void )
{
char *str = NULL;
GetMemory( &str, 100 );
strcpy( str, "hello" );
printf( str );
}
(1)应加申请内存失败判断
if(*p == nullptr || num < 0)
{
perror("malloc fail!");
return -1;
}
(2)应释放内存
free(str);
//避免野指针
str = nullptr;
(3)printf(str)可能发生格式化字符串攻击,写全了。
printf("%s", str);
6、局部野指针
如下代码有设么错误?
void swap( int* p1,int* p2 )
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
p是一个未初始化的野指针,有可能指向系统区,导致程序崩溃。改为
int *p = nullptr;
也是错误的,有可能指向有意义区域,破坏数据。正确改为:
void swap( int* p1,int* p2 )
{
int p = 0;
p = *p1;
*p1 = *p2;
*p2 = p;
}
7、BOOL、int、float、指针和“零值”的比较
//BOOL
if(var == flase)
//int,这样写如果将==错写成=,错误提示
if(0 == var)
//float,不能和0直接比
const float Epsilon = 1e-5;
if((var >= -Epsilon) && (var <= Epsilon))
//指针
if(var == nullptr)
8、数组名作为形参的退化
有以下代码计算sizeof的值
void Func ( char str[100] )
{
sizeof( str ) = ?
}
void *p = malloc( 100 );
sizeof ( p ) = ?
(1)数组名作为函数形参,在函数体内退化为普通指针。可修改,可++/–操作。故sizeof( str ) = 4。
(2)指针长度为4字节,故sizeof ( p ) = 4。
(3)正常情况下如
char str[10];
//sizeof(str)为10。
数组名可转化为指针,指向内存中数组首地址。此时为指针常量,其保存的地址不可被修改。因此不能做自增减操作,不可被修改。
8、关于宏
写一个标准宏MIN, 输入两个参数返回较小的一个。
least = MIN(*p++, b); 会有什么问题?
宏定义可实现类似函数功能,但终归不是函数,宏展开对参数进行老老实实的替换。
(1)老老实实的将宏定义的参数和整个宏,加括号防止问题。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
(2)防止宏的副作用。上述表达式原原本本替换后,会导致指针p作自增运算。
((*p++) <= (b) ? (*p++) : (b))
(3)宏定义后面不能加分号,否则被替换进去。
想起大一时邓凡老师出了道题目,全体阵亡。就是宏的题目,至今记忆尤深。
define A 32+3
a = A * 3
问a的值。大家自然一看简单,不就是35*3=105吗?但结果应该是32+3*3=41。原因:宏是老老实实替换。
9、标准头文件
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
(1)为确保各个文件中类的定义一致,类通常定义在头文件中。谁要用包含下该头文件即可。如果两个都包含,类可能会被重新定义2次。确保头文件被多次包含仍能安全工作的常用技术就是预处理器。
(2)常用的#include就是预处理功能,在编译前将制定头文件内容替换。另一个预处理功能是头文件保护符。依赖于预处理变量,一种是已定义,另一种是未定义。#define把一个名字设定为预处理变量。#ifdef当且仅当该变量已定义时为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,执行后续操作直至遇到#endif指令为止。预处理变量无视c++作用域的规则。有效防止重复包含。预处理变量必须唯一,通常将头文件的类名构建名字,全部大写。
(3)extern “c”
cpp文件编译器会自己定义__cplusplus宏。extern表示函数或变量,可以在本模块和其他模块使用。
为了在c++代码中使用c代码,此声明可让编译器按照c语言编译规则来编译代码。为什么这样讲?
c++支持重载,编译会将真实函数名+函数参数。c不支持重载使用原函数名。这样就找不见在别的模块中使用c规则编译的此函数的代码,报链接错误。
10、将char数组内容循环右移n个。
void loop(char *p, int n)
{
//strlen计算到'\0'
int len = strlen(p);
if (len == 0 || n < 0)
return;
//避免循坏移动
n = n % len;
char *tmp = new char[len];
//后移
memcpy(tmp+n, p, (len-n));
//出来的补到前头
memcpy(tmp, p+len-n, n);
//全部拷贝给原串
memcpy(p, tmp, len);
delete[] tmp;
}