首先,标签(Tag)是什么?

  我的理解:用来具体区分某一类内容的标识,和标签类似的一个概念是分类(Category),有一个示例可以很好的区分它们两个,比如人类分为:白种人、黄种人和黑种人(可以看作分类),但又可以根据职业分为:农民、工人和程序猿等等(可以看作标签)。

  概括来说就是:分类是大而全的概念(用抽象概念来区分),标签是小而具体的概念(用具体值来区分)。

  在所有的 CMS 内容管理系统中(比如园子中的博客、博问、新闻、网摘等),都存在标签(Tag)的概念,而且它们大多都有相似的功能,对于这类问题,我们最好把它抽象出来,然后单独去考虑并探讨它,如果一切顺利的话,最后完成的东西就是标签领域(TagDomain),当然这是最理想的方式。

  我们先从实际应用出发,今天要探讨的是:各种标签(Tag)模型设计下,各类应用操作的实现方式。

  标签(Tag)模型,我大概设计了 4 种(也可以在这个基础上进行扩展),如下:

  1. Tag 存在于 Post 中。

  2. Tag 独立 Post(一对多关系)。

  3. Tag 独立 Post(一对多关系),Post 中多一个 Tags。

  4. Tag 和 Post 都独立,创建 TagMap 映射(多对多关系)。

  应用操作(EF Linq 实现),我大概想了 8 种,对于 Tag 的一般操作,我想应该都包含了,如下:

  1. 添加 Post-Tag

  2. 单独修改 Tag

  3. 在 Post 中修改 Tag

  4. 单独删除 Tag

  5. 在 Post 中删除Tag

  6. 查询 Tag(带数量统计)

  7. 查询 Post(Tag 展示)

  8. 根据 Tag 查询 Post 列表

  下面我们分别来探讨下。

  1. Tag 存在于 Post 中。

  Tag 模型图:

  

  Tag 模型说明:这个 Tag 模型是最简单的,Tag 直接存在 Post 中,但是模型简单,就意味着应用操作实现会很复杂。

  应用操作实现代码:

  public void Tags1()

  {

  using (var context = new TagsDbContext())

  {

  //1.添加post-tag

  var postAdd = new Post1 { UserId = 1, Title = "title", Content = "content", Tags = ".net|asp.net vnext" };

  context.Post1s.Add(postAdd);

  //2.4.6单独对tag进行修改、删除、查询(带数量统计),难于登天。。。

  //3.在post中修改tag

  var postModify = context.Post1s.FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);

  postModify.Tags.Replace("asp.net vnext", "asp.net5");

  //5.在post中删除tag

  var postTagDelete = context.Post1s.FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);

  postTagDelete.Tags.Replace("asp.net vnext", "");

  //7.查询post(tag展示)

  var postSelect = context.Post1s.FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);

  postSelect.Tags.Replace('|', ',');

  //8.根据tag查询post

  var postTagSelect = context.Post1s.Where(p => p.Tags.Contains("asp.net5") && p.UserId == 1).ToList();

  context.SaveChanges();

  }

  }

  结论:可以看到,对于 2.4.6 应用操作,这种模型根本就没办法进行操作(也可以,但实现起来太复杂),2.4.6 应用操作属于对 Tag 的单独操作,如果应用场景只要求在 Post 中进行 Tag 操作,这种模型是完全可以胜任的,但如果要对 Tag 进行单独操作,用这种 Tag 模型,那就是自杀行为。。。

  2. Tag 独立 Post(一对多关系)。

  Tag 模型图:

  

  Tag 模型说明:这种设计虽然把 Tag 和 Post 分离了,但需要注意的是,Post 和 Tag 的关系是一对多,有人会说,Post 和 Tag 的关系不是多对多的吗?一个 Tag 也可能对应多个 Post,但这种模型设计并不是这样,Tag 中有一个 PostId,表示这个 Tag 属于哪个 Post,比如有这样的示例:Tag 为 ASP.NET 的 Post 有两篇,那么在 Tag 中就会有两条 Tag 为 ASP.NET 的数据,但对应不同的 PostId。

  应用操作实现代码:

  public void Tags2()

  {

  using (var context = new TagsDbContext())

  {

  //1.添加post-tag

  var postAdd = new Post2 { UserId = 1, Title = "title", Content = "content" };

  postAdd.Tag2s.Add(new Tag2 { UserId = 1, TagName = ".net" });

  postAdd.Tag2s.Add(new Tag2 { UserId = 1, TagName = "asp.net vnext" });

  context.Post2s.Add(postAdd);

  //2.单独修改tag

  var tagsModify = context.Tag2s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).ToList();

  tagsModify.ForEach(t => t.TagName = "asp.net5");

  //3.在post中修改tag

  var tagModify = context.Tag2s.FirstOrDefault(t => t.TagName == "asp.net vnext" && t.UserId == 1 && t.PostId == 1);

  tagModify.TagName = "asp.net5";

  //4.单独删除tag

  var tagsDelete = context.Tag2s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).ToList();

  context.Tag2s.RemoveRange(tagsDelete);

  //5.在post中删除tag

  var tagDelete = context.Tag2s.FirstOrDefault(t => t.TagName == "asp.net vnext" && t.UserId == 1 && t.PostId == 1);

  context.Tag2s.Remove(tagDelete);

  //6.查询tag(带数量统计)

  var tagsSelect = from t in context.Tag2s

  where t.UserId == 1

  group t by t.TagName into g

  orderby g.Count() descending

  select new

  {

  TagName = g.Key,

  UseCount = g.Count()

  };

  //7.查询post(tag展示)

  var postSelect = context.Post2s.Include(p => p.Tag2s).FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);

  var tags = string.Join(",", postSelect.Tag2s.Select(t => t.TagName));

  //8.根据tag查询post

  var postTagSelect = from p in context.Post2s

  join t in context.Tag2s on p.PostId equals t.PostId

  where t.TagName == "asp.net5" && p.UserId == 1

  select p;

  context.SaveChanges();

  }

  }

  结论:可以看到,使用这种 Tag 模型,7种应用操作的实现都不是很复杂,但有一个缺点是:Tag 重复数据会很多,如果有 10 个 Post,每个 Post 有 3 个 Tag,不管 Tag 是否相同,那么 Tag 的数据就是 30 条。如果对于数据量要求不大的话,可以采用这种方式,毕竟实现起来不是很复杂(比如其他三种的实现),我个人也比较偏向这种 Tag 模型设计。

  3. Tag 独立 Post(一对多关系),Post 中多一个 Tags。

  

  Tag 模型说明:这种模型设计和上面第二种差不多,只不过在 Post 中多了个 Tags(String 类型),它的作用就是为了在 Post Tag 展示的时候,不用再去关联查找 Tag,方便是方便,但我们需要付出一些代码,那就是需要对 Post 中的 Tags 进行维护,利与弊,我们看下应用操作的实现,就知道了。

  应用操作实现代码:

  public void Tags3()

  {

  using (var context = new TagsDbContext())

  {

  //1.添加post-tag

  var postAdd = new Post3 { UserId = 1, Title = "title", Content = "content", Tags = ".net|asp.net vnext" };

  context.Post3s.Add(postAdd);

  context.Tag3s.Add(new Tag3 { UserId = 1, TagName = ".net" });

  context.Tag3s.Add(new Tag3 { UserId = 1, TagName = "asp.net vnext" });

  //2.单独修改tag

  var tagsModify = context.Tag3s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).ToList();

  var postsModify = (from p in context.Post3s

  join t in tagsModify on p.PostId equals t.PostId

  select p).ToList();

  tagsModify.ForEach(t => t.TagName = "asp.net5");

  postsModify.ForEach(p => p.Tags.Replace("asp.net vnext", "asp.net5"));

  //3.在post中修改tag

  var tagModify = context.Tag3s.FirstOrDefault(t => t.TagName == "asp.net vnext" && t.UserId == 1 && t.PostId == 1);

  tagModify.TagName = "asp.net5";

  var postModify = context.Post3s.FirstOrDefault(t => t.UserId == 1 && t.PostId == 1);

  postModify.Tags.Replace("asp.net vnext", "asp.net5");

  //4.单独删除tag

  var tagsDelete = context.Tag3s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).ToList();

  var postsTagsModify = (from p in context.Post3s

  join t in tagsModify on p.PostId equals t.PostId

  select p).ToList();

  context.Tag3s.RemoveRange(tagsDelete);

  postsModify.ForEach(p => p.Tags.Replace("asp.net vnext", ""));

  //5.在post中删除tag

  var tagDelete = context.Tag3s.FirstOrDefault(t => t.TagName == "asp.net vnext" && t.UserId == 1 && t.PostId == 1);

  context.Tag3s.Remove(tagDelete);

  var postTagDelete = context.Post3s.FirstOrDefault(t => t.UserId == 1 && t.PostId == 1);

  postTagDelete.Tags.Replace("asp.net vnext", "");

  //6.查询tag(带数量统计)

  var tagsSelect = from t in context.Tag3s

  where t.UserId == 1

  group t by t.TagName into g

  orderby g.Count() descending

  select new

  {

  TagName = g.Key,

  UseCount = g.Count()

  };

  //7.查询post(tag展示)

  var postSelect = context.Post3s.FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);

  postSelect.Tags.Replace("|", ",");

  //8.根据tag查询post

  var postTagSelect = from p in context.Post3s

  join t in context.Tag3s on p.PostId equals t.PostId

  where t.TagName == "asp.net5" && p.UserId == 1

  select p;

  context.SaveChanges();

  }

  }

  结论:先不看应用操作的具体实现,单纯从代码量上和第二种进行对比,会发现这种 Tag 模型的应用操作实现代码会比较多,添加、修改和删除 Tag,都要对 Post 中的 Tags 进行操作,而我们做这些多余的工作,仅仅是换来的是,最后查询 Post 而不关联 Tag,总感觉有点得不偿失,但并不意味着这种 Tag 模型实现就无用武之地,如果我们的应用场景,要求对 Tag 操作,必须通过 Post,比如修改 Tag,则必须通过 Post 进行修改,也就是不能对 Tag 进行独立操作,那么这种 Tag 模型就很适用。

  应用场景不要求对 Tag 进行独立操作,上面说到,第一种 Tag 模型设计也适用啊,它们有什么不同呢? 分离 Tag 的好处是什么呢?很简单,就是为了方便 Tag 使用数量的统计,如果应用场景要求这个操作,第一种 Tag 模型设计就不适用了。

  4. Tag 和 Post 都独立,创建 TagMap 映射(多对多关系)。

  

  Tag 模型说明:这种 Tag 模型和上面第二种形成鲜明对比,上面第二种 Post 和 Tag 是一对多关系,而这种是多对多关系,第二种会出现重复 Tag 数据,而这种则不会。从模型图中,我们可以看到,Post 和 Tag 是独立存在的,它们通过一个 TagMap 进行映射关联,Tag 中的 UserId 和 TagName 是唯一的,并且多了一个 UseCount,在第二种 Tag 模型中,因为 Tag 根据 Post 产生,我们想要统计 Tag 的使用数量,直接对 Tag 进行 GroupBy 就可以了(具体看第二种的实现代码),而这种 Tag 模型,某一特定用户下的 Tag 是唯一的,所以要想统计 Tag 的使用数量,就必须通过 TagMap(需要关联 Tag 实现),既然 Tag 是独立的,那还不如增加一个 UseCount 更加方便。

  这种 Tag 模型设计是四种方案中最复杂的,好处就是模型更加健壮,方便扩展,没有荣冗余数据产生,那坏处呢?我们接着看下面。

  应用操作实现代码:

  public void Tags4()

  {

  using (var context = new TagsDbContext())

  {

  //1.添加post-tag

  var postAdd = new Post4 { UserId = 1, Title = "title", Content = "content" };

  context.Post4s.Add(postAdd);

  var tagAdd1 = context.Tag4s.FirstOrDefault(t => t.TagName == ".net" && t.UserId == 1);

  var tagAdd2 = context.Tag4s.FirstOrDefault(t => t.TagName == "asp.net vnext" && t.UserId == 1);

  if (tagAdd1 != null)

  tagAdd1.UseCount++;

  else

  context.Tag4s.Add(new Tag4 { UserId = 1, TagName = ".net", UseCount = 1 });

  if (tagAdd2 != null)

  tagAdd1.UseCount++;

  else

  context.Tag4s.Add(new Tag4 { UserId = 1, TagName = "asp.net vnext", UseCount = 1 });

  context.TagMap4s.Add(new TagMap4 { PostId = postAdd.PostId, TagId = tagAdd1.TagId });

  context.TagMap4s.Add(new TagMap4 { PostId = postAdd.PostId, TagId = tagAdd2.TagId });

  //2.单独修改tag

  var tagModify = context.Tag4s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).FirstOrDefault();

  tagModify.TagName = "asp.net5";

  //3.在post中修改tag

  var tagModify2 = (from t in context.Tag4s

  where t.UserId == 1 && t.TagName == "asp.net vnext"

  join m in context.TagMap4s on t.TagId equals m.TagId

  where m.PostId == 1

  select t).FirstOrDefault();

  tagModify2.UseCount--;

  var tagModify3 = context.Tag4s.FirstOrDefault(t => t.TagName == "asp.net 5" && t.UserId == 1);

  if (tagModify3 != null)

  tagModify3.UseCount++;

  else

  context.Tag4s.Add(new Tag4 { UserId = 1, TagName = "asp.net 5", UseCount = 1 });

  var postModify = context.Post4s.FirstOrDefault(p => p.PostId == 1 && p.UserId == 1);

  var tagMapDelete= context.TagMap4s.FirstOrDefault(p => p.PostId == 1 && p.TagId == tagModify2.TagId);

  context.TagMap4s.Remove(tagMapDelete);

  postModify.TagMap4s.Add(new TagMap4 { PostId = postModify.PostId, TagId = tagModify3.TagId });

  //4.单独删除tag

  var tagDelete = context.Tag4s.Where(t => t.TagName == "asp.net vnext" && t.UserId == 1).FirstOrDefault();

  var tagMapsDelete = context.TagMap4s.Where(t => t.TagId == tagDelete.TagId).ToList();

  context.Tag4s.Remove(tagDelete);

  context.TagMap4s.RemoveRange(tagMapsDelete);

  //5.在post中删除tag

  var tagDelete2 = (from t in context.Tag4s

  where t.UserId == 1 && t.TagName == "asp.net vnext"

  join m in context.TagMap4s on t.TagId equals m.TagId

  where m.PostId == 1

  select t).FirstOrDefault();

  tagDelete2.UseCount--;

  var tagMapDelete2 = context.TagMap4s.FirstOrDefault(p => p.PostId == 1 && p.TagId == tagDelete2.TagId);

  context.TagMap4s.Remove(tagMapDelete2);

  //6.查询tag(带数量统计)

  var tagsSelect = context.Tag4s.Where(t => t.UserId == 1).ToList();

  //7.查询post(tag展示)

  var postSelect = context.Post4s.FirstOrDefault(p =>p.PostId == 1 && p.UserId == 1);

  var tagsSelect2 = (from t in context.Tag4s

  where t.UserId == 1

  join m in context.TagMap4s on t.TagId equals m.TagId

  select t).ToList();

  var tags = string.Join(",", tagsSelect2.Select(t => t.TagName));

  //8.根据tag查询post

  var postTagSelect = from p in context.Post4s

  join m in context.TagMap4s on p.PostId equals m.PostId

  join t in context.Tag4s on m.TagId equals t.TagId

  where t.TagName == "asp.net5" && p.UserId == 1 && t.UserId == 1

  select p;

  context.SaveChanges();

  }

  }

  结论:单从代码量上来说,这种应用操作实现代码量最多,其实大部分操作都是在判断 Tag,也就是为了利用现有的 Tag 数据,并不是像前面两种,不管 Tag 是否存在,直接添加、修改和删除,统计 Tag 使用数量实现,是四种方案中最简单的,其余的应用操作,因为模型层级越多、关联越多,操作起来就会越复杂,但不可否认,这种设计,是四种方案中“最理想”的。

  5. 简要总结

  深入去设计并实现这四种 Tag 模型方案,其实有很多的感触,是之前没实现体会不到的,比如:

  模型的简单和复杂是相对的:并不是模型越简单越好,也不是越复杂越好,第一和第二种方案就说明这点。

  模型的设计是相对于应用场景的:在不能确定应用场景的情况下,不能说哪种模型设计是好是坏,交通工具有很多种,飞机快过汽车,但飞机在陆地上跑不过汽车。

  最后,简要总结下四种 Tag 模型设计的一些应用场景:

  1. Tag 存在于 Post 中:1.3.5.7.8 应用操作,不要求独立对 Tag 进行操作和数量统计。

  2. Tag 独立 Post(一对多关系):1-8 应用操作,数据量不是很大,对 Tag 操作比较频繁,对 Post 操作不频繁。

  3. Tag 独立 Post(一对多关系),Post 中多一个 Tags:1-8 应用操作,数据量不是很大,对 Post 操作比较频繁,对 Tag 操作不频繁。

  4. Tag 和 Post 都独立,创建 TagMap 映射(多对多关系):1-8 应用操作,Tag 业务变化比较频繁,对 Tag 和 Post 操作都比较频繁。

  对于我自己来说,上面四种 Tag 模型设计,我最偏向于第二种和第四种,如果非要选择一种的话,我可能会选择第二种,为什么呢?因为谁写过应用操作的实现代码,谁知道,哈哈!!!

  1 对用户输入做过多的约定和假设

  配置文件App.config中有一个设定报表路径的配置节:

  在程序中有一个销售报表文件SalesReport.rpt,用代码调用这个报表,可能会写成:

  string salesReport=ReportPath + "SalesReport.rpt";

  因为路径末尾没有加反斜线,会抛出找不到报表文件的异常(FileNotFoundException)。

  经过修改,可以考虑在配置前的末尾强制要加反斜线\,修改之后的配置节如下:

  经过这样的修改,上面代码运行正常。

  为此,我认为应该在程序中考虑增加一个判断,对于路径默认没有带反斜线\的,给它加一个。程序代码也就是一行:

  if (!reportPath.EndsWith("\\"))

  reportPath += "\\";

  这个例子也可以通过调用方法System.IO.Path.Combine实现,避免用户没有输入反斜线的问题。

  再举一个例子,比如IP地址后如果用户没有加端口号码,程序中做一个简单的判断,加上默认的端口号,不强制约定用户的输入的值,比较下面的两种配置方法。

  2 在数据库服务器中可以处理的查询,移动到程序端处理

  这一条产生的原因在于C#有很好用的foreach语句,永远不会抛出越界异常。所以就产生了一些偷懒的做法,宁愿把数据读到程序中处理,也不想优化SQL语句查询。参考下面的代码,我遇到后都是直接强制要求重写。

  IPrefetchPath2 prefetchPath=new PrefetchPath2((int)EntityType.SalesOrderEntity);

  prefetchPath.Add(PartEntity.PrefetchPathSalesOrderDetail);

  SalesOrderEntity order=salesOrderManager.GetPart("SC201507",prefetchPath);

  string description=string.Empty;

  foreach(SalesOrderDetailEntity orderDetail in order.SalesOrderDetail)

  {

  if(orderDetail.ItemNo="FLEX102030009")

  {

  description=entity.Description;

  break;

  }

  }

  能在数据库服务器中做的事情,就不应该移动到代码中处理,这样没有效率,代码没有质量。

  3 数据库SQL语句未考虑到重复执行或是自动化部署

  坏味道的代码:

  ALTER TABLE JobOrder ADD FiscalYear DECIMAL(4,0), PeriodNo DECIMAL(2,0)

  应该修改成下面的代码。

  IF NOT EXISTS(SELECT C.NAME FROM DBO.SYSCOLUMNS C, DBO.SYSOBJECTS O WHERE O.ID = C.ID AND O.NAME = 'CompanyParameter' AND C.NAME = 'POApprovalRequired')

  BEGIN

  Alter Table CompanyParameter ADD POApprovalRequired [nvarchar] (1) DEFAULT 'N'

  END

  4 DataTable 的用法中,用数字作索引,在维护时很难看见数字列名的含义

  坏味道的代码:

  string cachePath=string.Empty;

  DataTable table =queryManager.FetchTableBySqlCommand(query);

  foreach (DataRow row in table.Rows)

  cachePath = row.ItemArray[2].ToString();

  应该修改成用列名作索引:

  string cachePath=string.Empty;

  DataTable table =queryManager.FetchTableBySqlCommand(query);

  foreach (DataRow row in table.Rows)

  cachePath = row.ItemArray[“CachePath"].ToString();

  5 字符串操作没有考虑大小写

  字符串判断比较可以用等号(=)操作符,我推荐的写法是用Equal方法,传入参数是否区分大小写。

  字符串搜索也需要考虑大小写问题,比如下面的代码,不区大小写搜索。

  string findWath="SO2015";

  if (txtOrderNo.Text.IndexOf(findWhat, 0, StringComparison.InvariantCultureIgnoreCase) != -1)

  字符串空值判断用string.IsNullOrWhiteSpace,字符串设空值用string.Empty。

  6 长时间运行任务时,没有设置光标为等待状态,控件的状态也未变灰

  执行任务前

  UseWaitCursor = true;

  btnAdd.Enable=false;

  任务执行完成之后

  UseWaitCursor = false;

  btnAdd.Enable=true;

  7 打开文件对话框没有设置合适的标题和文件类型过滤器

  这是个小细节的地方,从stackoverflow中搜索几个filter供参考用。

  public const string ImageFilter = "Image files (*.jpg, *.jpeg, *.jpe, *.jfif, *.png,*.gif,*.bmp) | *.jpg; *.jpeg; *.jpe; *.jfif; *.png;*.gif;*bmp";

  public const string PDFFilter = "PDF files (*.pdf) | *.pdf";

  public const string ExcelFilter = "Excel Files|*.xls;*.xlsx;*.xlsm";

  8 删除数据时,如非必要,不用先读取到客户端再执行删除。

  如有验证逻辑,考虑用过滤条件传递到数据库中验证数据,代替读取数据到程序中执行验证。

  9 日期时间没有考虑到小时分钟值

  DateTime.Now 是带小时分钟的, DateTime.Today是不带时间的。曾经有一段时间,总是查不到今天做的日记帐数据,总是要求客户把时间查询时间范围提前一天,原因是DateTime.Now带有小时分钟值。

  10 SQL脚本文件编码

  需要保存的格式是UTF8,避免GB2312或是BIG5导致乱码。

  11 同一个数据库中出现多种命名习惯

  SalesOrder 首字母大写

  Sales_Order 单词用下划线分开

  SL_Sales_Order 加模块前缀

  12 系统默认值来源没有依据

  采购订单税率默认为17点,一年定期银行利息是2.25,ABC分析的三个值依次是85,15,5。

  这些默认值都应该加一个功能来存储,而不是直接在代码中写死默认值。

  13 常量值不统一

  主要是日期时间最大最小值,推荐下面的代码,用于设定公共变量值。

  private static readonly DateTime _dateTimeMinValue = new DateTime(1753, 1, 1, 0, 0, 0, 0);

  private static readonly DateTime _dateTimeMaxValue = new DateTime(2099, 12, 31, 0, 0, 0, 0);

  public static DateTime DateTimeMinValue

  {

  get { return _dateTimeMinValue; }

  }

  public static DateTime DateTimeMaxValue

  {

  get { return _dateTimeMaxValue; }

  }

  数据库中时间值为空值字段,可以设置值为NULL,也可以用DateTimeMinValue。

  对于特殊类财务报表,必须要有一个时间范围,用DateTimeMinValue。

  myEntity.SetNewFieldValue((int)MyEntityFieldIndex.MyDateField, null);

  14 没有发挥数据绑定的的威力

  WinForms的数据绑定是双向的,可以将实体对象通过数据源控件取到DataGridView控件中。

  坏味道的代码:

  string customerName=gridQuotation.Rows[0].Cell[0].Value;

  应该是:

  QuotationEntity quotation= detailBindingSource.Current as QuotationEntity;

  QuotationEntity quotation = this.gridQuotation.GetRowListObject(row) as QuotationEntity;

  string customerName=quotation.CustomerName

  15 数据验证没有借助于.NET的正则表达式。

  坏味道:

  try

  {

  Convert.ToDecimal("123abc456");

  return true;

  }

  catch

  {

  return false;

  }

  应该是

  Regex regex = new Regex("a{1,2}");

  Match m = regex.Match("123abc456")

  if(m.Success)

  {

  //验证成功

  }

  这些验证方式可以封装为公共方法,实现代码复用。

  16 数据库查询没有考虑NULL值情况

  坏味道的代码:

  ResultsetFields fields = new ResultsetFields(1);

  fields.DefineField(PurchaseRequisitionDetailFields.Qty, 0);

  应该是:

  DbFunctionCall dbFunQty = new DbFunctionCall("ISNULL", new object[] { PurchaseRequisitionDetailFields.Qty, 0 });

  EntityField2 efieldQty = new EntityField2(PurchaseRequisitionDetailFields.Qty.Name, dbFunQty);

  itemFields.DefineField(efieldQty, 0);

  相当于ISNULL函数调用 ISNULL(Qty,0) 。

  指针和数组是一样的吗?

  经常可以看到“数组就是指针”、“数组名就是常量指针”这些说法,但真的是这样吗?让我们先看一下指针和数组的定义。

  1. 指针

  根据C99标准,指针的定义如下:

  A pointer type may be derived from a function type or an object type, called the referenced type. A pointer type describes an object whose value provides a reference to an entity of the referenced type. A pointer type derived from the referenced type T is sometimes called “pointer to T”. The construction of a pointer type from a referenced type is called "pointer type derivation".

  指针是一种派生类型,它描述了这样一个对象,其值为对某种类型实体的引用。它包含了两方面的涵义:实体和类型。

  需要注意的是,指针变量只是指针的一种形态,但指针并不仅仅只有指针变量。

  2. 数组

  数组的定义如下:

  An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T , the array type is sometimes called “array of T”. The construction of an array type from an element type is called “array type derivation”.

  数组类型同样是派生类型,它描述了某种对象的连续的非空集合,由其中元素类型和元素个数来刻画。

  由指针和数组定义可以看出,指针和数组是完全不同的类型。但数组名是指针吗?

  根据《征服C指针》一书,数组名并不是指针,只不过在表达式中,数组名可以解读成“指向它的初始元素的指针”。

  在一篇博文第二章 数组名是一个指针常量吗?中,作者就分析的更加透彻:

  “

  数组名是一个指针常量这种观点来源于数组名在表达式计算中与指针的结果等效性。例如下面的代码:

  1 int a[10], *p = a, *q;

  2 q = a + 1;

  3 q = p + 1;

  在效果上看,a + 1与 p + 1是相同的,这很容易给人一种a就是p的假象,但,这仅仅是假象。

  在《C与指针》一书中,作者用一个著名的例子阐述了数组名与指针的不同。在一个文件中定义:int a[10]; 然后在另一个文件中声明:extern int *a; 笔者不在这里重复其中的原理,书中的作者试图从底层操作上阐述数组名与指针的不同点,但笔者认为这个例子存在一些不足,a在表达式中会转换为一个非对象的符号地址,而指针a却是一个对象,用一个非对象去跟一个对象比较,有“偷跑”的嫌疑,这个例子只是说明了数组名的非对象性质,只能证明对象与非对象实体在底层操作上的不同,事实上,如上一章所述,指针也有非对象形态。笔者认为,无须从底层的角度上花费那么多唇舌,仅仅从字面上的语义就可以推翻数组名是一个指针的观点。

  首先,在C/C++中,数组类型跟指针类型是两种不同的派生类型,数组名跟指针是两种不同类型的实体,把数组类型的实体说成“是”另一个类型的实体,本身就是荒谬的;

  其次,a + 1在效果上之所以等同于p + 1,是因为a进行了数组到指针的隐式转换,这是一个转换的过程,是converted to而不是is a的过程。如果是两个相同的事物,又怎会有转换的过程呢?当把a放在a + 1表达式中时,a已经从一个数组名转换为一个指针,a是作为指针而不是数组名参与运算的;

  第三,a + 1与p + 1是等效关系,不是等价关系。等价是相同事物的不同表现形式,而等效是不同事物的相同效果。把数组名说成是指针实际上把等效关系误解为等价关系。

  因此,数组名不是指针,永远也不是,但在一定条件下,数组名可以转换为指针。

  ”

  而根据《C和指针》一书的第8章8.1节,作者提到:“只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量”。注意这个值是指针常量,而不是指针变量。我们不能修改常量的值。只有在两种场合下,数组名在表达式中不用指针常量来表示——就是当数组名作为sizeof操作符或单目操作符&的操作数时。sizeof返回整个数组的长度,而不是指向数组的指针的长度。取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针。

  综上所述,数组不是指针,数组名也只有在表达式中才会被当成一个指针常量。

  下标运算符[]和数组有关系吗?

  对于下标运算符,相信大家都再熟悉不过了,我们可用其方便快速访问数组中的元素。但它和数组有关系吗?先看一个例子:

  1 #inc

  2 using namespace std;

  3

  4 int main()

  5 {

  6 int p[10];

  7 for (int i = 0; i < 10; i++)

  8 p[i] = i + 1;

  9

  10 cout << "*(p + i)\t" << "p[i]" << endl;

  11 for (int i = 0; i < 10; i++)

  12 cout << *(p + i) << "\t\t" << p[i] << endl;

  13

  14 return 0;

  15 }

  程序的输出如下:

  所以,*(p + i)跟p[i]的效果是一样的。根据《征服C指针》,p[i]这种写法只不过是*(p + i)的简便写法。实际上,至少对于编译器来说,[]这样的运算符完全可以不存在。[]运算符是为了方便人们读写而引入的,是一种语法糖。

  注意,认为[]和数组没有关系中的[]指的是表达式中出现的下标运算符,而不是声明中的[]。

  二级指针和指针数组

  二级指针是指向指针的指针的简称,如下常见例子:

  int **p;

  而指针数组则是元素类型为指针的数组,如:

  int *p[10];

  二级指针和指针数组区别与联系

  我们知道指针和数组是不一样的,当然二级指针和指针数组也是不一样的。那他们有什么联系呢?

  下边请看一个例子:

  对于main函数,常见的其中两种写法如下:

  int main(int argc, char *argv[]);

  或

  int main(int argc, char **argv);

  根据Linux C编程一站式学习说法,函数原型中的[]表示指针而不表示数组,等价于char **argv。那为什么要写成char *argv[]而不写成char **argv呢?这样写给读代码的人提供了有用信息,argv不是指向单个指针,而是指向一个指针数组的首元素。数组中每个元素都是char *指针,指向一个命令行参数字符串。

  其实,就算在表达式中,它们也是等效的。

  二级指针和指针数组如何表示二维数组?

  在表达式中,二级指针和指针数组是等效的。所以我们下文可以只以二级指针来说明。

  对于一个二级指针而言,第一级指针指向一个指针数组的首元素,因此利用下标运算符[]即可获得指针数组的每一个元素;而指针数组每个元素存储的是指针,可以再额外指向另一个数组。这样一来,我们就可以利用二级指针来实现一个二维数组了,如下例:

  先看一个简单例子

  1 #include

  2 #include

  3 #include

  4

  5 int main(int argc, char *argv[])

  6 {

  7 int i;

  8 for(i = 0; i < argc; i++)

  9 {

  10 printf("argv[%d]=%s\t", i, argv[i]);

  11 int len = strlen(argv[i]), j;

  12 for(j = 0; j < len; j++)

  13 printf("%s[%d]=%c ", argv[i], j, argv[i][j]);

  14 printf("\n");

  15 }

  16 return 0;

  17 }

  在Linux下编译执行:

  二级指针妙用:删除单向链表

  主要参考:

  Two star programming

  Linus:利用二级指针删除单向链表(有详细解释,非常值得参考!)

  在删除单向链表(保留头指针head)时,我们可能会采用比较典型的做法:

  1 typedef struct node

  2 {

  3 struct node * next;

  4 ....

  5 } node;

  6

  7 typedef bool (* remove_fn)(node const * v);

  8

  9 // Remove all nodes from the supplied list for which the

  10 // supplied remove function returns true.

  11 // Returns the new head of the list.

  12 node * remove_if(node * head, remove_fn rm)

  13 {

  14 for (node * prev = NULL, * curr = head; curr != NULL; )

  15 {

  16 node * const next = curr->next;

  17 if (rm(curr))

  18 {

  19 if (prev)

  20 prev->next = next;

  21 else

  22 head = next;

  23 free(curr);

  24 }

  25 else

  26 prev = curr;

  27 curr = next;

  28 }

  29 return head;

  30 }

  但Linus大婶就要说这样子写的人了:“This person doesn’t understand pointers”。那对于Linus大婶来说,怎样做才是最好的?那就是利用二级指针,具体如下:

  1 void remove_if(node ** head, remove_fn rm)

  2 {

  3 for (node** curr = head; *curr; )

  4 {

  5 node * entry = *curr;

  6 if (rm(entry))

  7 {

  8 *curr = entry->next;

  9 free(entry);

  10 }

  11 else

  12 curr = &entry->next;

  13 }

  14 }

  果然,改写之后的程序简洁了许多,而且也不需要维护一个prev表项指针和考虑头指针的问题。

  在博文Linus:利用二级指针删除单向链表中,作者对利用二级指针的程序还附上了一个比较详细的解说:

  “

  对于——

  删除节点是表头的情况,输入参数中传入head的二级指针,在for循环里将其初始化curr,然后entry就是*head(*curr),我们马上删除它,那么第8行就等效于*head = (*head)->next,就是删除表头的实现。

  删除节点不是表头的情况,对于上面的代码,我们可以看到——

  1)(第12行)如果不删除当前结点 —— curr保存的是当前结点next指针的地址。

  2)(第5行) entry 保存了 *curr —— 这意味着在下一次循环:entry就是prev->next指针所指向的内存。

  3)(第8行)删除结点:*curr = entry->next; —— 于是:prev->next 指向了 entry -> next

转载于:https://www.cnblogs.com/ggamm/p/4727923.html

深入探讨:标签(Tag)的各种设计方案相关推荐

  1. 【Java编程系列】Java自定义标签-Tag

    热门系列: [Java编程系列]WebService的使用 [Java编程系列]在Spring MVC中使用工具类调用Service层时,Service类为null如何解决 [Java编程系列]Spr ...

  2. c 清除 html标签,13.4. 去除HTML的标签tag:htmlRemoveTag

    13.4. 去除HTML的标签tag:htmlRemoveTag /* * [Function] * remove html tag, retain html content * [Input] * ...

  3. 【Android RTMP】RTMP 数据格式 ( FLV 视频格式分析 | 文件头 Header 分析 | 标签 Tag 分析 | 视频标签 Tag 数据分析 )

    文章目录 安卓直播推流专栏博客总结 一. RTMP 格式解析 二. 文件头 Header 分析 三. 标签 Tag 分析 四. 视频标签 Tag 数据分析 安卓直播推流专栏博客总结 Android R ...

  4. java自定义标签 map_基于Spring MVC的自定义标签Tag

    基于Spring MVC的自定义标签Tag 1.环境准备 本次小项目基于Spring MVC进行测试呈现,项目依赖的jar文件列表如下: 2.搭建SpringMVC Spring MVC环境最关键的两 ...

  5. iOS学习系列 - 标签Tag列表的实现

    本文讲述如何实现一个标签Tag列表的效果. 在iOS项目中的效果为图上所示: 在各个任务下,包括对应的标签列表. 首先,自定义一个UILabel类: FillLabel.h: #import < ...

  6. 给你的ABAP对象打上标签(Tag)

    标签(Tag)几乎是信息管理软件的一个必备功能,目的是帮助用户更迅速地检索出自己需要的数据,以及对海量数据进行更有效的管理. 如今在ABAP Development Tool里也支持标签功能了,可以像 ...

  7. svn 分支(branch)和标签(tag)管理

    版本控制的一大功能是可以隔离变化在某个开发线上,这个开发线就是分支(branch). 分支通常用于开发新功能,而不会影响主干的开发.也就是说分支上的代码的编译错误.bug不会对主干(trunk)产生影 ...

  8. JUnit5学习之五:标签(Tag)和自定义注解

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于<JUnit5学习>系列 <JU ...

  9. Struts2 自己定义下拉框标签Tag

    自己定义标签主要包含三个步骤: 1.编写java类,继承TagSupport类. 2.创建tld文件,影射标签名和标签的java类. 3.jsp页面引入tld. 样例:自己定义下拉框标签 假设页面上有 ...

  10. Git 基础 - 打标签 tag

    打标签 同大多数 VCS 一样,Git 也可以对某一时间点上的版本打上标签.人们在发布某个软件版本(比如 v1.0 等等)的时候,经常这么做.本节我们一起来学习如何列出所有可用的标签,如何新建标签,以 ...

最新文章

  1. 车联网APP,安全设施薄弱的山寨品
  2. linux中的fg命令
  3. [gic]-gicv3/gicv4的feature总结
  4. STL删除元素注意事项
  5. iOS蓝牙开发CoreBluetooth快速入门
  6. 现代软件工程讲义 11 项目管理 - 事后诸葛亮会议
  7. 猜数大小编程c语言_猜数正确编程
  8. js操作select标签
  9. 应用加速,数字人民币接入多地交通出行场景 | 产业区块链发展周报
  10. matlab求导程序,「matlab求导」matlab 求导的一个简单程序 - 金橙教程网
  11. 计算机视觉技术与应用综述
  12. lecture 16:DID双重差分方法
  13. 自我觉察日志——9.17 (为什么老想买东西?)
  14. java无法找到加载主类是什么意思_java – 什么是“找不到或加载主类”是什么意思?...
  15. 记录---提取合并VCF文件
  16. mybatis/mybatis-plus 子查询实现 涉及到in、exist操作
  17. iOS集成EasyAR实现虚拟现实
  18. 400 (Bad Request)错误的解决方法
  19. CSS3学习笔记三---怪异盒模型 | 弹性盒*
  20. 姚晨为何可以新浪微博排名第一

热门文章

  1. LoadRunner之Controller的Goal-Oriented Scenario(目标场景)
  2. 关于Html中文乱码的问题
  3. 如何从字符串中取字符
  4. Web3的应用及发展
  5. 【转载】ACM入门 .
  6. 什么是jdk?什么是jre?jdk和jre的区别。
  7. 9.29黄金实时行情趋势分析、黄金原油价格涨跌分析及操作建议
  8. 记录我小白起步的点滴生活
  9. like语句太慢 sqlserver_SQLServer找出执行慢的SQL语句
  10. 学生成绩管理系统利用数据结构顺序表来进行二分查找,直接插入,快速排序操作